Android 14 Photo Picker Change
READ_MEDIA_VISUAL_USER_SELECTED with code demo
#Android #Android14 #photopicker #permission
Introduction to New Feature:
- Android 14 introduces “Selective Photo and Video Access,” enhancing user privacy by allowing users to grant apps access to specific photos and videos instead of all.
Historical Context of Android Permissions:
- Android 1.0: No restrictions on reading/writing to public storage.
- Android 1.6: Introduction of WRITE_EXTERNAL_STORAGE for writing to public storage.
- Android 4.4: READ_EXTERNAL_STORAGE permission is required for reading public storage.
- Android 6.0: Runtime permissions introduced for high-risk actions, requiring user consent during app use.
- Android 10: Introduction of Scoped Storage; apps must use MediaStore API to access public storage.
- Android 11: Removal of requestLegacyExternalStorage; MANAGE_EXTERNAL_STORAGE introduced for specific apps.
- Android 13: Segmentation of storage access permissions into READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, and READ_MEDIA_AUDIO.
- Android 14: Partial grant of choosing photos and videos with READ_MEDIA_VISUAL_USER_SELECTED
Details of Android 14’s New Feature:
- Introduces selective access for photos and videos.
- Users can choose whether to grant apps access to all photos/videos or only selected ones.
Developer Challenges:
- New permissions model adds complexity for developers.
- Need to account for various permission scenarios in code.
Practical Demonstration:
- The article includes a demo on implementing the new permission model in Android 14, highlighting potential complexities
Everyone knows to declare permission in the AndroidManifest.xml. However, it is more challenging than you thought. Because of historical different version restriction problems. We need to write down as below
<manifest>
<!-- Devices running Android 12L (API level 32) or lower -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Devices running Android 13 (API level 33) or higher -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- To handle the reselection within the app on Android 14 (API level 34) -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
...
</manifest>
Furthermore, we need to request permission in the run time for different os versions.
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { _ ->
// deal with result after request permission
}
private fun requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
permissionLauncher.launch(
arrayOf(READ_MEDIA_IMAGES,
READ_MEDIA_VIDEO,
READ_MEDIA_VISUAL_USER_SELECTED)
)
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {
permissionLauncher.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO))
} else {
permissionLauncher.launch(arrayOf(READ_EXTERNAL_STORAGE))
}
}
Besides, we should have check permissions to do our customization
private fun checkPermissionResult() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
&& (ContextCompat.checkSelfPermission(this, READ_MEDIA_IMAGES) == PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, READ_MEDIA_VIDEO) == PERMISSION_GRANTED)
) {
// get fullly access photos or videos include and above 13
} else if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
ContextCompat.checkSelfPermission(this, READ_MEDIA_VISUAL_USER_SELECTED) == PERMISSION_GRANTED
) {
// partial access photos or videos include and above 14
} else if (ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
// get read storage below 13
} else {
// no permission to access
}
}
Finally, you’ve verified you have access to the right storage permissions, you can interact with MediaStore
to query the device library (the same approach works whether the granted access is partial or full):
val cursor = contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null,
null, null, "${MediaStore.MediaColumns.DATE_ADDED} desc"
)
if (cursor != null) {
while (cursor.moveToNext()) {
val id =
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
val uri =
ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
imageList.add(uri)
}
cursor.close()
}
Here is the full code