summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/framework/java/android/provider/EmbeddedPhotopickerFeatureInfo.java2
-rw-r--r--apex/framework/java/android/provider/MediaStore.java5
-rw-r--r--mediaprovider_flags.aconfig1
-rw-r--r--pdf/OWNERS1
-rw-r--r--photopicker/Android.bp1
-rw-r--r--photopicker/res/drawable/photopicker_selected_media.xml19
-rw-r--r--photopicker/res/values-af/core_strings.xml7
-rw-r--r--photopicker/res/values-af/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-af/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-am/core_strings.xml7
-rw-r--r--photopicker/res/values-am/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-am/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ar/core_strings.xml7
-rw-r--r--photopicker/res/values-ar/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-ar/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-as/core_strings.xml7
-rw-r--r--photopicker/res/values-as/feature_cloud_strings.xml8
-rw-r--r--photopicker/res/values-as/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-az/core_strings.xml7
-rw-r--r--photopicker/res/values-az/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-az/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-b+sr+Latn/core_strings.xml7
-rw-r--r--photopicker/res/values-b+sr+Latn/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-b+sr+Latn/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-be/core_strings.xml7
-rw-r--r--photopicker/res/values-be/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-be/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-bg/core_strings.xml7
-rw-r--r--photopicker/res/values-bg/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-bg/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-bn/core_strings.xml1
-rw-r--r--photopicker/res/values-bn/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-bn/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-bs/core_strings.xml7
-rw-r--r--photopicker/res/values-bs/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-bs/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ca/core_strings.xml7
-rw-r--r--photopicker/res/values-ca/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-ca/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-cs/core_strings.xml7
-rw-r--r--photopicker/res/values-cs/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-cs/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-da/core_strings.xml7
-rw-r--r--photopicker/res/values-da/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-da/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-de/core_strings.xml7
-rw-r--r--photopicker/res/values-de/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-de/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-el/core_strings.xml7
-rw-r--r--photopicker/res/values-el/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-el/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-en-rAU/core_strings.xml7
-rw-r--r--photopicker/res/values-en-rAU/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-en-rAU/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-en-rCA/core_strings.xml1
-rw-r--r--photopicker/res/values-en-rCA/feature_cloud_strings.xml8
-rw-r--r--photopicker/res/values-en-rCA/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-en-rGB/core_strings.xml7
-rw-r--r--photopicker/res/values-en-rGB/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-en-rGB/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-en-rIN/core_strings.xml7
-rw-r--r--photopicker/res/values-en-rIN/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-en-rIN/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-en-rXC/core_strings.xml1
-rw-r--r--photopicker/res/values-en-rXC/feature_cloud_strings.xml8
-rw-r--r--photopicker/res/values-en-rXC/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-es-rUS/core_strings.xml7
-rw-r--r--photopicker/res/values-es-rUS/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-es-rUS/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-es/core_strings.xml7
-rw-r--r--photopicker/res/values-es/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-es/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-et/core_strings.xml7
-rw-r--r--photopicker/res/values-et/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-et/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-eu/core_strings.xml7
-rw-r--r--photopicker/res/values-eu/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-eu/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-fa/core_strings.xml7
-rw-r--r--photopicker/res/values-fa/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-fa/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-fi/core_strings.xml7
-rw-r--r--photopicker/res/values-fi/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-fi/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-fr-rCA/core_strings.xml7
-rw-r--r--photopicker/res/values-fr-rCA/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-fr-rCA/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-fr/core_strings.xml7
-rw-r--r--photopicker/res/values-fr/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-fr/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-gl/core_strings.xml1
-rw-r--r--photopicker/res/values-gl/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-gl/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-gu/core_strings.xml7
-rw-r--r--photopicker/res/values-gu/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-gu/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-hi/core_strings.xml1
-rw-r--r--photopicker/res/values-hi/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-hi/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-hr/core_strings.xml7
-rw-r--r--photopicker/res/values-hr/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-hr/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-hu/core_strings.xml7
-rw-r--r--photopicker/res/values-hu/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-hu/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-hy/core_strings.xml7
-rw-r--r--photopicker/res/values-hy/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-hy/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-in/core_strings.xml7
-rw-r--r--photopicker/res/values-in/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-in/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-is/core_strings.xml7
-rw-r--r--photopicker/res/values-is/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-is/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-it/core_strings.xml7
-rw-r--r--photopicker/res/values-it/feature_cloud_strings.xml8
-rw-r--r--photopicker/res/values-it/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-iw/core_strings.xml7
-rw-r--r--photopicker/res/values-iw/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-iw/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ja/core_strings.xml1
-rw-r--r--photopicker/res/values-ja/feature_cloud_strings.xml8
-rw-r--r--photopicker/res/values-ja/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ka/core_strings.xml1
-rw-r--r--photopicker/res/values-ka/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-ka/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-kk/core_strings.xml7
-rw-r--r--photopicker/res/values-kk/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-kk/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-km/core_strings.xml7
-rw-r--r--photopicker/res/values-km/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-km/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-kn/core_strings.xml1
-rw-r--r--photopicker/res/values-kn/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-kn/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ko/core_strings.xml7
-rw-r--r--photopicker/res/values-ko/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-ko/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ky/core_strings.xml7
-rw-r--r--photopicker/res/values-ky/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-ky/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-lo/core_strings.xml1
-rw-r--r--photopicker/res/values-lo/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-lo/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-lt/core_strings.xml1
-rw-r--r--photopicker/res/values-lt/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-lt/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-lv/core_strings.xml7
-rw-r--r--photopicker/res/values-lv/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-lv/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-mk/core_strings.xml7
-rw-r--r--photopicker/res/values-mk/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-mk/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ml/core_strings.xml7
-rw-r--r--photopicker/res/values-ml/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-ml/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-mn/core_strings.xml7
-rw-r--r--photopicker/res/values-mn/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-mn/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-mr/core_strings.xml1
-rw-r--r--photopicker/res/values-mr/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-mr/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ms/core_strings.xml1
-rw-r--r--photopicker/res/values-ms/feature_cloud_strings.xml8
-rw-r--r--photopicker/res/values-ms/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-my/core_strings.xml11
-rw-r--r--photopicker/res/values-my/feature_cloud_strings.xml8
-rw-r--r--photopicker/res/values-my/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-nb/core_strings.xml7
-rw-r--r--photopicker/res/values-nb/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-nb/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ne/core_strings.xml1
-rw-r--r--photopicker/res/values-ne/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-ne/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-nl/core_strings.xml1
-rw-r--r--photopicker/res/values-nl/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-nl/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-or/core_strings.xml9
-rw-r--r--photopicker/res/values-or/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-or/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-pa/core_strings.xml7
-rw-r--r--photopicker/res/values-pa/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-pa/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-pl/core_strings.xml7
-rw-r--r--photopicker/res/values-pl/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-pl/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-pt-rBR/core_strings.xml7
-rw-r--r--photopicker/res/values-pt-rBR/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-pt-rBR/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-pt-rPT/core_strings.xml1
-rw-r--r--photopicker/res/values-pt-rPT/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-pt-rPT/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-pt/core_strings.xml7
-rw-r--r--photopicker/res/values-pt/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-pt/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ro/core_strings.xml7
-rw-r--r--photopicker/res/values-ro/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-ro/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ru/core_strings.xml7
-rw-r--r--photopicker/res/values-ru/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-ru/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-si/core_strings.xml7
-rw-r--r--photopicker/res/values-si/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-si/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-sk/core_strings.xml7
-rw-r--r--photopicker/res/values-sk/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-sk/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-sl/core_strings.xml7
-rw-r--r--photopicker/res/values-sl/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-sl/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-sq/core_strings.xml7
-rw-r--r--photopicker/res/values-sq/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-sq/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-sr/core_strings.xml7
-rw-r--r--photopicker/res/values-sr/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-sr/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-sv/core_strings.xml7
-rw-r--r--photopicker/res/values-sv/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-sv/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-sw/core_strings.xml7
-rw-r--r--photopicker/res/values-sw/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-sw/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ta/core_strings.xml7
-rw-r--r--photopicker/res/values-ta/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-ta/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-te/core_strings.xml7
-rw-r--r--photopicker/res/values-te/feature_cloud_strings.xml8
-rw-r--r--photopicker/res/values-te/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-th/core_strings.xml7
-rw-r--r--photopicker/res/values-th/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-th/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-tl/core_strings.xml7
-rw-r--r--photopicker/res/values-tl/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-tl/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-tr/core_strings.xml7
-rw-r--r--photopicker/res/values-tr/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-tr/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-uk/core_strings.xml7
-rw-r--r--photopicker/res/values-uk/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-uk/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-ur/core_strings.xml7
-rw-r--r--photopicker/res/values-ur/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-ur/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-uz/core_strings.xml7
-rw-r--r--photopicker/res/values-uz/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-uz/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-vi/core_strings.xml7
-rw-r--r--photopicker/res/values-vi/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-vi/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-zh-rCN/core_strings.xml7
-rw-r--r--photopicker/res/values-zh-rCN/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-zh-rCN/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-zh-rHK/core_strings.xml7
-rw-r--r--photopicker/res/values-zh-rHK/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-zh-rHK/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-zh-rTW/core_strings.xml7
-rw-r--r--photopicker/res/values-zh-rTW/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-zh-rTW/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values-zu/core_strings.xml7
-rw-r--r--photopicker/res/values-zu/feature_cloud_strings.xml16
-rw-r--r--photopicker/res/values-zu/feature_preview_strings.xml4
-rw-r--r--photopicker/res/values/core_strings.xml3
-rw-r--r--photopicker/src/com/android/photopicker/MainActivity.kt294
-rw-r--r--photopicker/src/com/android/photopicker/PhotopickerDeviceConfigReceiver.kt3
-rw-r--r--photopicker/src/com/android/photopicker/core/PhotopickerApp.kt194
-rw-r--r--photopicker/src/com/android/photopicker/core/banners/Banner.kt65
-rw-r--r--photopicker/src/com/android/photopicker/core/components/mediagrid/MediaGrid.kt49
-rw-r--r--photopicker/src/com/android/photopicker/core/configuration/ConfigurationManager.kt55
-rw-r--r--photopicker/src/com/android/photopicker/core/configuration/PhotopickerConfiguration.kt6
-rw-r--r--photopicker/src/com/android/photopicker/core/embedded/EmbeddedService.kt59
-rw-r--r--photopicker/src/com/android/photopicker/core/embedded/EmbeddedState.kt15
-rw-r--r--photopicker/src/com/android/photopicker/core/embedded/EmbeddedStateManager.kt38
-rw-r--r--photopicker/src/com/android/photopicker/core/embedded/EmbeddedViewModelFactory.kt36
-rw-r--r--photopicker/src/com/android/photopicker/core/embedded/Session.kt134
-rw-r--r--photopicker/src/com/android/photopicker/core/events/Event.kt141
-rw-r--r--photopicker/src/com/android/photopicker/core/events/PhotopickerEventLogger.kt99
-rw-r--r--photopicker/src/com/android/photopicker/core/events/SessionId.kt35
-rw-r--r--photopicker/src/com/android/photopicker/core/features/FeatureManager.kt2
-rw-r--r--photopicker/src/com/android/photopicker/core/selection/GrantsAwareSelectionImpl.kt57
-rw-r--r--photopicker/src/com/android/photopicker/core/selection/GrantsAwareSet.kt33
-rw-r--r--photopicker/src/com/android/photopicker/core/theme/AccentColorHelper.kt5
-rw-r--r--photopicker/src/com/android/photopicker/core/theme/AccentColorScheme.kt11
-rw-r--r--photopicker/src/com/android/photopicker/core/theme/PhotopickerTheme.kt24
-rw-r--r--photopicker/src/com/android/photopicker/data/DataService.kt6
-rw-r--r--photopicker/src/com/android/photopicker/data/DataServiceImpl.kt74
-rw-r--r--photopicker/src/com/android/photopicker/data/MediaProviderClient.kt119
-rw-r--r--photopicker/src/com/android/photopicker/data/UriHelper.kt16
-rw-r--r--photopicker/src/com/android/photopicker/data/model/Media.kt33
-rw-r--r--photopicker/src/com/android/photopicker/data/model/Selectable.kt30
-rw-r--r--photopicker/src/com/android/photopicker/data/paging/AlbumMediaPagingSource.kt59
-rw-r--r--photopicker/src/com/android/photopicker/data/paging/AlbumPagingSource.kt52
-rw-r--r--photopicker/src/com/android/photopicker/data/paging/MediaPagingSource.kt70
-rw-r--r--photopicker/src/com/android/photopicker/extensions/Flow.kt22
-rw-r--r--photopicker/src/com/android/photopicker/extensions/Intent.kt2
-rw-r--r--photopicker/src/com/android/photopicker/extensions/Modifier.kt141
-rw-r--r--photopicker/src/com/android/photopicker/features/albumgrid/AlbumGrid.kt79
-rw-r--r--photopicker/src/com/android/photopicker/features/albumgrid/AlbumGridFeature.kt7
-rw-r--r--photopicker/src/com/android/photopicker/features/albumgrid/AlbumGridViewModel.kt12
-rw-r--r--photopicker/src/com/android/photopicker/features/albumgrid/AlbumMediaGrid.kt54
-rw-r--r--photopicker/src/com/android/photopicker/features/cloudmedia/CloudMediaFeature.kt27
-rw-r--r--photopicker/src/com/android/photopicker/features/cloudmedia/MediaPreloader.kt26
-rw-r--r--photopicker/src/com/android/photopicker/features/cloudmedia/MediaPreloaderViewModel.kt40
-rw-r--r--photopicker/src/com/android/photopicker/features/navigationbar/NavigationBar.kt17
-rw-r--r--photopicker/src/com/android/photopicker/features/overflowmenu/OverflowMenu.kt29
-rw-r--r--photopicker/src/com/android/photopicker/features/overflowmenu/OverflowMenuFeature.kt8
-rw-r--r--photopicker/src/com/android/photopicker/features/photogrid/PhotoGrid.kt175
-rw-r--r--photopicker/src/com/android/photopicker/features/photogrid/PhotoGridFeature.kt3
-rw-r--r--photopicker/src/com/android/photopicker/features/photogrid/PhotoGridViewModel.kt34
-rw-r--r--photopicker/src/com/android/photopicker/features/preview/Preview.kt125
-rw-r--r--photopicker/src/com/android/photopicker/features/preview/PreviewFeature.kt11
-rw-r--r--photopicker/src/com/android/photopicker/features/preview/PreviewViewModel.kt32
-rw-r--r--photopicker/src/com/android/photopicker/features/preview/video/VideoUi.kt66
-rw-r--r--photopicker/src/com/android/photopicker/features/profileselector/ProfileSelector.kt6
-rw-r--r--photopicker/src/com/android/photopicker/features/profileselector/ProfileSelectorFeature.kt4
-rw-r--r--photopicker/src/com/android/photopicker/features/profileselector/ProfileSelectorViewModel.kt25
-rw-r--r--photopicker/src/com/android/photopicker/features/selectionbar/SelectionBar.kt18
-rw-r--r--photopicker/src/com/android/photopicker/features/selectionbar/SelectionBarFeature.kt14
-rw-r--r--photopicker/src/com/android/photopicker/features/snackbar/Snackbar.kt6
-rw-r--r--photopicker/src/com/android/photopicker/inject/ActivityModule.kt6
-rw-r--r--photopicker/src/com/android/photopicker/inject/EmbeddedServiceModule.kt11
-rw-r--r--photopicker/tests/Android.bp1
-rw-r--r--photopicker/tests/AndroidTest.xml2
-rw-r--r--photopicker/tests/src/com/android/photopicker/PhotopickerFeatureBaseTest.kt9
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/PhotopickerAppTest.kt172
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/banners/BannerManagerImplTest.kt13
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/banners/BannerTest.kt189
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/components/mediagrid/MediaGridTest.kt422
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/configuration/ConfigurationManagerTest.kt226
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/configuration/TestPhotopickerConfiguration.kt31
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedFeaturesTest.kt509
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedPhotopickerFeatureBaseTest.kt4
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedServiceTest.kt2
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedStateManagerTest.kt87
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/embedded/SessionTest.kt407
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/events/EventsTest.kt13
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/features/FeatureManagerTest.kt5
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/glide/GlideTestRule.kt51
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/glide/LoadMediaTest.kt8
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/navigation/PhotopickerNavGraphTest.kt11
-rw-r--r--photopicker/tests/src/com/android/photopicker/core/selection/GrantsAwareSelectionTest.kt306
-rw-r--r--photopicker/tests/src/com/android/photopicker/data/DataServiceImplTest.kt187
-rw-r--r--photopicker/tests/src/com/android/photopicker/data/TestDataServiceImpl.kt16
-rw-r--r--photopicker/tests/src/com/android/photopicker/data/TestMediaProvider.kt12
-rw-r--r--photopicker/tests/src/com/android/photopicker/data/paging/AlbumMediaPagingSourceTest.kt31
-rw-r--r--photopicker/tests/src/com/android/photopicker/data/paging/AlbumPagingSourceTest.kt37
-rw-r--r--photopicker/tests/src/com/android/photopicker/data/paging/MediaPagingSourceTest.kt37
-rw-r--r--photopicker/tests/src/com/android/photopicker/data/paging/MediaProviderClientTest.kt94
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/albumgrid/AlbumGridFeatureTest.kt89
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/albumgrid/AlbumGridViewModelTest.kt39
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/browse/BrowseFeatureTest.kt6
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/cloudmedia/CloudMediaFeatureTest.kt10
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/cloudmedia/MediaPreloaderTest.kt18
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/navigationbar/NavigationBarFeatureTest.kt6
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/overflowmenu/OverflowMenuFeatureTest.kt6
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/photogrid/PhotoGridFeatureTest.kt58
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/photogrid/PhotoGridViewModelTest.kt248
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/preview/PreviewFeatureTest.kt20
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/preview/PreviewViewModelTest.kt202
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/privacyexplainer/PrivacyExplainerFeatureTest.kt4
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/profileselector/ProfileSelectorFeatureTest.kt10
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/profileselector/ProfileSelectorViewModelTest.kt52
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/selectionbar/SelectionBarFeatureTest.kt61
-rw-r--r--photopicker/tests/src/com/android/photopicker/features/snackbar/SnackbarFeatureTest.kt2
-rw-r--r--photopicker/tests/src/com/android/photopicker/inject/EmbeddedTestModule.kt8
-rw-r--r--photopicker/tests/src/com/android/photopicker/inject/PhotopickerTestModule.kt5
-rw-r--r--res/values/strings.xml4
-rw-r--r--src/com/android/providers/media/MediaGrants.java14
-rw-r--r--src/com/android/providers/media/MediaProvider.java57
-rw-r--r--src/com/android/providers/media/MediaService.java14
-rw-r--r--src/com/android/providers/media/photopicker/PickerDataLayer.java4
-rw-r--r--src/com/android/providers/media/photopicker/PickerSyncController.java135
-rw-r--r--src/com/android/providers/media/photopicker/data/PickerDatabaseHelper.java50
-rw-r--r--src/com/android/providers/media/photopicker/data/PickerDbFacade.java128
-rw-r--r--src/com/android/providers/media/photopicker/data/PickerSyncRequestExtras.java54
-rw-r--r--src/com/android/providers/media/photopicker/sync/ImmediateGrantsSyncWorker.java113
-rw-r--r--src/com/android/providers/media/photopicker/sync/PickerSyncManager.java71
-rw-r--r--src/com/android/providers/media/photopicker/sync/SyncTrackerRegistry.java21
-rw-r--r--src/com/android/providers/media/photopicker/sync/WorkManagerInitializer.java2
-rw-r--r--src/com/android/providers/media/photopicker/util/exceptions/WorkCancelledException.java26
-rw-r--r--src/com/android/providers/media/photopicker/v2/PickerDataLayerV2.java271
-rw-r--r--src/com/android/providers/media/photopicker/v2/PickerSQLConstants.java29
-rw-r--r--src/com/android/providers/media/photopicker/v2/PickerUriResolverV2.java17
-rw-r--r--src/com/android/providers/media/photopicker/v2/model/MediaQuery.java77
-rw-r--r--src/com/android/providers/media/photopicker/v2/model/PreviewMediaQuery.java222
-rw-r--r--src/com/android/providers/media/scan/ModernMediaScanner.java2
-rw-r--r--src/com/android/providers/media/util/IsoInterface.java113
-rw-r--r--src/com/android/providers/media/util/XmpDataParser.java6
-rw-r--r--tests/AndroidTest.xml7
-rw-r--r--tests/src/com/android/providers/media/MediaProviderTest.java26
-rw-r--r--tests/src/com/android/providers/media/TestConfigStore.java10
-rw-r--r--tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java76
-rw-r--r--tests/src/com/android/providers/media/photopicker/data/PickerDatabaseHelperTest.java34
-rw-r--r--tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java58
-rw-r--r--tests/src/com/android/providers/media/photopicker/sync/ImmediateGrantsSyncWorkerTest.java168
-rw-r--r--tests/src/com/android/providers/media/photopicker/sync/PickerSyncManagerTest.java64
-rw-r--r--tests/src/com/android/providers/media/photopicker/sync/SyncTrackerTests.java2
-rw-r--r--tests/src/com/android/providers/media/photopicker/sync/SyncWorkerTestUtils.java17
-rw-r--r--tests/src/com/android/providers/media/photopicker/util/PickerDbTestUtils.java65
-rw-r--r--tests/src/com/android/providers/media/photopicker/v2/PickerDataLayerV2Test.java328
-rw-r--r--tests/src/com/android/providers/media/util/IsoInterfaceTest.java14
-rw-r--r--tools/photopickerV2/Android.bp5
-rw-r--r--tools/photopickerV2/AndroidManifest.xml5
-rw-r--r--tools/photopickerV2/res/values-af/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-am/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-ar/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-as/strings.xml10
-rw-r--r--tools/photopickerV2/res/values-az/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-b+sr+Latn/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-be/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-bg/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-bn/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-bs/strings.xml19
-rw-r--r--tools/photopickerV2/res/values-ca/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-cs/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-da/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-de/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-el/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-en-rAU/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-en-rCA/strings.xml9
-rw-r--r--tools/photopickerV2/res/values-en-rGB/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-en-rIN/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-en-rXC/strings.xml9
-rw-r--r--tools/photopickerV2/res/values-es-rUS/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-es/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-et/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-eu/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-fa/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-fi/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-fr-rCA/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-fr/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-gl/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-gu/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-hi/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-hr/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-hu/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-hy/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-in/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-is/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-it/strings.xml10
-rw-r--r--tools/photopickerV2/res/values-iw/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-ja/strings.xml10
-rw-r--r--tools/photopickerV2/res/values-ka/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-kk/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-km/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-kn/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-ko/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-ky/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-lo/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-lt/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-lv/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-mk/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-ml/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-mn/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-mr/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-ms/strings.xml10
-rw-r--r--tools/photopickerV2/res/values-my/strings.xml10
-rw-r--r--tools/photopickerV2/res/values-nb/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-ne/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-nl/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-or/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-pa/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-pl/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-pt-rBR/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-pt-rPT/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-pt/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-ro/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-ru/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-si/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-sk/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-sl/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-sq/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-sr/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-sv/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-sw/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-ta/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-te/strings.xml10
-rw-r--r--tools/photopickerV2/res/values-th/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-tl/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-tr/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-uk/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-ur/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-uz/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-vi/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-zh-rCN/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-zh-rHK/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-zh-rTW/strings.xml17
-rw-r--r--tools/photopickerV2/res/values-zu/strings.xml17
-rw-r--r--tools/photopickerV2/res/values/strings.xml16
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIScreen.kt128
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIViewModel.kt22
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/navigation/NavGraph.kt3
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerScreen.kt91
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerViewModel.kt28
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/pickerchoice/PickerChoiceScreen.kt253
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/pickerchoice/PickerChoiceViewModel.kt157
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/UIComponents.kt97
-rw-r--r--tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/Utils.kt6
497 files changed, 11600 insertions, 1618 deletions
diff --git a/apex/framework/java/android/provider/EmbeddedPhotopickerFeatureInfo.java b/apex/framework/java/android/provider/EmbeddedPhotopickerFeatureInfo.java
index 44d71d422..42dcb98fa 100644
--- a/apex/framework/java/android/provider/EmbeddedPhotopickerFeatureInfo.java
+++ b/apex/framework/java/android/provider/EmbeddedPhotopickerFeatureInfo.java
@@ -56,7 +56,7 @@ public final class EmbeddedPhotopickerFeatureInfo implements Parcelable {
private final List<Uri> mPreSelectedUris;
@NonNull
- private static final List<String> DEFAULT_MIME_TYPES = Arrays.asList("*/*");
+ private static final List<String> DEFAULT_MIME_TYPES = Arrays.asList("image/*", "video/*");
@ColorLong
private static final long DEFAULT_ACCENT_COLOR = -1;
private static final boolean DEFAULT_ORDERED_SELECTION = false;
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index c0f1d84b3..77c2c9473 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -4655,6 +4655,11 @@ public final class MediaStore {
final Bundle in = new Bundle();
in.putString(Intent.EXTRA_TEXT, volumeName);
final Bundle out = resolver.call(AUTHORITY, GET_GENERATION_CALL, null, in);
+ if (out == null) {
+ throw new IllegalStateException("Failed to get generation for volume '"
+ + volumeName + "'. The ContentResolver call returned null.");
+ }
+
return out.getLong(Intent.EXTRA_INDEX);
}
diff --git a/mediaprovider_flags.aconfig b/mediaprovider_flags.aconfig
index dc1ff99e2..826a67b95 100644
--- a/mediaprovider_flags.aconfig
+++ b/mediaprovider_flags.aconfig
@@ -63,6 +63,7 @@ flag {
namespace: "mediaprovider"
description: "This flag will enable the abstract service for media cognition processes"
bug: "331771553"
+ is_fixed_read_only: true
}
flag {
diff --git a/pdf/OWNERS b/pdf/OWNERS
index 925f5fb9b..40f36b7fd 100644
--- a/pdf/OWNERS
+++ b/pdf/OWNERS
@@ -3,3 +3,4 @@
nishantpanwar@google.com
bhavyajain@google.com
anothermark@google.com
+gulshansingh@google.com \ No newline at end of file
diff --git a/photopicker/Android.bp b/photopicker/Android.bp
index 39d364384..dc0b64b2c 100644
--- a/photopicker/Android.bp
+++ b/photopicker/Android.bp
@@ -28,6 +28,7 @@ android_library {
],
static_libs: [
"androidx.activity_activity-compose",
+ "androidx.appcompat_appcompat",
"androidx.compose.foundation_foundation",
"androidx.compose.material3_material3",
"androidx.compose.material3_material3-window-size-class",
diff --git a/photopicker/res/drawable/photopicker_selected_media.xml b/photopicker/res/drawable/photopicker_selected_media.xml
new file mode 100644
index 000000000..49250c4d2
--- /dev/null
+++ b/photopicker/res/drawable/photopicker_selected_media.xml
@@ -0,0 +1,19 @@
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- Circle Check -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960">
+<path android:fillColor="@android:color/white" android:pathData="M424,664L706,382L650,326L424,552L310,438L254,494L424,664ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880Z"/>
+</vector>
diff --git a/photopicker/res/values-af/core_strings.xml b/photopicker/res/values-af/core_strings.xml
index 36119189f..ec127b90a 100644
--- a/photopicker/res/values-af/core_strings.xml
+++ b/photopicker/res/values-af/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Foto’s en video’s"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Gekies"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Voeg <xliff:g id="COUNT">(%1$s)</xliff:g> by"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Klaar"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Ontkies almal"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Kies tot <xliff:g id="COUNT">%1$s</xliff:g> items"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Foto’s"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albums"</string>
diff --git a/photopicker/res/values-af/feature_cloud_strings.xml b/photopicker/res/values-af/feature_cloud_strings.xml
index 586dce823..c02152508 100644
--- a/photopicker/res/values-af/feature_cloud_strings.xml
+++ b/photopicker/res/values-af/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> van <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> is gereed"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Sommige foto’s kan nie laai nie"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Probeer later weer. Jou foto’s sal beskikbaar wees sodra die kwessie opgelos is."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-af/feature_preview_strings.xml b/photopicker/res/values-af/feature_preview_strings.xml
index 30dd03e24..2b16bf0b4 100644
--- a/photopicker/res/values-af/feature_preview_strings.xml
+++ b/photopicker/res/values-af/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Kies"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Ontkies"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Kies alles <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Ontkies alles <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Voorbeskou"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Sukkel om video te speel"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Gaan jou internetverbinding na en probeer weer"</string>
diff --git a/photopicker/res/values-am/core_strings.xml b/photopicker/res/values-am/core_strings.xml
index fdcbb72cc..69a5b6492 100644
--- a/photopicker/res/values-am/core_strings.xml
+++ b/photopicker/res/values-am/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ፎቶዎች እና ቪድዮዎች"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"ሚዲያ"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"ተመርጧል"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> ያክሉ"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"ተከናውኗል"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"ሁሉንም አትምረጥ"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"እስከ <xliff:g id="COUNT">%1$s</xliff:g> ንጥሎች ድረስ ይምረጡ"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"ፎቶዎች"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"አልበሞች"</string>
diff --git a/photopicker/res/values-am/feature_cloud_strings.xml b/photopicker/res/values-am/feature_cloud_strings.xml
index ac66dd491..e792fc5e0 100644
--- a/photopicker/res/values-am/feature_cloud_strings.xml
+++ b/photopicker/res/values-am/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> ከ<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> ዝግጁ"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"አንዳንድ ፎቶዎችን መጫን አይቻለም"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"ቆይተው እንደገና ይሞክሩ። የእርስዎ ፎቶዎች አንዴ ችግሩ ከተፈታ በኋላ ይገኛሉ።"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-am/feature_preview_strings.xml b/photopicker/res/values-am/feature_preview_strings.xml
index 10d0a458a..e7fbc09b3 100644
--- a/photopicker/res/values-am/feature_preview_strings.xml
+++ b/photopicker/res/values-am/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"ምረጥ"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"አትምረጥ"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"ሁሉንም <xliff:g id="COUNT">(%1$s)</xliff:g> ምረጥ"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"ሁሉንም <xliff:g id="COUNT">(%1$s)</xliff:g> አትምረጥ"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"ቅድመ-ዕይታ"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"ቪድዮን ማጫወት ላይ ችግር"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"የበይነመረብ ግንኙነትዎን ይፈትሹት እና እንደገና ይሞክሩ"</string>
diff --git a/photopicker/res/values-ar/core_strings.xml b/photopicker/res/values-ar/core_strings.xml
index c97f24d73..6547a039b 100644
--- a/photopicker/res/values-ar/core_strings.xml
+++ b/photopicker/res/values-ar/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"الصور والفيديوهات"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"الوسائط"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"تم وضع علامة"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"إضافة <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"تم"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"إلغاء اختيار الكل"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"اختَر ما يصل إلى <xliff:g id="COUNT">%1$s</xliff:g> عنصر"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"الصور"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"الألبومات"</string>
diff --git a/photopicker/res/values-ar/feature_cloud_strings.xml b/photopicker/res/values-ar/feature_cloud_strings.xml
index 9fda12365..f590aaf84 100644
--- a/photopicker/res/values-ar/feature_cloud_strings.xml
+++ b/photopicker/res/values-ar/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"‫<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> صورة جاهزة من إجمالي <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"يتعذّر تحميل بعض الصور"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"يُرجى إعادة المحاولة لاحقًا. ستتوفّر صورك عند حل المشكلة."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-ar/feature_preview_strings.xml b/photopicker/res/values-ar/feature_preview_strings.xml
index 0affde1d6..a5aa40540 100644
--- a/photopicker/res/values-ar/feature_preview_strings.xml
+++ b/photopicker/res/values-ar/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"اختيار"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"إلغاء الاختيار"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"اختيار الكل: <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"إلغاء اختيار الكل: <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"معاينة"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"مشكلة في تشغيل الفيديو"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"يُرجى التحقّق من الاتصال بالإنترنت، ثم إعادة المحاولة"</string>
diff --git a/photopicker/res/values-as/core_strings.xml b/photopicker/res/values-as/core_strings.xml
index 6c155d781..e3837e187 100644
--- a/photopicker/res/values-as/core_strings.xml
+++ b/photopicker/res/values-as/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ফট’ আৰু ভিডিঅ’"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"মিডিয়া"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"বাছনি কৰা হৈছে"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> খন যোগ দিয়ক"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"হ’ল"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"বাছনিৰ পৰা আটাইবোৰ আঁতৰাওক"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> টা পৰ্যন্ত বস্তু বাছনি কৰক"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"ফট’"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"এলবাম"</string>
diff --git a/photopicker/res/values-as/feature_cloud_strings.xml b/photopicker/res/values-as/feature_cloud_strings.xml
index 2f7a9d471..3f4aadf8a 100644
--- a/photopicker/res/values-as/feature_cloud_strings.xml
+++ b/photopicker/res/values-as/feature_cloud_strings.xml
@@ -21,4 +21,12 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> টা বস্তুৰ ভিতৰত <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> টা সাজু"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"কিছুমান ফট’ ল’ড কৰিব নোৱাৰি"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"পাছত পুনৰ চেষ্টা কৰক। সমস্যাটো সমাধান হোৱাৰ পাছত আপোনাৰ ফট’সমূহ উপলব্ধ হ’ব।"</string>
+ <string name="photopicker_banner_cloud_media_available_title" msgid="3630564281302679941">"এতিয়া বেকআপ লোৱা ফট’ অন্তৰ্ভুক্ত কৰা হৈছে"</string>
+ <string name="photopicker_banner_cloud_media_available_message" msgid="6224134038092008505">"আপুনি <xliff:g id="APP_NAME">%1$s</xliff:g>ৰ একাউণ্টৰ <xliff:g id="USER_ACCOUNT">%2$s</xliff:g>ৰ পৰা ফট’ বাছনি কৰিব পাৰে"</string>
+ <string name="photopicker_banner_cloud_choose_account_title" msgid="286679907089224434">"<xliff:g id="APP_NAME">%1$s</xliff:g> একাউণ্ট বাছনি কৰক"</string>
+ <string name="photopicker_banner_cloud_choose_account_message" msgid="5173210485511611652">"ইয়াত <xliff:g id="APP_NAME">%1$s</xliff:g>ৰ পৰা ফট’ অন্তৰ্ভুক্ত কৰিবলৈ, এপ্‌টোত এটা একাউণ্ট বাছনি কৰক"</string>
+ <string name="photopicker_banner_cloud_choose_account_button" msgid="3407910358624445230">"একাউণ্ট বাছনি কৰক"</string>
+ <string name="photopicker_banner_cloud_choose_provider_title" msgid="992613053341538886">"ক্লাউড মিডিয়া এপ্ বাছনি কৰক"</string>
+ <string name="photopicker_banner_cloud_choose_provider_message" msgid="1102889303996108506">"ইয়াত বেকআপ লোৱা ফট’ অন্তৰ্ভুক্ত কৰিবলৈ, ছেটিঙত এটা ক্লাউড মিডিয়া এপ্ বাছনি কৰক"</string>
+ <string name="photopicker_banner_cloud_choose_app_button" msgid="8228365266860220123">"এপ্‌ বাছনি কৰক"</string>
</resources>
diff --git a/photopicker/res/values-as/feature_preview_strings.xml b/photopicker/res/values-as/feature_preview_strings.xml
index 989a9fc42..8fce9679b 100644
--- a/photopicker/res/values-as/feature_preview_strings.xml
+++ b/photopicker/res/values-as/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"বাছনি কৰক"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"বাছনিৰ পৰা আঁতৰাওক"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"<xliff:g id="COUNT">(%1$s)</xliff:g> টাৰ আটাইবোৰ বাছনি কৰক"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"<xliff:g id="COUNT">(%1$s)</xliff:g> টাৰ আটাইবোৰ বাছনিৰ পৰা আঁতৰাওক"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"পূৰ্বদৰ্শন কৰক"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"ভিডিঅ’ প্লে’ কৰাত সমস্যা হৈছে"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"আপোনাৰ ইণ্টাৰনেট সংযোগ পৰীক্ষা কৰক আৰু পুনৰ চেষ্টা কৰক"</string>
diff --git a/photopicker/res/values-az/core_strings.xml b/photopicker/res/values-az/core_strings.xml
index 1977a9cba..54566b5dd 100644
--- a/photopicker/res/values-az/core_strings.xml
+++ b/photopicker/res/values-az/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Foto və videolar"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Seçilib"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Əlavə edin: <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Hazırdır"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Bütün seçimləri silin"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Maksimum <xliff:g id="COUNT">%1$s</xliff:g> element seçin"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Foto"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albomlar"</string>
diff --git a/photopicker/res/values-az/feature_cloud_strings.xml b/photopicker/res/values-az/feature_cloud_strings.xml
index 4ed5a8551..d3b3909d9 100644
--- a/photopicker/res/values-az/feature_cloud_strings.xml
+++ b/photopicker/res/values-az/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>/<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> hazırdır"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Bəzi fotolar yüklənmir"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Sonra cəhd edin. Problem həll edildikdən sonra fotolar əlçatan olacaq."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-az/feature_preview_strings.xml b/photopicker/res/values-az/feature_preview_strings.xml
index cf2bfca01..68d00eec6 100644
--- a/photopicker/res/values-az/feature_preview_strings.xml
+++ b/photopicker/res/values-az/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Seçin"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Seçimi ləğv edin"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Hamısını seçin <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Hamısının seçimini silin <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Önizləmə"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Videonu işə salarkən xəta oldu"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"İnternet bağlantısını yoxlayın və yenidən cəhd edin"</string>
diff --git a/photopicker/res/values-b+sr+Latn/core_strings.xml b/photopicker/res/values-b+sr+Latn/core_strings.xml
index 4f6eb3931..940db81f6 100644
--- a/photopicker/res/values-b+sr+Latn/core_strings.xml
+++ b/photopicker/res/values-b+sr+Latn/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Slike i videi"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Mediji"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Izabrano"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Dodaj <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Gotovo"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Poništite sve"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Najveći broj stavki koje možete da izaberete je <xliff:g id="COUNT">%1$s</xliff:g>"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Slike"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumi"</string>
diff --git a/photopicker/res/values-b+sr+Latn/feature_cloud_strings.xml b/photopicker/res/values-b+sr+Latn/feature_cloud_strings.xml
index 92e3afe64..938525e94 100644
--- a/photopicker/res/values-b+sr+Latn/feature_cloud_strings.xml
+++ b/photopicker/res/values-b+sr+Latn/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Spremno:<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> od <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Učitavanje nekih slika nije uspelo"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Probajte ponovo kasnije. Slike će biti dostupne kada se problem reši."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-b+sr+Latn/feature_preview_strings.xml b/photopicker/res/values-b+sr+Latn/feature_preview_strings.xml
index ba4b0f4a5..48d0bdca7 100644
--- a/photopicker/res/values-b+sr+Latn/feature_preview_strings.xml
+++ b/photopicker/res/values-b+sr+Latn/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Izaberi"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Opozovi izbor"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Izaberi sve <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Poništi izbor <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Pregled"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Došlo je do greške pri puštanju videa"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Proverite internet vezu i probajte ponovo"</string>
diff --git a/photopicker/res/values-be/core_strings.xml b/photopicker/res/values-be/core_strings.xml
index 8e4d6e4a6..b6fdb60e6 100644
--- a/photopicker/res/values-be/core_strings.xml
+++ b/photopicker/res/values-be/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Фота і відэа"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Медыяфайл"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Выбрана"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Дадаць <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Гатова"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Скасаваць выбар"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Выберыце элементы (не больш <xliff:g id="COUNT">%1$s</xliff:g>)"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Фота"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Альбомы"</string>
diff --git a/photopicker/res/values-be/feature_cloud_strings.xml b/photopicker/res/values-be/feature_cloud_strings.xml
index 66dca165b..9d5add300 100644
--- a/photopicker/res/values-be/feature_cloud_strings.xml
+++ b/photopicker/res/values-be/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Гатова: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> з <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Некаторыя фота не ўдалося загрузіць"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Паўтарыце спробу пазней. Калі праблема будзе вырашана, вашы фота стануць даступнымі."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-be/feature_preview_strings.xml b/photopicker/res/values-be/feature_preview_strings.xml
index 94b9be341..95b6633db 100644
--- a/photopicker/res/values-be/feature_preview_strings.xml
+++ b/photopicker/res/values-be/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Выбраць"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Скасаваць выбар"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Выбраць усе (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Скасаваць выбар для ўсіх (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Перадпрагляд"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Праблемы з прайграваннем відэа"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Праверце падключэнне да інтэрнэту і паўтарыце спробу"</string>
diff --git a/photopicker/res/values-bg/core_strings.xml b/photopicker/res/values-bg/core_strings.xml
index bacf2a503..6fd9f4514 100644
--- a/photopicker/res/values-bg/core_strings.xml
+++ b/photopicker/res/values-bg/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Снимки и видеоклипове"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Мултимедия"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Избрано"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Добавяне на <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Готово"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Премахване на избора от всички"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Изберете най-много <xliff:g id="COUNT">%1$s</xliff:g> елемента"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Снимки"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Албуми"</string>
diff --git a/photopicker/res/values-bg/feature_cloud_strings.xml b/photopicker/res/values-bg/feature_cloud_strings.xml
index 28dcdc365..38a2f4be0 100644
--- a/photopicker/res/values-bg/feature_cloud_strings.xml
+++ b/photopicker/res/values-bg/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Готови: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> от <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Някои снимки не могат да се заредят"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Опитайте отново по-късно. Снимките ви ще бъдат налице, след като проблемът бъде разрешен."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-bg/feature_preview_strings.xml b/photopicker/res/values-bg/feature_preview_strings.xml
index 98b31ca14..6641fc59c 100644
--- a/photopicker/res/values-bg/feature_preview_strings.xml
+++ b/photopicker/res/values-bg/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Избор"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Премахване на избора"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Избиране на всички <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Отмяна на избора от всички <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Визуализация"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Проблем при възпроизвеждането на видеоклипа"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Проверете връзката си с интернет и опитайте отново"</string>
diff --git a/photopicker/res/values-bn/core_strings.xml b/photopicker/res/values-bn/core_strings.xml
index 8597ed9cc..4b2e6440d 100644
--- a/photopicker/res/values-bn/core_strings.xml
+++ b/photopicker/res/values-bn/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ফটো &amp; ভিডিও"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"মিডিয়া"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"বেছে নেওয়া হয়েছে"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g>টি যোগ করুন"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"হয়ে গেছে"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"সবকটি বাদ দিন"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"সর্বাধিক <xliff:g id="COUNT">%1$s</xliff:g>টি আইটেম বেছে নিন"</string>
diff --git a/photopicker/res/values-bn/feature_cloud_strings.xml b/photopicker/res/values-bn/feature_cloud_strings.xml
index 61f7cc85f..dae9d195f 100644
--- a/photopicker/res/values-bn/feature_cloud_strings.xml
+++ b/photopicker/res/values-bn/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>টির মধ্যে <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> নম্বর রেডি"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"কিছু ফটো লোড করা যাচ্ছে না"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"পরে আবার চেষ্টা করুন। সমস্যার সমাধান হয়ে গেলে আপনার ফটো উপলভ্য হবে।"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-bn/feature_preview_strings.xml b/photopicker/res/values-bn/feature_preview_strings.xml
index 6de7878dd..b137315b4 100644
--- a/photopicker/res/values-bn/feature_preview_strings.xml
+++ b/photopicker/res/values-bn/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"বেছে নিন"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"বাদ দিন"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"<xliff:g id="COUNT">(%1$s)</xliff:g>টির সবকটি বেছে নিন"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"<xliff:g id="COUNT">(%1$s)</xliff:g>টির সবকটি বাদ দিন"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"প্রিভিউ দেখুন"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"ভিডিও চালাতে সমস্যা হচ্ছে"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"আপনার ইন্টারনেট কানেকশন ঠিক আছে কিনা দেখে নিয়ে আবার চেষ্টা করুন"</string>
diff --git a/photopicker/res/values-bs/core_strings.xml b/photopicker/res/values-bs/core_strings.xml
index 04e4e0ba3..52943fad3 100644
--- a/photopicker/res/values-bs/core_strings.xml
+++ b/photopicker/res/values-bs/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotografije i videozapisi"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Mediji"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Odabrano"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Dodaj <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Gotovo"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Poništavanje svih odabira"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> je maksimalni broj stavki koje možete odabrati"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotografije"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumi"</string>
diff --git a/photopicker/res/values-bs/feature_cloud_strings.xml b/photopicker/res/values-bs/feature_cloud_strings.xml
index 32eed10b0..d06806e36 100644
--- a/photopicker/res/values-bs/feature_cloud_strings.xml
+++ b/photopicker/res/values-bs/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Spremno: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> od <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Nije moguće učitati određene fotografije"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Pokušajte ponovo kasnije. Fotografije će biti dostupne čim se problem riješi."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-bs/feature_preview_strings.xml b/photopicker/res/values-bs/feature_preview_strings.xml
index e02dec7d9..ac760afc7 100644
--- a/photopicker/res/values-bs/feature_preview_strings.xml
+++ b/photopicker/res/values-bs/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Odaberi"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Poništi odabir"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Odaberi sve (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Poništi odabir svega (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Pregled"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Poteškoće prilikom reprodukcije videozapisa"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Provjerite internetsku vezu i pokušajte ponovo"</string>
diff --git a/photopicker/res/values-ca/core_strings.xml b/photopicker/res/values-ca/core_strings.xml
index 299382c7a..8168b13d5 100644
--- a/photopicker/res/values-ca/core_strings.xml
+++ b/photopicker/res/values-ca/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotos i vídeos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Contingut multimèdia"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Seleccionat"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Afegeix <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Fet"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Desselecciona-ho tot"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Selecciona fins a <xliff:g id="COUNT">%1$s</xliff:g> elements"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Àlbums"</string>
diff --git a/photopicker/res/values-ca/feature_cloud_strings.xml b/photopicker/res/values-ca/feature_cloud_strings.xml
index 477bce8b3..977260d29 100644
--- a/photopicker/res/values-ca/feature_cloud_strings.xml
+++ b/photopicker/res/values-ca/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> de <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> a punt"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"No es poden carregar algunes fotos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Torna-ho a provar més tard. Les teves fotos estaran disponibles un cop el problema s\'hagi resolt."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-ca/feature_preview_strings.xml b/photopicker/res/values-ca/feature_preview_strings.xml
index 522083783..397f71cd7 100644
--- a/photopicker/res/values-ca/feature_preview_strings.xml
+++ b/photopicker/res/values-ca/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Selecciona"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Desselecciona"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Selecciona-ho tot (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Desselecciona-ho tot (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Previsualitza"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Hi ha hagut un problema en reproduir el vídeo"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Comprova la connexió a Internet i torna-ho a provar"</string>
diff --git a/photopicker/res/values-cs/core_strings.xml b/photopicker/res/values-cs/core_strings.xml
index faff0ebfd..ca946d429 100644
--- a/photopicker/res/values-cs/core_strings.xml
+++ b/photopicker/res/values-cs/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotky a videa"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Média"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Vybráno"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Přidat <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Hotovo"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Zrušit výběr všech"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Vyberte maximálně <xliff:g id="COUNT">%1$s</xliff:g> položek"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotky"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Alba"</string>
diff --git a/photopicker/res/values-cs/feature_cloud_strings.xml b/photopicker/res/values-cs/feature_cloud_strings.xml
index ebc59b21c..da6316fec 100644
--- a/photopicker/res/values-cs/feature_cloud_strings.xml
+++ b/photopicker/res/values-cs/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Připraveno: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> z <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Některé fotografie nelze načíst"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Zkuste to později. Fotky budou k dispozici po vyřešení tohoto problému."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-cs/feature_preview_strings.xml b/photopicker/res/values-cs/feature_preview_strings.xml
index c027efb3a..f238c3e3c 100644
--- a/photopicker/res/values-cs/feature_preview_strings.xml
+++ b/photopicker/res/values-cs/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Vybrat"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Zrušit výběr"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Vybrat vše (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Zrušit výběr všech (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Zobrazit náhled"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Při přehrávání videa došlo k potížím"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Zkontrolujte připojení k internetu a zkuste to znovu"</string>
diff --git a/photopicker/res/values-da/core_strings.xml b/photopicker/res/values-da/core_strings.xml
index 794a32e1e..10e2ba85f 100644
--- a/photopicker/res/values-da/core_strings.xml
+++ b/photopicker/res/values-da/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Billeder og videoer"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Medier"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Valgt"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Tilføj <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Udfør"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Fravælg alle"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Vælg højst <xliff:g id="COUNT">%1$s</xliff:g> elementer"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Album"</string>
diff --git a/photopicker/res/values-da/feature_cloud_strings.xml b/photopicker/res/values-da/feature_cloud_strings.xml
index 2d9acb1e2..eb21a6bef 100644
--- a/photopicker/res/values-da/feature_cloud_strings.xml
+++ b/photopicker/res/values-da/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> af <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> er klar"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Nogle billeder kan ikke indlæses"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Prøv igen senere. Dine billeder bliver tilgængelige, så snart problemet er løst."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-da/feature_preview_strings.xml b/photopicker/res/values-da/feature_preview_strings.xml
index a51d7202b..6188f0f3e 100644
--- a/photopicker/res/values-da/feature_preview_strings.xml
+++ b/photopicker/res/values-da/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Vælg"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Fravælg"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Markér alle <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Fjern markeringen af alle <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Forhåndsvisning"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Problemer med at afspille video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Tjek din internetforbindelse, og prøv igen"</string>
diff --git a/photopicker/res/values-de/core_strings.xml b/photopicker/res/values-de/core_strings.xml
index 2f5d5e35f..847b31698 100644
--- a/photopicker/res/values-de/core_strings.xml
+++ b/photopicker/res/values-de/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotos &amp; Videos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Medium"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Ausgewählt"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> hinzufügen"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Fertig"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Auswahl für alle aufheben"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Maximal <xliff:g id="COUNT">%1$s</xliff:g> Elemente auswählen"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Alben"</string>
diff --git a/photopicker/res/values-de/feature_cloud_strings.xml b/photopicker/res/values-de/feature_cloud_strings.xml
index c0e1187b7..6b8f81757 100644
--- a/photopicker/res/values-de/feature_cloud_strings.xml
+++ b/photopicker/res/values-de/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> von <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> fertig"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Einige Fotos können nicht geladen werden"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Versuch es später noch einmal. Deine Fotos sind verfügbar, sobald das Problem gelöst wurde."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-de/feature_preview_strings.xml b/photopicker/res/values-de/feature_preview_strings.xml
index bfc5c3544..b7fd06fa9 100644
--- a/photopicker/res/values-de/feature_preview_strings.xml
+++ b/photopicker/res/values-de/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Auswählen"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Auswahl aufheben"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Alle <xliff:g id="COUNT">(%1$s)</xliff:g> auswählen"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Auswahl für alle <xliff:g id="COUNT">(%1$s)</xliff:g> aufheben"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Vorschau anzeigen"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Probleme bei der Wiedergabe des Videos"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Prüfe deine Internetverbindung und versuch es noch einmal"</string>
diff --git a/photopicker/res/values-el/core_strings.xml b/photopicker/res/values-el/core_strings.xml
index 2924e659c..ccc46ef38 100644
--- a/photopicker/res/values-el/core_strings.xml
+++ b/photopicker/res/values-el/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Φωτογραφίες και βίντεο"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Μέσα"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Επιλεγμένο"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Προσθήκη <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Τέλος"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Αποεπιλογή όλων"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Επιλέξτε έως και <xliff:g id="COUNT">%1$s</xliff:g> στοιχεία"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Φωτογραφίες"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Λευκώματα"</string>
diff --git a/photopicker/res/values-el/feature_cloud_strings.xml b/photopicker/res/values-el/feature_cloud_strings.xml
index b06442350..ebc97134a 100644
--- a/photopicker/res/values-el/feature_cloud_strings.xml
+++ b/photopicker/res/values-el/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Έτοιμα: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> από <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Δεν είναι δυνατή η φόρτωση ορισμένων φωτογραφιών"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Δοκιμάστε ξανά αργότερα. Οι φωτογραφίες σας θα καταστούν διαθέσιμες μόλις επιλυθεί το πρόβλημα."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-el/feature_preview_strings.xml b/photopicker/res/values-el/feature_preview_strings.xml
index f117e32fa..2f7ed89d5 100644
--- a/photopicker/res/values-el/feature_preview_strings.xml
+++ b/photopicker/res/values-el/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Επιλογή"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Αποεπιλογή"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Επιλογή και των <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Αποεπιλογή και των <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Προεπισκόπηση"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Πρόβλημα με την αναπαραγωγή βίντεο"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Ελέγξτε τη σύνδεσή σας στο διαδίκτυο και δοκιμάστε ξανά"</string>
diff --git a/photopicker/res/values-en-rAU/core_strings.xml b/photopicker/res/values-en-rAU/core_strings.xml
index 45d4fc70a..35b9beb33 100644
--- a/photopicker/res/values-en-rAU/core_strings.xml
+++ b/photopicker/res/values-en-rAU/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Photos and videos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Selected"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Add <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Done"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Deselect all"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Select up to <xliff:g id="COUNT">%1$s</xliff:g> items"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albums"</string>
diff --git a/photopicker/res/values-en-rAU/feature_cloud_strings.xml b/photopicker/res/values-en-rAU/feature_cloud_strings.xml
index 14f76f3ac..6fc9685e1 100644
--- a/photopicker/res/values-en-rAU/feature_cloud_strings.xml
+++ b/photopicker/res/values-en-rAU/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> of <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> ready"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Can\'t load some photos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Please try again later. Your photos will be available once the issue is resolved."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-en-rAU/feature_preview_strings.xml b/photopicker/res/values-en-rAU/feature_preview_strings.xml
index 97f7b109c..3c44c6c3b 100644
--- a/photopicker/res/values-en-rAU/feature_preview_strings.xml
+++ b/photopicker/res/values-en-rAU/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Select"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Deselect"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Select all <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Unselect all <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Preview"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Trouble playing video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Please check your Internet connection and try again"</string>
diff --git a/photopicker/res/values-en-rCA/core_strings.xml b/photopicker/res/values-en-rCA/core_strings.xml
index b0e9fb221..ce2efb179 100644
--- a/photopicker/res/values-en-rCA/core_strings.xml
+++ b/photopicker/res/values-en-rCA/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Photos &amp; videos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Selected"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Add <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"Done"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Deselect all"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Select up to <xliff:g id="COUNT">%1$s</xliff:g> items"</string>
diff --git a/photopicker/res/values-en-rCA/feature_cloud_strings.xml b/photopicker/res/values-en-rCA/feature_cloud_strings.xml
index cb464a0d8..0edd4794b 100644
--- a/photopicker/res/values-en-rCA/feature_cloud_strings.xml
+++ b/photopicker/res/values-en-rCA/feature_cloud_strings.xml
@@ -21,4 +21,12 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> of <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> ready"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Can\'t load some Photos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Try again later. Your photos will be available once the issue is resolved."</string>
+ <string name="photopicker_banner_cloud_media_available_title" msgid="3630564281302679941">"Backed up photos now included"</string>
+ <string name="photopicker_banner_cloud_media_available_message" msgid="6224134038092008505">"You can select photos from <xliff:g id="APP_NAME">%1$s</xliff:g> account <xliff:g id="USER_ACCOUNT">%2$s</xliff:g>"</string>
+ <string name="photopicker_banner_cloud_choose_account_title" msgid="286679907089224434">"Choose <xliff:g id="APP_NAME">%1$s</xliff:g> account"</string>
+ <string name="photopicker_banner_cloud_choose_account_message" msgid="5173210485511611652">"To include photos from <xliff:g id="APP_NAME">%1$s</xliff:g> here, choose an account in the app"</string>
+ <string name="photopicker_banner_cloud_choose_account_button" msgid="3407910358624445230">"Choose account"</string>
+ <string name="photopicker_banner_cloud_choose_provider_title" msgid="992613053341538886">"Choose cloud media app"</string>
+ <string name="photopicker_banner_cloud_choose_provider_message" msgid="1102889303996108506">"To include backed up photos here, choose a cloud media app in Settings"</string>
+ <string name="photopicker_banner_cloud_choose_app_button" msgid="8228365266860220123">"Choose app"</string>
</resources>
diff --git a/photopicker/res/values-en-rCA/feature_preview_strings.xml b/photopicker/res/values-en-rCA/feature_preview_strings.xml
index d4d7eb2a0..89462e22e 100644
--- a/photopicker/res/values-en-rCA/feature_preview_strings.xml
+++ b/photopicker/res/values-en-rCA/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Select"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Deselect"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Select all <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Unselect all <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Preview"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Trouble playing video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Check your internet connection and try again"</string>
diff --git a/photopicker/res/values-en-rGB/core_strings.xml b/photopicker/res/values-en-rGB/core_strings.xml
index 45d4fc70a..35b9beb33 100644
--- a/photopicker/res/values-en-rGB/core_strings.xml
+++ b/photopicker/res/values-en-rGB/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Photos and videos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Selected"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Add <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Done"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Deselect all"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Select up to <xliff:g id="COUNT">%1$s</xliff:g> items"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albums"</string>
diff --git a/photopicker/res/values-en-rGB/feature_cloud_strings.xml b/photopicker/res/values-en-rGB/feature_cloud_strings.xml
index 14f76f3ac..6fc9685e1 100644
--- a/photopicker/res/values-en-rGB/feature_cloud_strings.xml
+++ b/photopicker/res/values-en-rGB/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> of <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> ready"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Can\'t load some photos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Please try again later. Your photos will be available once the issue is resolved."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-en-rGB/feature_preview_strings.xml b/photopicker/res/values-en-rGB/feature_preview_strings.xml
index 97f7b109c..3c44c6c3b 100644
--- a/photopicker/res/values-en-rGB/feature_preview_strings.xml
+++ b/photopicker/res/values-en-rGB/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Select"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Deselect"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Select all <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Unselect all <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Preview"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Trouble playing video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Please check your Internet connection and try again"</string>
diff --git a/photopicker/res/values-en-rIN/core_strings.xml b/photopicker/res/values-en-rIN/core_strings.xml
index 45d4fc70a..35b9beb33 100644
--- a/photopicker/res/values-en-rIN/core_strings.xml
+++ b/photopicker/res/values-en-rIN/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Photos and videos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Selected"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Add <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Done"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Deselect all"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Select up to <xliff:g id="COUNT">%1$s</xliff:g> items"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albums"</string>
diff --git a/photopicker/res/values-en-rIN/feature_cloud_strings.xml b/photopicker/res/values-en-rIN/feature_cloud_strings.xml
index 14f76f3ac..6fc9685e1 100644
--- a/photopicker/res/values-en-rIN/feature_cloud_strings.xml
+++ b/photopicker/res/values-en-rIN/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> of <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> ready"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Can\'t load some photos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Please try again later. Your photos will be available once the issue is resolved."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-en-rIN/feature_preview_strings.xml b/photopicker/res/values-en-rIN/feature_preview_strings.xml
index 97f7b109c..3c44c6c3b 100644
--- a/photopicker/res/values-en-rIN/feature_preview_strings.xml
+++ b/photopicker/res/values-en-rIN/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Select"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Deselect"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Select all <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Unselect all <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Preview"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Trouble playing video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Please check your Internet connection and try again"</string>
diff --git a/photopicker/res/values-en-rXC/core_strings.xml b/photopicker/res/values-en-rXC/core_strings.xml
index e500fdafe..6e0c81a39 100644
--- a/photopicker/res/values-en-rXC/core_strings.xml
+++ b/photopicker/res/values-en-rXC/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‎‎Photos &amp; videos‎‏‎‎‏‎"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎Media‎‏‎‎‏‎"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎Selected‎‏‎‎‏‎"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‏‏‎‎‎‏‏‏‎‎Add ‎‏‎‎‏‏‎<xliff:g id="COUNT">(%1$s)</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‏‏‏‏‎Done‎‏‎‎‏‎"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‎‎‏‎‎‎‎‎Deselect all‎‏‎‎‏‎"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎Select up to ‎‏‎‎‏‏‎<xliff:g id="COUNT">%1$s</xliff:g>‎‏‎‎‏‏‏‎ items‎‏‎‎‏‎"</string>
diff --git a/photopicker/res/values-en-rXC/feature_cloud_strings.xml b/photopicker/res/values-en-rXC/feature_cloud_strings.xml
index c1dbc9bf8..2131da35b 100644
--- a/photopicker/res/values-en-rXC/feature_cloud_strings.xml
+++ b/photopicker/res/values-en-rXC/feature_cloud_strings.xml
@@ -21,4 +21,12 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g>‎‏‎‎‏‏‏‎ of ‎‏‎‎‏‏‎<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>‎‏‎‎‏‏‏‎ ready‎‏‎‎‏‎"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‎‎‏‎‎‎‎‎‎‏‎‏‏‎‎Can\'t load some Photos‎‏‎‎‏‎"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‎‎Try again later. Your photos will be available once the issue is resolved.‎‏‎‎‏‎"</string>
+ <string name="photopicker_banner_cloud_media_available_title" msgid="3630564281302679941">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‎‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‏‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‎‎‎‏‎‏‎Backed up photos now included‎‏‎‎‏‎"</string>
+ <string name="photopicker_banner_cloud_media_available_message" msgid="6224134038092008505">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎You can select photos from ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ account ‎‏‎‎‏‏‎<xliff:g id="USER_ACCOUNT">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+ <string name="photopicker_banner_cloud_choose_account_title" msgid="286679907089224434">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‏‎‎‏‎‏‏‏‏‎‎‏‎‎Choose ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ account‎‏‎‎‏‎"</string>
+ <string name="photopicker_banner_cloud_choose_account_message" msgid="5173210485511611652">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‏‏‎‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‎‎‎‏‎‎‎To include photos from ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ here, choose an account in the app‎‏‎‎‏‎"</string>
+ <string name="photopicker_banner_cloud_choose_account_button" msgid="3407910358624445230">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‎‏‎‏‏‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‏‎‎Choose account‎‏‎‎‏‎"</string>
+ <string name="photopicker_banner_cloud_choose_provider_title" msgid="992613053341538886">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‎Choose cloud media app‎‏‎‎‏‎"</string>
+ <string name="photopicker_banner_cloud_choose_provider_message" msgid="1102889303996108506">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‏‎‏‎‎To include backed up photos here, choose a cloud media app in Settings‎‏‎‎‏‎"</string>
+ <string name="photopicker_banner_cloud_choose_app_button" msgid="8228365266860220123">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‎‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‏‏‎‎‏‏‏‏‎‏‏‎‏‏‎‏‏‎Choose app‎‏‎‎‏‎"</string>
</resources>
diff --git a/photopicker/res/values-en-rXC/feature_preview_strings.xml b/photopicker/res/values-en-rXC/feature_preview_strings.xml
index 724474525..42273b73d 100644
--- a/photopicker/res/values-en-rXC/feature_preview_strings.xml
+++ b/photopicker/res/values-en-rXC/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‏‎‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‏‎‏‏‎‎Select‎‏‎‎‏‎"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‎‎‎‎‎‏‎‏‎‎‏‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎Deselect‎‏‎‎‏‎"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‎‎Select all ‎‏‎‎‏‏‎<xliff:g id="COUNT">(%1$s)</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎Unselect all ‎‏‎‎‏‏‎<xliff:g id="COUNT">(%1$s)</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎‎‏‏‎‏‎‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‎‎‎‏‎‎‎‏‏‏‎‏‏‎Preview‎‏‎‎‏‎"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‎Trouble playing video‎‏‎‎‏‎"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎Check your internet connection and try again‎‏‎‎‏‎"</string>
diff --git a/photopicker/res/values-es-rUS/core_strings.xml b/photopicker/res/values-es-rUS/core_strings.xml
index c126698b2..8b02930a3 100644
--- a/photopicker/res/values-es-rUS/core_strings.xml
+++ b/photopicker/res/values-es-rUS/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotos y videos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Contenido multimedia"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Seleccionado"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Agregar <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Listo"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Anular toda la selección"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Elige <xliff:g id="COUNT">%1$s</xliff:g> elementos como máximo"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Álbumes"</string>
diff --git a/photopicker/res/values-es-rUS/feature_cloud_strings.xml b/photopicker/res/values-es-rUS/feature_cloud_strings.xml
index d1e59ca80..5b670a0c9 100644
--- a/photopicker/res/values-es-rUS/feature_cloud_strings.xml
+++ b/photopicker/res/values-es-rUS/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Elementos listos: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> de <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Se produjo un error al cargar algunas fotos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Vuelve a intentarlo más tarde. Tus fotos estarán disponibles una vez que se resuelva el problema."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-es-rUS/feature_preview_strings.xml b/photopicker/res/values-es-rUS/feature_preview_strings.xml
index 161c4b5f3..5c1c8e590 100644
--- a/photopicker/res/values-es-rUS/feature_preview_strings.xml
+++ b/photopicker/res/values-es-rUS/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Seleccionar"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Anular selección"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Seleccionar todos los <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Anular la selección de todos los <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Vista previa"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Se produjo un error al reproducir el video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Revisa la conexión a Internet y vuelve a intentarlo"</string>
diff --git a/photopicker/res/values-es/core_strings.xml b/photopicker/res/values-es/core_strings.xml
index dd41b186c..017648b08 100644
--- a/photopicker/res/values-es/core_strings.xml
+++ b/photopicker/res/values-es/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotos y vídeos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Contenido multimedia"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Seleccionado"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Añadir <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Hecho"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Desmarcar todo"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Selecciona hasta <xliff:g id="COUNT">%1$s</xliff:g> elementos"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Álbumes"</string>
diff --git a/photopicker/res/values-es/feature_cloud_strings.xml b/photopicker/res/values-es/feature_cloud_strings.xml
index bfb4f4ef4..0872fe908 100644
--- a/photopicker/res/values-es/feature_cloud_strings.xml
+++ b/photopicker/res/values-es/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> de <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> listos"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"No se pueden cargar algunas fotos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Inténtalo de nuevo más tarde. Tus fotos estarán disponibles cuando se resuelva el problema."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-es/feature_preview_strings.xml b/photopicker/res/values-es/feature_preview_strings.xml
index 0b763d9fa..bb3377d03 100644
--- a/photopicker/res/values-es/feature_preview_strings.xml
+++ b/photopicker/res/values-es/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Seleccionar"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Desmarcar"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Seleccionar todo (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Deseleccionar todo (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Vista previa"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Hay problemas para reproducir el vídeo"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Comprueba tu conexión a Internet y vuelve a intentarlo"</string>
diff --git a/photopicker/res/values-et/core_strings.xml b/photopicker/res/values-et/core_strings.xml
index 6aa415a54..df6ed079b 100644
--- a/photopicker/res/values-et/core_strings.xml
+++ b/photopicker/res/values-et/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotod ja videod"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Meedia"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Valitud"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Lisa <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Valmis"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Tühista kogu valik"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Valige kuni <xliff:g id="COUNT">%1$s</xliff:g> üksust"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotod"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumid"</string>
diff --git a/photopicker/res/values-et/feature_cloud_strings.xml b/photopicker/res/values-et/feature_cloud_strings.xml
index 84854a9c4..092a5f55c 100644
--- a/photopicker/res/values-et/feature_cloud_strings.xml
+++ b/photopicker/res/values-et/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>-st on valmis"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Mõnda fotot ei saa laadida"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Proovige hiljem uuesti. Teie fotod on saadaval pärast probleemi lahendamist."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-et/feature_preview_strings.xml b/photopicker/res/values-et/feature_preview_strings.xml
index 40cff466d..0b85ea80b 100644
--- a/photopicker/res/values-et/feature_preview_strings.xml
+++ b/photopicker/res/values-et/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Vali"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Tühista valik"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Vali kõik <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Tühista kõigi <xliff:g id="COUNT">(%1$s)</xliff:g> valik"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Eelvaade"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Probleem video esitamisel"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Kontrollige oma internetiühendust ja proovige uuesti"</string>
diff --git a/photopicker/res/values-eu/core_strings.xml b/photopicker/res/values-eu/core_strings.xml
index de15c3a37..a15bcc899 100644
--- a/photopicker/res/values-eu/core_strings.xml
+++ b/photopicker/res/values-eu/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Argazkiak eta bideoak"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Multimedia-edukia"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Hautatuta"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Gehitu <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Eginda"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Desautatu guztiak"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Hautatu <xliff:g id="COUNT">%1$s</xliff:g> elementu, gehienez"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Argazkiak"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumak"</string>
diff --git a/photopicker/res/values-eu/feature_cloud_strings.xml b/photopicker/res/values-eu/feature_cloud_strings.xml
index 7c94e494f..d29617af1 100644
--- a/photopicker/res/values-eu/feature_cloud_strings.xml
+++ b/photopicker/res/values-eu/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g>/<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> prest"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Ezin dira kargatu argazki batzuk"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Saiatu berriro geroago. Arazoa konpondu ondoren egongo dira erabilgarri argazkiak."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-eu/feature_preview_strings.xml b/photopicker/res/values-eu/feature_preview_strings.xml
index 79ea21331..1d494e011 100644
--- a/photopicker/res/values-eu/feature_preview_strings.xml
+++ b/photopicker/res/values-eu/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Hautatu"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Desautatu"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Hautatu guztiak (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Desautatu guztiak (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Aurreikusi"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Arazoren bat izan da bideoa erreproduzitzean"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Egiaztatu Internetera konektatuta zaudela eta saiatu berriro"</string>
diff --git a/photopicker/res/values-fa/core_strings.xml b/photopicker/res/values-fa/core_strings.xml
index 0e304b872..de5e6e371 100644
--- a/photopicker/res/values-fa/core_strings.xml
+++ b/photopicker/res/values-fa/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"عکس و ویدیو"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"رسانه"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"انتخاب‌شده"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"افزودن <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"تمام"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"لغو انتخاب کردن همه"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"حداکثر <xliff:g id="COUNT">%1$s</xliff:g> مورد انتخاب کنید"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"آلبوم‌ها"</string>
diff --git a/photopicker/res/values-fa/feature_cloud_strings.xml b/photopicker/res/values-fa/feature_cloud_strings.xml
index 86d7c2a2b..e153beb4b 100644
--- a/photopicker/res/values-fa/feature_cloud_strings.xml
+++ b/photopicker/res/values-fa/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"‫<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> مورد از <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> مورد آماده است"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"نمی‌توان برخی‌از عکس‌ها را بار کرد"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"بعداً دوباره امتحان کنید. عکس‌هایتان پس‌از رفع مشکل دردسترس خواهد بود."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-fa/feature_preview_strings.xml b/photopicker/res/values-fa/feature_preview_strings.xml
index b4e6e4a07..ccbf9c13a 100644
--- a/photopicker/res/values-fa/feature_preview_strings.xml
+++ b/photopicker/res/values-fa/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"انتخاب کردن"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"لغو انتخاب کردن"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"انتخاب کل <xliff:g id="COUNT">(%1$s)</xliff:g> مورد"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"لغو انتخاب کل <xliff:g id="COUNT">(%1$s)</xliff:g> مورد"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"پیش‌نمایش"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"در پخش ویدیو مشکل رخ داد"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"اتصال اینترنت را بررسی کنید و دوباره امتحان کنید"</string>
diff --git a/photopicker/res/values-fi/core_strings.xml b/photopicker/res/values-fi/core_strings.xml
index 00a2043db..5d5706a25 100644
--- a/photopicker/res/values-fi/core_strings.xml
+++ b/photopicker/res/values-fi/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Kuvat ja videot"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Valittu"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Lisää <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Valmis"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Poista valinta kaikista"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Valitse enintään <xliff:g id="COUNT">%1$s</xliff:g>"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Kuvat"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumit"</string>
diff --git a/photopicker/res/values-fi/feature_cloud_strings.xml b/photopicker/res/values-fi/feature_cloud_strings.xml
index c850864cf..63cb36668 100644
--- a/photopicker/res/values-fi/feature_cloud_strings.xml
+++ b/photopicker/res/values-fi/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g>/<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> valmiina"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Joitain kuvia ei voi ladata"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Yritä myöhemmin uudelleen. Kuvat ovat saatavilla, kun ongelma on korjattu."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-fi/feature_preview_strings.xml b/photopicker/res/values-fi/feature_preview_strings.xml
index faaf4ec6b..f69aef05e 100644
--- a/photopicker/res/values-fi/feature_preview_strings.xml
+++ b/photopicker/res/values-fi/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Valitse"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Poista valinta"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Valitse kaikki <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Poista kaikki <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Esikatsele"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Ongelma videon toistamisessa"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Tarkista internetyhteys ja yritä uudelleen"</string>
diff --git a/photopicker/res/values-fr-rCA/core_strings.xml b/photopicker/res/values-fr-rCA/core_strings.xml
index e0293a664..f1230a9aa 100644
--- a/photopicker/res/values-fr-rCA/core_strings.xml
+++ b/photopicker/res/values-fr-rCA/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Photos et vidéos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Contenu multimédia"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Sélectionné"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Ajouter <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"OK"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Tout désélectionner"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Sélectionnez jusqu\'à <xliff:g id="COUNT">%1$s</xliff:g> éléments"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albums"</string>
diff --git a/photopicker/res/values-fr-rCA/feature_cloud_strings.xml b/photopicker/res/values-fr-rCA/feature_cloud_strings.xml
index 8f106f5e6..d85eac017 100644
--- a/photopicker/res/values-fr-rCA/feature_cloud_strings.xml
+++ b/photopicker/res/values-fr-rCA/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> sur <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> prêt(s)"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Impossible de charger certaines photos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Réessayez plus tard. Vos photos seront accessibles dès que le problème sera résolu."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-fr-rCA/feature_preview_strings.xml b/photopicker/res/values-fr-rCA/feature_preview_strings.xml
index 73721a71a..1ac08e16b 100644
--- a/photopicker/res/values-fr-rCA/feature_preview_strings.xml
+++ b/photopicker/res/values-fr-rCA/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Sélectionner"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Désélectionner"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Sélectionner les <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Désélectionner les <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Aperçu"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Difficulté à faire jouer la vidéo"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Vérifiez votre connexion Internet et réessayez"</string>
diff --git a/photopicker/res/values-fr/core_strings.xml b/photopicker/res/values-fr/core_strings.xml
index 1e981ee9c..bf0ecc493 100644
--- a/photopicker/res/values-fr/core_strings.xml
+++ b/photopicker/res/values-fr/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Photos et vidéos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Contenus multimédias"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Sélectionnée"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Ajouter <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"OK"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Tout désélectionner"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Sélectionnez <xliff:g id="COUNT">%1$s</xliff:g> éléments maximum"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albums"</string>
diff --git a/photopicker/res/values-fr/feature_cloud_strings.xml b/photopicker/res/values-fr/feature_cloud_strings.xml
index b692fa919..17263fa71 100644
--- a/photopicker/res/values-fr/feature_cloud_strings.xml
+++ b/photopicker/res/values-fr/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Prêt(s) : <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> sur <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Impossible de charger certaines photos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Réessayez plus tard. Vos photos seront disponibles une fois le problème résolu."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-fr/feature_preview_strings.xml b/photopicker/res/values-fr/feature_preview_strings.xml
index 88ee340ea..c22717fc1 100644
--- a/photopicker/res/values-fr/feature_preview_strings.xml
+++ b/photopicker/res/values-fr/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Sélectionner"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Désélectionner"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Tout sélectionner (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Tout désélectionner (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Prévisualiser"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Problème de lecture vidéo"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Vérifiez votre connexion Internet, puis réessayez"</string>
diff --git a/photopicker/res/values-gl/core_strings.xml b/photopicker/res/values-gl/core_strings.xml
index d47009396..05485636d 100644
--- a/photopicker/res/values-gl/core_strings.xml
+++ b/photopicker/res/values-gl/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotos e vídeos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Multimedia"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Elemento seleccionado"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Engadir <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"Feito"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Anular toda a selección"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Selecciona ata <xliff:g id="COUNT">%1$s</xliff:g> elementos"</string>
diff --git a/photopicker/res/values-gl/feature_cloud_strings.xml b/photopicker/res/values-gl/feature_cloud_strings.xml
index 56d5ef5ed..bb2b3e4e3 100644
--- a/photopicker/res/values-gl/feature_cloud_strings.xml
+++ b/photopicker/res/values-gl/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Elementos listos: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> de <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Non se puideron cargar algunhas fotos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Téntao de novo máis tarde. As túas fotos estarán dispoñibles en canto se resolva o problema."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-gl/feature_preview_strings.xml b/photopicker/res/values-gl/feature_preview_strings.xml
index 0e166464e..aa9c76052 100644
--- a/photopicker/res/values-gl/feature_preview_strings.xml
+++ b/photopicker/res/values-gl/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Seleccionar"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Anular selección"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Seleccionar todo (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Anular selección (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Previsualizar"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Produciuse un problema ao reproducir o vídeo"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Comproba a túa conexión a Internet e téntao de novo"</string>
diff --git a/photopicker/res/values-gu/core_strings.xml b/photopicker/res/values-gu/core_strings.xml
index b803540f6..981a8dffc 100644
--- a/photopicker/res/values-gu/core_strings.xml
+++ b/photopicker/res/values-gu/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ફોટા અને વીડિયો"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"મીડિયા"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"પસંદ કર્યું છે"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> ઉમેરો"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"થઈ ગયું"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"બધાને નાપસંદ કરો"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> જેટલી આઇટમ પસંદ કરો"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"આલ્બમ"</string>
diff --git a/photopicker/res/values-gu/feature_cloud_strings.xml b/photopicker/res/values-gu/feature_cloud_strings.xml
index a30450dbb..a379ea5ab 100644
--- a/photopicker/res/values-gu/feature_cloud_strings.xml
+++ b/photopicker/res/values-gu/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>માંથી <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> તૈયાર"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"અમુક ફોટા લોડ કરી શકાતા નથી"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"થોડા સમય પછી ફરી પ્રયાસ કરો. એકવાર સમસ્યાનું નિરાકરણ થઈ જાય, તે પછી તમારા ફોટા ઉપલબ્ધ થશે."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-gu/feature_preview_strings.xml b/photopicker/res/values-gu/feature_preview_strings.xml
index 9698c2449..3cd081417 100644
--- a/photopicker/res/values-gu/feature_preview_strings.xml
+++ b/photopicker/res/values-gu/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"પસંદ કરો"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"નાપસંદ કરો"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"તમામ <xliff:g id="COUNT">(%1$s)</xliff:g> પસંદ કરો"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"તમામ <xliff:g id="COUNT">(%1$s)</xliff:g> નાપસંદ કરો"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"પ્રીવ્યૂ કરો"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"વીડિયો ચલાવવામાં સમસ્યા આવી"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"તમારું ઇન્ટરનેટ કનેક્શન ચેક કરો અને ફરી પ્રયાસ કરો"</string>
diff --git a/photopicker/res/values-hi/core_strings.xml b/photopicker/res/values-hi/core_strings.xml
index 1fe7f24f2..337e7391b 100644
--- a/photopicker/res/values-hi/core_strings.xml
+++ b/photopicker/res/values-hi/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"फ़ोटो और वीडियो"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"मीडिया"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"चुना गया"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> जोड़ें"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"हो गया"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"सभी से चुने हुए का निशान हटाएं"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"ज़्यादा से ज़्यादा <xliff:g id="COUNT">%1$s</xliff:g> आइटम चुनें"</string>
diff --git a/photopicker/res/values-hi/feature_cloud_strings.xml b/photopicker/res/values-hi/feature_cloud_strings.xml
index 435c34fbe..8e2d3f211 100644
--- a/photopicker/res/values-hi/feature_cloud_strings.xml
+++ b/photopicker/res/values-hi/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> में से <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> तैयार हैं"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"कुछ फ़ोटो लोड नहीं की जा सकीं"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"बाद में कोशिश करें. समस्या ठीक होने के बाद आपकी फ़ोटो उपलब्ध हो जाएंगी."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-hi/feature_preview_strings.xml b/photopicker/res/values-hi/feature_preview_strings.xml
index e6ecaf4bb..04431b78a 100644
--- a/photopicker/res/values-hi/feature_preview_strings.xml
+++ b/photopicker/res/values-hi/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"चुनें"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"चुने हुए को हटाएं"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"सभी चुनें <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"सभी से चुने हुए का निशान हटाएं <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"झलक"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"वीडियो चलाने में समस्या हो रही है"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"अपने इंटरनेट कनेक्शन की जांच करें और फिर से कोशिश करें"</string>
diff --git a/photopicker/res/values-hr/core_strings.xml b/photopicker/res/values-hr/core_strings.xml
index 504f04b5d..53387a8bf 100644
--- a/photopicker/res/values-hr/core_strings.xml
+++ b/photopicker/res/values-hr/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotografije i videozapisi"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Mediji"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Odabrano"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Dodaj <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Gotovo"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Poništi odabir za sve"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Odaberite najviše ovoliko stavki: <xliff:g id="COUNT">%1$s</xliff:g>"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotografije"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumi"</string>
diff --git a/photopicker/res/values-hr/feature_cloud_strings.xml b/photopicker/res/values-hr/feature_cloud_strings.xml
index ecfe6c303..0d5a12001 100644
--- a/photopicker/res/values-hr/feature_cloud_strings.xml
+++ b/photopicker/res/values-hr/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Spremno: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> od <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Neke fotografije ne mogu se učitati"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Pokušajte ponovno kasnije. Vaše fotografije bit će dostupne kad se problem riješi."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-hr/feature_preview_strings.xml b/photopicker/res/values-hr/feature_preview_strings.xml
index 2dce4d5c7..d70aae754 100644
--- a/photopicker/res/values-hr/feature_preview_strings.xml
+++ b/photopicker/res/values-hr/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Odaberi"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Poništi odabir"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Odaberi sve (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Poništi odabir za sve (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Pregled"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Poteškoće s reprodukcijom videozapisa"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Provjerite internetsku vezu i pokušajte ponovo"</string>
diff --git a/photopicker/res/values-hu/core_strings.xml b/photopicker/res/values-hu/core_strings.xml
index e73a50c8e..80be78b4d 100644
--- a/photopicker/res/values-hu/core_strings.xml
+++ b/photopicker/res/values-hu/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotók és videók"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Média"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Kijelölve"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> hozzáadása"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Kész"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Összes kijelölés megszüntetése"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Legfeljebb <xliff:g id="COUNT">%1$s</xliff:g> elemet jelölhet ki"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotók"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumok"</string>
diff --git a/photopicker/res/values-hu/feature_cloud_strings.xml b/photopicker/res/values-hu/feature_cloud_strings.xml
index c8b6714d7..7563bb074 100644
--- a/photopicker/res/values-hu/feature_cloud_strings.xml
+++ b/photopicker/res/values-hu/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>/<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> kész"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Egyes fotók nem tölthetők be"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Próbálkozzon újra később. Fotói hozzáférhetők lesznek a probléma elhárítását követően."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-hu/feature_preview_strings.xml b/photopicker/res/values-hu/feature_preview_strings.xml
index 033c2397b..82d4409af 100644
--- a/photopicker/res/values-hu/feature_preview_strings.xml
+++ b/photopicker/res/values-hu/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Kijelölés"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Kijelölés megszüntetése"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Összes (<xliff:g id="COUNT">(%1$s)</xliff:g>) kijelölése"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Összes (<xliff:g id="COUNT">(%1$s)</xliff:g>) kijelölés megszüntetése"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Előnézet"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Probléma merült fel a videó lejátszásakor"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Ellenőrizze az internetkapcsolatot, és próbálja újra"</string>
diff --git a/photopicker/res/values-hy/core_strings.xml b/photopicker/res/values-hy/core_strings.xml
index cebedb863..47df78421 100644
--- a/photopicker/res/values-hy/core_strings.xml
+++ b/photopicker/res/values-hy/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Լուսանկարներ և տեսանյութեր"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Մեդիա"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Ընտրված է"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Ավելացնել <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Պատրաստ է"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Չեղարկել ընտրությունը"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Ընտրեք մինչև <xliff:g id="COUNT">%1$s</xliff:g> տարր"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Լուսանկարներ"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Ալբոմներ"</string>
diff --git a/photopicker/res/values-hy/feature_cloud_strings.xml b/photopicker/res/values-hy/feature_cloud_strings.xml
index 52360cae0..8d4b4d404 100644
--- a/photopicker/res/values-hy/feature_cloud_strings.xml
+++ b/photopicker/res/values-hy/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g>/<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> պատրաստ է"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Չհաջողվեց բեռնել որոշ լուսանկարներ"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Փորձեք ավելի ուշ։ Ձեր լուսանկարները հասանելի կլինեն, երբ խնդիրը լուծվի։"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-hy/feature_preview_strings.xml b/photopicker/res/values-hy/feature_preview_strings.xml
index 49efb32fe..ced491778 100644
--- a/photopicker/res/values-hy/feature_preview_strings.xml
+++ b/photopicker/res/values-hy/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Ընտրել"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Չեղարկել ընտրությունը"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Ընտրել բոլոր <xliff:g id="COUNT">(%1$s)</xliff:g>-ը"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Չեղարկել բոլոր <xliff:g id="COUNT">(%1$s)</xliff:g>-ի ընտրությունը"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Նախադիտել"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Տեսանյութի նվագարկման սխալ"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Ստուգեք ձեր ինտերնետ կապը և նորից փորձեք"</string>
diff --git a/photopicker/res/values-in/core_strings.xml b/photopicker/res/values-in/core_strings.xml
index 969eb474c..f4271bed6 100644
--- a/photopicker/res/values-in/core_strings.xml
+++ b/photopicker/res/values-in/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Foto &amp; video"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Dipilih"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Tambahkan <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Selesai"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Batalkan semua pilihan"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Pilih hingga <xliff:g id="COUNT">%1$s</xliff:g> item"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Foto"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Album"</string>
diff --git a/photopicker/res/values-in/feature_cloud_strings.xml b/photopicker/res/values-in/feature_cloud_strings.xml
index 1529c2331..470ec7c53 100644
--- a/photopicker/res/values-in/feature_cloud_strings.xml
+++ b/photopicker/res/values-in/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> dari <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> siap"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Tidak dapat memuat beberapa Foto"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Coba lagi nanti. Foto Anda akan tersedia setelah masalah terselesaikan."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-in/feature_preview_strings.xml b/photopicker/res/values-in/feature_preview_strings.xml
index 9c4c31181..a16824d47 100644
--- a/photopicker/res/values-in/feature_preview_strings.xml
+++ b/photopicker/res/values-in/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Pilih"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Batalkan pilihan"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Pilih semua <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Batalkan pilihan semua <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Pratinjau"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Terjadi masalah saat memutar video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Periksa koneksi internet Anda, lalu coba lagi"</string>
diff --git a/photopicker/res/values-is/core_strings.xml b/photopicker/res/values-is/core_strings.xml
index 19b823610..e60ffed48 100644
--- a/photopicker/res/values-is/core_strings.xml
+++ b/photopicker/res/values-is/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Myndir og vídeó"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Efni"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Valið"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Bæta <xliff:g id="COUNT">(%1$s)</xliff:g> við"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Lokið"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Afvelja allt"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Veldu allt að <xliff:g id="COUNT">%1$s</xliff:g> atriði"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Myndir"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Plötur"</string>
diff --git a/photopicker/res/values-is/feature_cloud_strings.xml b/photopicker/res/values-is/feature_cloud_strings.xml
index 1f3e25e4f..effad1f8a 100644
--- a/photopicker/res/values-is/feature_cloud_strings.xml
+++ b/photopicker/res/values-is/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> af <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> til reiðu"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Ekki tekst að hlaða sumum myndum"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Reyndu aftur síðar. Myndirnar þínar verða tiltækar um leið og vandamálið er leyst."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-is/feature_preview_strings.xml b/photopicker/res/values-is/feature_preview_strings.xml
index 7207c2ee2..10c702a92 100644
--- a/photopicker/res/values-is/feature_preview_strings.xml
+++ b/photopicker/res/values-is/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Velja"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Afvelja"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Velja allt (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Afturkalla val á öllu (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Forskoða"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Vandamál við spilun vídeós"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Athugaðu nettenginguna og reyndu aftur"</string>
diff --git a/photopicker/res/values-it/core_strings.xml b/photopicker/res/values-it/core_strings.xml
index ec7361025..377b2d6c1 100644
--- a/photopicker/res/values-it/core_strings.xml
+++ b/photopicker/res/values-it/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Foto e video"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Contenuti multimediali"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Opzione selezionata"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Aggiungi <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Fine"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Deseleziona tutto"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Seleziona massimo <xliff:g id="COUNT">%1$s</xliff:g> elementi"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Foto"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Album"</string>
diff --git a/photopicker/res/values-it/feature_cloud_strings.xml b/photopicker/res/values-it/feature_cloud_strings.xml
index af0740f30..97ddc0555 100644
--- a/photopicker/res/values-it/feature_cloud_strings.xml
+++ b/photopicker/res/values-it/feature_cloud_strings.xml
@@ -21,4 +21,12 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> su <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> pronti"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Impossibile caricare alcune foto"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Riprova più tardi. Le tue foto saranno disponibili dopo aver risolto il problema."</string>
+ <string name="photopicker_banner_cloud_media_available_title" msgid="3630564281302679941">"Ora sono incluse le foto di cui hai eseguito il backup"</string>
+ <string name="photopicker_banner_cloud_media_available_message" msgid="6224134038092008505">"Puoi selezionare foto dall\'account <xliff:g id="USER_ACCOUNT">%2$s</xliff:g> nell\'app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="photopicker_banner_cloud_choose_account_title" msgid="286679907089224434">"Scegli un account <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="photopicker_banner_cloud_choose_account_message" msgid="5173210485511611652">"Per includere qui le foto di <xliff:g id="APP_NAME">%1$s</xliff:g>, scegli un account nell\'app"</string>
+ <string name="photopicker_banner_cloud_choose_account_button" msgid="3407910358624445230">"Scegli account"</string>
+ <string name="photopicker_banner_cloud_choose_provider_title" msgid="992613053341538886">"Scegli un\'app multimediale con funzionalità cloud"</string>
+ <string name="photopicker_banner_cloud_choose_provider_message" msgid="1102889303996108506">"Per includere qui le foto di cui hai eseguito il backup, scegli un\'app multimediale con funzionalità cloud nelle Impostazioni"</string>
+ <string name="photopicker_banner_cloud_choose_app_button" msgid="8228365266860220123">"Scegli app"</string>
</resources>
diff --git a/photopicker/res/values-it/feature_preview_strings.xml b/photopicker/res/values-it/feature_preview_strings.xml
index 28bd2a93b..03834b554 100644
--- a/photopicker/res/values-it/feature_preview_strings.xml
+++ b/photopicker/res/values-it/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Seleziona"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Deseleziona"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Seleziona tutto (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Deseleziona tutto (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Anteprima"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Errore durante la riproduzione del video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Controlla la connessione a internet e riprova"</string>
diff --git a/photopicker/res/values-iw/core_strings.xml b/photopicker/res/values-iw/core_strings.xml
index 1ba3a8820..fb3d8770e 100644
--- a/photopicker/res/values-iw/core_strings.xml
+++ b/photopicker/res/values-iw/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"תמונות וסרטונים"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"מדיה"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"האפשרות נבחרה"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"הוספה של <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"סיום"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"ביטול הבחירה של הכול"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"אפשר לבחור <xliff:g id="COUNT">%1$s</xliff:g> פריטים לכל היותר"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"תמונות"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"אלבומים"</string>
diff --git a/photopicker/res/values-iw/feature_cloud_strings.xml b/photopicker/res/values-iw/feature_cloud_strings.xml
index 8a8de3ffc..63875f851 100644
--- a/photopicker/res/values-iw/feature_cloud_strings.xml
+++ b/photopicker/res/values-iw/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"‫<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> מתוך <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> מוכנים"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"לא ניתן לטעון חלק מהתמונות"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"כדאי לנסות שוב אחר כך. התמונות שלך יהיו זמינות כשהבעיה תיפתר."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-iw/feature_preview_strings.xml b/photopicker/res/values-iw/feature_preview_strings.xml
index 17cbb35c7..5feaa3397 100644
--- a/photopicker/res/values-iw/feature_preview_strings.xml
+++ b/photopicker/res/values-iw/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"בחירה"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"ביטול הבחירה"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"בחירת הכול (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"ביטול הבחירה של הכול (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"תצוגה מקדימה"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"בעיות בהפעלת הסרטון"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"כדאי לבדוק את החיבור לאינטרנט ולנסות שוב"</string>
diff --git a/photopicker/res/values-ja/core_strings.xml b/photopicker/res/values-ja/core_strings.xml
index 39e0f370e..a7c210652 100644
--- a/photopicker/res/values-ja/core_strings.xml
+++ b/photopicker/res/values-ja/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"写真と動画"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"メディア"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"選択中"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> 枚追加"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"完了"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"選択をすべて解除"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> 件まで選択できます"</string>
diff --git a/photopicker/res/values-ja/feature_cloud_strings.xml b/photopicker/res/values-ja/feature_cloud_strings.xml
index 3a3adc6f3..9695b794e 100644
--- a/photopicker/res/values-ja/feature_cloud_strings.xml
+++ b/photopicker/res/values-ja/feature_cloud_strings.xml
@@ -21,4 +21,12 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g>/<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> 件準備完了"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"読み込めなかった写真があります"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"しばらくしてからもう一度お試しください。問題が解決したら、写真をご利用いただけるようになります。"</string>
+ <string name="photopicker_banner_cloud_media_available_title" msgid="3630564281302679941">"バックアップした写真が追加されました"</string>
+ <string name="photopicker_banner_cloud_media_available_message" msgid="6224134038092008505">"<xliff:g id="APP_NAME">%1$s</xliff:g> のアカウント <xliff:g id="USER_ACCOUNT">%2$s</xliff:g> の写真を選択できます"</string>
+ <string name="photopicker_banner_cloud_choose_account_title" msgid="286679907089224434">"<xliff:g id="APP_NAME">%1$s</xliff:g> のアカウントを選択してください"</string>
+ <string name="photopicker_banner_cloud_choose_account_message" msgid="5173210485511611652">"<xliff:g id="APP_NAME">%1$s</xliff:g> の写真をここに追加するには、アプリでアカウントを選択してください"</string>
+ <string name="photopicker_banner_cloud_choose_account_button" msgid="3407910358624445230">"アカウントの選択"</string>
+ <string name="photopicker_banner_cloud_choose_provider_title" msgid="992613053341538886">"クラウド メディアアプリを選択してください"</string>
+ <string name="photopicker_banner_cloud_choose_provider_message" msgid="1102889303996108506">"バックアップした写真をここに追加するには、[設定] でクラウド メディアアプリを選択してください"</string>
+ <string name="photopicker_banner_cloud_choose_app_button" msgid="8228365266860220123">"アプリを選択"</string>
</resources>
diff --git a/photopicker/res/values-ja/feature_preview_strings.xml b/photopicker/res/values-ja/feature_preview_strings.xml
index 4acf82c7f..5a451bbcd 100644
--- a/photopicker/res/values-ja/feature_preview_strings.xml
+++ b/photopicker/res/values-ja/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"選択"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"選択を解除"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"すべて選択(<xliff:g id="COUNT">(%1$s)</xliff:g> 件)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"すべての選択を解除(<xliff:g id="COUNT">(%1$s)</xliff:g> 件)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"プレビュー"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"動画を再生できません"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"インターネット接続を確認してもう一度お試しください"</string>
diff --git a/photopicker/res/values-ka/core_strings.xml b/photopicker/res/values-ka/core_strings.xml
index 15aad9a18..357089f11 100644
--- a/photopicker/res/values-ka/core_strings.xml
+++ b/photopicker/res/values-ka/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ფოტოები და ვიდეოები"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"მედია"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"არჩეულია"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g>-ის დამატება"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"მზადაა"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"ყველა არჩევის გაუქმება"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"აირჩიეთ <xliff:g id="COUNT">%1$s</xliff:g>-მდე ერთეული"</string>
diff --git a/photopicker/res/values-ka/feature_cloud_strings.xml b/photopicker/res/values-ka/feature_cloud_strings.xml
index 531f8c365..8a389a382 100644
--- a/photopicker/res/values-ka/feature_cloud_strings.xml
+++ b/photopicker/res/values-ka/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> სულ <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>-დან მზადაა"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"ზოგიერთი ფოტოს ჩატვირთვა ვერ ხერხდება"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"ცადეთ მოგვიანებით. ხარვეზის აღმოფხვრის შემდეგ თქვენი ფოტოები ხელმისაწვდომი იქნება."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-ka/feature_preview_strings.xml b/photopicker/res/values-ka/feature_preview_strings.xml
index 31301adfa..1e14adadc 100644
--- a/photopicker/res/values-ka/feature_preview_strings.xml
+++ b/photopicker/res/values-ka/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"არჩევა"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"არჩევის გაუქმება"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"<xliff:g id="COUNT">(%1$s)</xliff:g>-ვეს არჩევა"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"<xliff:g id="COUNT">(%1$s)</xliff:g>-ვეს არჩევის გაუქმება"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"გადახედვა"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"ვიდეოს დაკვრისას წარმოიქმნა პრობლემა"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"შეამოწმეთ ინტერნეტ-კავშირი და ცადეთ ხელახლა"</string>
diff --git a/photopicker/res/values-kk/core_strings.xml b/photopicker/res/values-kk/core_strings.xml
index 5ed3b4fbf..4c49a8c32 100644
--- a/photopicker/res/values-kk/core_strings.xml
+++ b/photopicker/res/values-kk/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Фотосуреттер мен бейнелер"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Meдиа"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Таңдалды"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> қосу"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Дайын"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Барлық таңдаудан бас тарту"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> элементке дейін таңдаңыз."</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Aльбомдар"</string>
diff --git a/photopicker/res/values-kk/feature_cloud_strings.xml b/photopicker/res/values-kk/feature_cloud_strings.xml
index 787c59cc6..b82615615 100644
--- a/photopicker/res/values-kk/feature_cloud_strings.xml
+++ b/photopicker/res/values-kk/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g>/<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> дайын"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Кейбір фотосуреттерді жүктеу мүмкін емес"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Кейінірек қайталап көріңіз. Мәселе шешілген соң, фотосуреттеріңіз қолжетімді болады."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-kk/feature_preview_strings.xml b/photopicker/res/values-kk/feature_preview_strings.xml
index 26dfc3f73..110afb00e 100644
--- a/photopicker/res/values-kk/feature_preview_strings.xml
+++ b/photopicker/res/values-kk/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Таңдау"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Таңдаудан алу"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Барлығын (<xliff:g id="COUNT">(%1$s)</xliff:g>) таңдау"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Барлығын (<xliff:g id="COUNT">(%1$s)</xliff:g>) таңдаудан бас тарту"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Алдын ала көру"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Бейнені ойнату кезінде қиындық туындады"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Интернет байланысын тексеріп, әрекетті қайталап көріңіз."</string>
diff --git a/photopicker/res/values-km/core_strings.xml b/photopicker/res/values-km/core_strings.xml
index 39f59082e..c2b071d44 100644
--- a/photopicker/res/values-km/core_strings.xml
+++ b/photopicker/res/values-km/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"រូបថត និងវីដេអូ"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"មេឌៀ"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"បានជ្រើសរើស"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"បញ្ចូល <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"រួចរាល់"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"ដក​ការ​ជ្រើសរើស​ទាំងអស់"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"ជ្រើសរើស​ធាតុ​រហូតដល់ <xliff:g id="COUNT">%1$s</xliff:g>"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"រូបថត"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"អាល់ប៊ុម"</string>
diff --git a/photopicker/res/values-km/feature_cloud_strings.xml b/photopicker/res/values-km/feature_cloud_strings.xml
index c82cd5f47..f8a393122 100644
--- a/photopicker/res/values-km/feature_cloud_strings.xml
+++ b/photopicker/res/values-km/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> នៃ <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> រួចរាល់ហើយ"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"មិនអាចផ្ទុករូបថតមួយចំនួនបានទេ"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"សូមព្យាយាមម្ដងទៀតនៅពេលក្រោយ។ រូបថត​របស់អ្នក​នឹងអាចប្រើបាន បន្ទាប់ពី​ដោះស្រាយ​បញ្ហា។"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-km/feature_preview_strings.xml b/photopicker/res/values-km/feature_preview_strings.xml
index 76da0b8c0..4c808e957 100644
--- a/photopicker/res/values-km/feature_preview_strings.xml
+++ b/photopicker/res/values-km/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"ជ្រើសរើស"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"ដក​ការជ្រើសរើស"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"ជ្រើសរើសទាំង <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"ដក​ការ​ជ្រើសរើសទាំង <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"មើលសាកល្បង"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"មានបញ្ហាក្នុងការចាក់វីដេអូ"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"ពិនិត្យការតភ្ជាប់អ៊ីនធឺណិតរបស់អ្នក រួចព្យាយាមម្ដងទៀត"</string>
diff --git a/photopicker/res/values-kn/core_strings.xml b/photopicker/res/values-kn/core_strings.xml
index c34506d3f..6c6653d64 100644
--- a/photopicker/res/values-kn/core_strings.xml
+++ b/photopicker/res/values-kn/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ಫೋಟೋಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳು"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"ಮೀಡಿಯಾ"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> ಸೇರಿಸಿ"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"ಮುಗಿದಿದೆ"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"ಎಲ್ಲಾ ಆಯ್ಕೆಯನ್ನು ರದ್ದುಮಾಡಿ"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> ಐಟಂಗಳವರೆಗೆ ಆಯ್ಕೆಮಾಡಿ"</string>
diff --git a/photopicker/res/values-kn/feature_cloud_strings.xml b/photopicker/res/values-kn/feature_cloud_strings.xml
index 97c83d76f..3cf573ed0 100644
--- a/photopicker/res/values-kn/feature_cloud_strings.xml
+++ b/photopicker/res/values-kn/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> ರಲ್ಲಿ <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> ಸಿದ್ಧವಾಗಿವೆ"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"ಕೆಲವು ಫೋಟೋಗಳನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"ನಂತರ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ. ಸಮಸ್ಯೆ ಬಗೆಹರಿದ ನಂತರ ನಿಮ್ಮ ಫೋಟೋಗಳು ಲಭ್ಯವಿರುತ್ತವೆ."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-kn/feature_preview_strings.xml b/photopicker/res/values-kn/feature_preview_strings.xml
index c9afc75c9..dd4e374c3 100644
--- a/photopicker/res/values-kn/feature_preview_strings.xml
+++ b/photopicker/res/values-kn/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"ಆಯ್ಕೆ ಮಾಡಿ"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"ಆಯ್ಕೆ ರದ್ದುಮಾಡಿ"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"<xliff:g id="COUNT">(%1$s)</xliff:g> ಎಲ್ಲವನ್ನೂ ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"<xliff:g id="COUNT">(%1$s)</xliff:g> ಎಲ್ಲವನ್ನೂ ಆಯ್ಕೆ ಮಾಡಬೇಡಿ"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"ಪೂರ್ವವೀಕ್ಷಣೆ"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"ವೀಡಿಯೊ ಪ್ಲೇ ಮಾಡಲು ಸಮಸ್ಯೆಯಾಗಿದೆ"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"ನಿಮ್ಮ ಇಂಟರ್‌ನೆಟ್ ಕನೆಕ್ಷನ್ ಅನ್ನು ಪರಿಶೀಲಿಸಿ ಹಾಗೂ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
diff --git a/photopicker/res/values-ko/core_strings.xml b/photopicker/res/values-ko/core_strings.xml
index 736918781..d0c8ca032 100644
--- a/photopicker/res/values-ko/core_strings.xml
+++ b/photopicker/res/values-ko/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"사진 및 동영상"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"미디어"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"선택됨"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> 추가"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"완료"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"전체 선택 해제"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"최대 <xliff:g id="COUNT">%1$s</xliff:g>개 항목 선택"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"사진"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"앨범"</string>
diff --git a/photopicker/res/values-ko/feature_cloud_strings.xml b/photopicker/res/values-ko/feature_cloud_strings.xml
index 1653d863b..6d3b18763 100644
--- a/photopicker/res/values-ko/feature_cloud_strings.xml
+++ b/photopicker/res/values-ko/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>개 중 <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g>개가 준비됨"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"일부 사진을 로드할 수 없음"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"나중에 다시 시도해 주세요. 사진은 문제가 해결된 후에 사용할 수 있습니다."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-ko/feature_preview_strings.xml b/photopicker/res/values-ko/feature_preview_strings.xml
index 02471b058..097b5ed9a 100644
--- a/photopicker/res/values-ko/feature_preview_strings.xml
+++ b/photopicker/res/values-ko/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"선택"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"선택 해제"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"<xliff:g id="COUNT">(%1$s)</xliff:g>개 모두 선택"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"<xliff:g id="COUNT">(%1$s)</xliff:g>개 모두 선택 해제"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"미리보기"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"동영상 재생 중 문제 발생"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"인터넷 연결 상태를 확인하고 다시 시도해 주세요"</string>
diff --git a/photopicker/res/values-ky/core_strings.xml b/photopicker/res/values-ky/core_strings.xml
index 4ff54931a..596d21405 100644
--- a/photopicker/res/values-ky/core_strings.xml
+++ b/photopicker/res/values-ky/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Сүрөттөр жана видеолор"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Медиа"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Тандалды"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> кошуу"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Бүттү"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Баарын тандоодон чыгаруу"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> объектке чейин тандаңыз"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Сүрөттөр"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Альбомдор"</string>
diff --git a/photopicker/res/values-ky/feature_cloud_strings.xml b/photopicker/res/values-ky/feature_cloud_strings.xml
index 6c49b2010..bc2153a97 100644
--- a/photopicker/res/values-ky/feature_cloud_strings.xml
+++ b/photopicker/res/values-ky/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> ичинен <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> даяр"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Айрым сүрөттөр жүктөлбөй жатат"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Кийинчерээк кайталап көрүңүз. Сүрөттөрүңүз маселе чечилгенден кийин жеткиликтүү болот."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-ky/feature_preview_strings.xml b/photopicker/res/values-ky/feature_preview_strings.xml
index 75174d736..3c1e55552 100644
--- a/photopicker/res/values-ky/feature_preview_strings.xml
+++ b/photopicker/res/values-ky/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Тандоо"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Тандоодон чыгаруу"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"<xliff:g id="COUNT">(%1$s)</xliff:g> тең тандоо"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"<xliff:g id="COUNT">(%1$s)</xliff:g> тең тандоодон чыгаруу"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Алдын ала көрүү"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Видеону ойнотууда маселе келип чыкты"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Интернет байланышыңызды текшерип, кайталап көрүңүз"</string>
diff --git a/photopicker/res/values-lo/core_strings.xml b/photopicker/res/values-lo/core_strings.xml
index 0cfcdc32b..eb20be86e 100644
--- a/photopicker/res/values-lo/core_strings.xml
+++ b/photopicker/res/values-lo/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ຮູບພາບ ແລະ ວິດີໂອ"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"ສື່"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"ເລືອກແລ້ວ"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"ເພີ່ມ <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"ແລ້ວໆ"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"ເຊົາເລືອກທັງໝົດ"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"ເລືອກໄດ້ສູງສຸດ <xliff:g id="COUNT">%1$s</xliff:g> ລາຍການ"</string>
diff --git a/photopicker/res/values-lo/feature_cloud_strings.xml b/photopicker/res/values-lo/feature_cloud_strings.xml
index d6509010d..59c7ad929 100644
--- a/photopicker/res/values-lo/feature_cloud_strings.xml
+++ b/photopicker/res/values-lo/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"ພ້ອມແລ້ວ <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> ຈາກທັງໝົດ <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> ລາຍການ"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"ບໍ່ສາມາດໂຫຼດບາງຮູບພາບໄດ້"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"ກະລຸນາລອງໃໝ່ໃນພາຍຫຼັງ. ຮູບພາບຂອງທ່ານຈະພ້ອມນຳໃຊ້ເມື່ອບັນຫາໄດ້ຮັບການແກ້ໄຂແລ້ວ."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-lo/feature_preview_strings.xml b/photopicker/res/values-lo/feature_preview_strings.xml
index 59a52a20d..4af627d33 100644
--- a/photopicker/res/values-lo/feature_preview_strings.xml
+++ b/photopicker/res/values-lo/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"ເລືອກ"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"ເຊົາເລືອກ"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"ເລືອກທັງໝົດ <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"ບໍ່ເລືອກທັງໝົດ <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"ຕົວຢ່າງ"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"ເກີດບັນຫາໃນການຫຼິ້ນວິດີໂອ"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"ກະລຸນາກວດສອບການເຊື່ອມຕໍ່ອິນເຕີເນັດຂອງທ່ານແລ້ວລອງໃໝ່"</string>
diff --git a/photopicker/res/values-lt/core_strings.xml b/photopicker/res/values-lt/core_strings.xml
index 1846fb1fc..d88197bb9 100644
--- a/photopicker/res/values-lt/core_strings.xml
+++ b/photopicker/res/values-lt/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Nuotraukos ir vaizdo įrašai"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Medija"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Pasirinkta"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Pridėti <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"Atlikta"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Panaikinti visus pasirinkimus"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Pasirinkite iki <xliff:g id="COUNT">%1$s</xliff:g> elemen."</string>
diff --git a/photopicker/res/values-lt/feature_cloud_strings.xml b/photopicker/res/values-lt/feature_cloud_strings.xml
index 6fef8ab3d..f9b94ae3a 100644
--- a/photopicker/res/values-lt/feature_cloud_strings.xml
+++ b/photopicker/res/values-lt/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Paruošta: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> iš <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Nepavyko įkelti kai kurių nuotraukų"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Vėliau bandykite dar kartą. Nuotraukos bus pasiekiamos išsprendus problemą."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-lt/feature_preview_strings.xml b/photopicker/res/values-lt/feature_preview_strings.xml
index b5faba4e2..4083517b9 100644
--- a/photopicker/res/values-lt/feature_preview_strings.xml
+++ b/photopicker/res/values-lt/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Pasirinkti"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Panaikinti pasirinkimą"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Pasirinkti viską (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Atšaukti visų (<xliff:g id="COUNT">(%1$s)</xliff:g>) pasirinkimą"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Peržiūrėti"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Paleidžiant vaizdo įrašą kilo problema"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Patikrinkite interneto ryšį ir bandykite dar kartą"</string>
diff --git a/photopicker/res/values-lv/core_strings.xml b/photopicker/res/values-lv/core_strings.xml
index 02a67dfcb..5f9b1992c 100644
--- a/photopicker/res/values-lv/core_strings.xml
+++ b/photopicker/res/values-lv/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotoattēli un videoklipi"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Multivides vienums"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Atlasīts"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Pievienot <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Gatavs"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Noņemt visu atlasi"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Varat atlasīt ne vairāk kā <xliff:g id="COUNT">%1$s</xliff:g> vienumu(-us)."</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotoattēli"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumi"</string>
diff --git a/photopicker/res/values-lv/feature_cloud_strings.xml b/photopicker/res/values-lv/feature_cloud_strings.xml
index 85018a70c..3ddd281f2 100644
--- a/photopicker/res/values-lv/feature_cloud_strings.xml
+++ b/photopicker/res/values-lv/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Gatavs: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> no <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Nevar ielādēt dažus fotoattēlus"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Vēlāk mēģiniet vēlreiz. Fotoattēli būs pieejami, tiklīdz būs novērsta problēma."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-lv/feature_preview_strings.xml b/photopicker/res/values-lv/feature_preview_strings.xml
index 5d8b9df38..75aa71225 100644
--- a/photopicker/res/values-lv/feature_preview_strings.xml
+++ b/photopicker/res/values-lv/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Atlasīt"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Noņemt atlasi"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Atlasīt visu (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Noņemt visu atlasi (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Priekšskatīt"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Atskaņojot videoklipu, radās kļūda"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Pārbaudiet interneta savienojumu un mēģiniet vēlreiz."</string>
diff --git a/photopicker/res/values-mk/core_strings.xml b/photopicker/res/values-mk/core_strings.xml
index 70e1ebb9a..30f5126da 100644
--- a/photopicker/res/values-mk/core_strings.xml
+++ b/photopicker/res/values-mk/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Фотографии и видеа"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Аудиовизуелни содржини"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Избрано"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Додајте „<xliff:g id="COUNT">(%1$s)</xliff:g>“"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Готово"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Поништи го изборот на сите"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Изберете до <xliff:g id="COUNT">%1$s</xliff:g> ставки"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Фотографии"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Албуми"</string>
diff --git a/photopicker/res/values-mk/feature_cloud_strings.xml b/photopicker/res/values-mk/feature_cloud_strings.xml
index bf87905df..61e8a9a22 100644
--- a/photopicker/res/values-mk/feature_cloud_strings.xml
+++ b/photopicker/res/values-mk/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Подготвени: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> од <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Некои фотографии не може да се вчитаат"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Обидете се повторно подоцна. Вашите фотографии ќе бидат достапни откако ќе се реши проблемот."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-mk/feature_preview_strings.xml b/photopicker/res/values-mk/feature_preview_strings.xml
index 7239b35b1..b95554117 100644
--- a/photopicker/res/values-mk/feature_preview_strings.xml
+++ b/photopicker/res/values-mk/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Избери"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Поништи го изборот"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Избери ги сите <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Поништи го изборот на сите <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Прикажи"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Проблем со пуштањето на видеото"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Проверете ја интернет-врската и обидете се повторно"</string>
diff --git a/photopicker/res/values-ml/core_strings.xml b/photopicker/res/values-ml/core_strings.xml
index db05f3083..a83076510 100644
--- a/photopicker/res/values-ml/core_strings.xml
+++ b/photopicker/res/values-ml/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ഫോട്ടോകളും വീഡിയോകളും"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"മീഡിയ"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"തിരഞ്ഞെടുത്തു"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> ചേർക്കുക"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"പൂർത്തിയായി"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"തിരഞ്ഞെടുത്തത് എല്ലാം മാറ്റുക"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> ഇനങ്ങൾ വരെ തിരഞ്ഞെടുക്കുക"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"ഫോട്ടോകൾ"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"ആൽബങ്ങൾ"</string>
diff --git a/photopicker/res/values-ml/feature_cloud_strings.xml b/photopicker/res/values-ml/feature_cloud_strings.xml
index 613716057..72d73e059 100644
--- a/photopicker/res/values-ml/feature_cloud_strings.xml
+++ b/photopicker/res/values-ml/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>-ൽ <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> എണ്ണം തയ്യാറാണ്"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"ചില ഫോട്ടോകൾ ലോഡ് ചെയ്യാനാകുന്നില്ല"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"പിന്നീട് വീണ്ടും ശ്രമിക്കൂ. പ്രശ്‌നം പരിഹരിച്ച് കഴിഞ്ഞ് നിങ്ങളുടെ ഫോട്ടോകൾ ലഭ്യമാകും."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-ml/feature_preview_strings.xml b/photopicker/res/values-ml/feature_preview_strings.xml
index d26f94538..b530e7a1d 100644
--- a/photopicker/res/values-ml/feature_preview_strings.xml
+++ b/photopicker/res/values-ml/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"തിരഞ്ഞെടുക്കുക"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"തിരഞ്ഞെടുത്തത് മാറ്റുക"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"എല്ലാം തിരഞ്ഞെടുക്കുക (<xliff:g id="COUNT">(%1$s)</xliff:g> എണ്ണം)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"തിരഞ്ഞെടുത്തത് മാറ്റുക (<xliff:g id="COUNT">(%1$s)</xliff:g> എണ്ണം)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"പ്രിവ്യൂ ചെയ്യുക"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"വീഡിയോ പ്ലേ ചെയ്യുന്നതിൽ പ്രശ്നം"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"നിങ്ങളുടെ ഇന്റർനെറ്റ് കണക്ഷൻ പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക"</string>
diff --git a/photopicker/res/values-mn/core_strings.xml b/photopicker/res/values-mn/core_strings.xml
index d7dc5394e..e4a4ff4e8 100644
--- a/photopicker/res/values-mn/core_strings.xml
+++ b/photopicker/res/values-mn/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Зураг болон видео"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Медиа"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Сонгосон"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g>-г нэмэх"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Болсон"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Бүх сонголтыг цуцлах"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> хүртэлх зүйл сонгоно уу"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Зураг"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Цомог"</string>
diff --git a/photopicker/res/values-mn/feature_cloud_strings.xml b/photopicker/res/values-mn/feature_cloud_strings.xml
index f0d6e8208..09787c9ae 100644
--- a/photopicker/res/values-mn/feature_cloud_strings.xml
+++ b/photopicker/res/values-mn/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>-с <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> бэлэн байна"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Зарим зургийг ачаалах боломжгүй"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Дараа дахин оролдоно уу. Асуудлыг шийдвэрлэсний дараа таны зургууд боломжтой болно."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-mn/feature_preview_strings.xml b/photopicker/res/values-mn/feature_preview_strings.xml
index 82f45d286..b14cdde92 100644
--- a/photopicker/res/values-mn/feature_preview_strings.xml
+++ b/photopicker/res/values-mn/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Сонгох"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Сонголтыг цуцлах"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Бүх <xliff:g id="COUNT">(%1$s)</xliff:g> сонголтыг сонгох"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Бүх <xliff:g id="COUNT">(%1$s)</xliff:g> сонголтыг болиулах"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Урьдчилан үзэх"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Видеог тоглуулахад асуудал гарлаа"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Интернэт холболтоо шалгаад, дахин оролдоно уу"</string>
diff --git a/photopicker/res/values-mr/core_strings.xml b/photopicker/res/values-mr/core_strings.xml
index 31a8eba0b..d5c2cd4a8 100644
--- a/photopicker/res/values-mr/core_strings.xml
+++ b/photopicker/res/values-mr/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"फोटो आणि व्हिडिओ"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"मीडिया"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"निवडले आहे"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> जोडा"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"पूर्ण झाले"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"सर्व निवडी रद्द करा"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"कमाल <xliff:g id="COUNT">%1$s</xliff:g> आयटम निवडा"</string>
diff --git a/photopicker/res/values-mr/feature_cloud_strings.xml b/photopicker/res/values-mr/feature_cloud_strings.xml
index 8dd1ddb51..332c8502e 100644
--- a/photopicker/res/values-mr/feature_cloud_strings.xml
+++ b/photopicker/res/values-mr/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> पैकी <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> तयार"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"काही फोटो लोड करू शकत नाही"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"नंतर पुन्हा प्रयत्न करा. समस्येचे निराकरण झाल्यावर तुमचे फोटो उपलब्ध होतील."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-mr/feature_preview_strings.xml b/photopicker/res/values-mr/feature_preview_strings.xml
index 471e2bf25..6086d79ff 100644
--- a/photopicker/res/values-mr/feature_preview_strings.xml
+++ b/photopicker/res/values-mr/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"निवडा"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"निवड रद्द करा"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"सर्व निवडा <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"सर्व निवडी रद्द करा <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"पूर्वावलोकन करा"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"व्हिडिओ प्ले करण्यात समस्या आली"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"तुमचे इंटरनेट कनेक्शन तपासा आणि पुन्हा प्रयत्न करा"</string>
diff --git a/photopicker/res/values-ms/core_strings.xml b/photopicker/res/values-ms/core_strings.xml
index 6e8b1571e..d02bf4b80 100644
--- a/photopicker/res/values-ms/core_strings.xml
+++ b/photopicker/res/values-ms/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Foto &amp; video"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Dipilih"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Tambahkan <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"Selesai"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Nyahpilih semua"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Pilih hingga <xliff:g id="COUNT">%1$s</xliff:g> item"</string>
diff --git a/photopicker/res/values-ms/feature_cloud_strings.xml b/photopicker/res/values-ms/feature_cloud_strings.xml
index 704fb4ee1..5fc30ca5d 100644
--- a/photopicker/res/values-ms/feature_cloud_strings.xml
+++ b/photopicker/res/values-ms/feature_cloud_strings.xml
@@ -21,4 +21,12 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> daripada <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> sudah sedia"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Tidak dapat memuatkan beberapa Foto"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Cuba lagi nanti. Foto anda akan tersedia selepas masalah ini diselesaikan."</string>
+ <string name="photopicker_banner_cloud_media_available_title" msgid="3630564281302679941">"Foto yang disandarkan kini disertakan"</string>
+ <string name="photopicker_banner_cloud_media_available_message" msgid="6224134038092008505">"Anda boleh memilih foto daripada akaun <xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="USER_ACCOUNT">%2$s</xliff:g>"</string>
+ <string name="photopicker_banner_cloud_choose_account_title" msgid="286679907089224434">"Pilih akaun <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="photopicker_banner_cloud_choose_account_message" msgid="5173210485511611652">"Untuk menyertakan foto daripada <xliff:g id="APP_NAME">%1$s</xliff:g> di sini, pilih akaun dalam apl"</string>
+ <string name="photopicker_banner_cloud_choose_account_button" msgid="3407910358624445230">"Pilih akaun"</string>
+ <string name="photopicker_banner_cloud_choose_provider_title" msgid="992613053341538886">"Pilih apl media awan"</string>
+ <string name="photopicker_banner_cloud_choose_provider_message" msgid="1102889303996108506">"Untuk menyertakan foto yang disandarkan di sini, pilih apl media awan dalam Tetapan"</string>
+ <string name="photopicker_banner_cloud_choose_app_button" msgid="8228365266860220123">"Pilih apl"</string>
</resources>
diff --git a/photopicker/res/values-ms/feature_preview_strings.xml b/photopicker/res/values-ms/feature_preview_strings.xml
index 7e17ec862..272d0a33d 100644
--- a/photopicker/res/values-ms/feature_preview_strings.xml
+++ b/photopicker/res/values-ms/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Pilih"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Nyahpilih"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Pilih semua <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Nyahpilih semua <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Pratonton"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Berlaku masalah semasa memainkan video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Semak sambungan Internet anda, kemudian cuba lagi"</string>
diff --git a/photopicker/res/values-my/core_strings.xml b/photopicker/res/values-my/core_strings.xml
index 5f1644a6c..c104a84a9 100644
--- a/photopicker/res/values-my/core_strings.xml
+++ b/photopicker/res/values-my/core_strings.xml
@@ -20,19 +20,16 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ဓာတ်ပုံနှင့် ဗီဒီယိုများ"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"မီဒီယာ"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"ရွေးထားသည်"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> ပုံ ထည့်ရန်"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"ပြီးပြီ"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"အားလုံးကို ပြန်ဖြုတ်ရန်"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"ဖိုင် <xliff:g id="COUNT">%1$s</xliff:g> ဖိုင်အထိ ရွေးနိုင်သည်"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"ဓာတ်ပုံများ"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"အယ်လ်ဘမ်များ"</string>
<string name="photopicker_photos_empty_state_title" msgid="7018770515431149456">"ဓာတ်ပုံ မရှိသေးပါ"</string>
<string name="photopicker_photos_empty_state_body" msgid="5959729294856198675">"ဓာတ်ပုံနှင့် ဗီဒီယိုများကို စတင်ရိုက်ကူးနေသည်"</string>
<string name="photopicker_favorites_empty_state_title" msgid="3855048169943856242">"အကြိုက်ဆုံး မရှိသေးပါ"</string>
- <string name="photopicker_favorites_empty_state_body" msgid="3265404004750778533">"၎င်းသည် ကြယ်ပွင့်ပြထားသော (သို့) စိတ်ကြိုက်အဖြစ် မှတ်ထားသော ဖိုင်များကို စုစည်းသော အလိုအလျောက် အယ်လ်ဘမ်ဖြစ်သည်"</string>
- <string name="photopicker_videos_empty_state_title" msgid="159181717463348909">"မည်သည့် ဗီဒီယိုမှ မရှိသေးပါ"</string>
+ <string name="photopicker_favorites_empty_state_body" msgid="3265404004750778533">"၎င်းသည် ကြယ်ပွင့်ပြထားသော (သို့) စိတ်ကြိုက်အဖြစ် မှတ်ထားသော ဖိုင်များကို စုစည်းသည့် အလိုအလျောက် အယ်လ်ဘမ်ဖြစ်သည်"</string>
+ <string name="photopicker_videos_empty_state_title" msgid="159181717463348909">"မည်သည့် ဗီဒီယိုမျှ မရှိသေးပါ"</string>
<string name="photopicker_videos_empty_state_body" msgid="5149028843414577039">"၎င်းသည် သင့်ဗီဒီယိုအားလုံးကို စုစည်းသော အလိုအလျောက် အယ်လ်ဘမ်ဖြစ်သည်"</string>
<string name="photopicker_back_option" msgid="986374743479020214">"နောက်သို့"</string>
<string name="photopicker_dismiss_banner_button_label" msgid="2309894998787905178">"ပယ်ရန်"</string>
diff --git a/photopicker/res/values-my/feature_cloud_strings.xml b/photopicker/res/values-my/feature_cloud_strings.xml
index 34c93ce52..24ce838f8 100644
--- a/photopicker/res/values-my/feature_cloud_strings.xml
+++ b/photopicker/res/values-my/feature_cloud_strings.xml
@@ -21,4 +21,12 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> အနက် <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> အသင့်ဖြစ်ပြီ"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"ဓာတ်ပုံအချို့ကို ဖွင့်၍မရပါ"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"နောက်မှထပ်စမ်းပါ။ ပြဿနာကို ဖြေရှင်းပြီးသည့်အခါ သင့်ဓာတ်ပုံများကို ရနိုင်မည်။"</string>
+ <string name="photopicker_banner_cloud_media_available_title" msgid="3630564281302679941">"အရန်သိမ်းထားသော ဓာတ်ပုံများ ယခုထည့်သွင်းထားပြီ"</string>
+ <string name="photopicker_banner_cloud_media_available_message" msgid="6224134038092008505">"<xliff:g id="APP_NAME">%1$s</xliff:g> အကောင့် <xliff:g id="USER_ACCOUNT">%2$s</xliff:g> မှ ဓာတ်ပုံများ ရွေးနိုင်သည်"</string>
+ <string name="photopicker_banner_cloud_choose_account_title" msgid="286679907089224434">"<xliff:g id="APP_NAME">%1$s</xliff:g> အကောင့်ရွေးရန်"</string>
+ <string name="photopicker_banner_cloud_choose_account_message" msgid="5173210485511611652">"<xliff:g id="APP_NAME">%1$s</xliff:g> မှ ဓာတ်ပုံများကို ဤနေရာတွင်ထည့်သွင်းရန် အက်ပ်၌ အကောင့်ရွေးပါ"</string>
+ <string name="photopicker_banner_cloud_choose_account_button" msgid="3407910358624445230">"အကောင့်ရွေးရန်"</string>
+ <string name="photopicker_banner_cloud_choose_provider_title" msgid="992613053341538886">"Cloud မီဒီယာအက်ပ် ရွေးရန်"</string>
+ <string name="photopicker_banner_cloud_choose_provider_message" msgid="1102889303996108506">"အရန်သိမ်းထားသော ဓာတ်ပုံများ ဤနေရာတွင်ထည့်သွင်းရန် ဆက်တင်များ၌ cloud မီဒီယာအက်ပ် ရွေးပါ"</string>
+ <string name="photopicker_banner_cloud_choose_app_button" msgid="8228365266860220123">"အက်ပ်ရွေးရန်"</string>
</resources>
diff --git a/photopicker/res/values-my/feature_preview_strings.xml b/photopicker/res/values-my/feature_preview_strings.xml
index 1631d1ff8..9688543c5 100644
--- a/photopicker/res/values-my/feature_preview_strings.xml
+++ b/photopicker/res/values-my/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"ရွေးရန်"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"မရွေးတော့ရန်"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"<xliff:g id="COUNT">(%1$s)</xliff:g> ခုစလုံး ရွေးရန်"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"<xliff:g id="COUNT">(%1$s)</xliff:g> ခုစလုံး ပြန်ဖြုတ်ရန်"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"အစမ်းကြည့်ရှုရန်"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"ဗီဒီယိုဖွင့်ရာတွင် ပြဿနာရှိသည်"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"အင်တာနက်ချိတ်ဆက်မှုကို စစ်ဆေးပြီး ထပ်စမ်းကြည့်ပါ"</string>
diff --git a/photopicker/res/values-nb/core_strings.xml b/photopicker/res/values-nb/core_strings.xml
index a90375cff..8a8d774da 100644
--- a/photopicker/res/values-nb/core_strings.xml
+++ b/photopicker/res/values-nb/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Bilder og videoer"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Medieinnhold"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Valgt"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Legg til <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Ferdig"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Fjern alle valg"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Velg opptil <xliff:g id="COUNT">%1$s</xliff:g> elementer"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Bilder"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Album"</string>
diff --git a/photopicker/res/values-nb/feature_cloud_strings.xml b/photopicker/res/values-nb/feature_cloud_strings.xml
index 86abb75a5..564a38e31 100644
--- a/photopicker/res/values-nb/feature_cloud_strings.xml
+++ b/photopicker/res/values-nb/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> av <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> er klare"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Noen bilder kan ikke lastes inn"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Prøv på nytt senere. Bildene dine blir tilgjengelige når problemet er løst."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-nb/feature_preview_strings.xml b/photopicker/res/values-nb/feature_preview_strings.xml
index 7d253eeb7..33719f911 100644
--- a/photopicker/res/values-nb/feature_preview_strings.xml
+++ b/photopicker/res/values-nb/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Merk"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Fjern merkingen"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Merk av alle <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Fjern merkingen av alle <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Se forhåndsvisning"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Problem med avspilling av videoen"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Sjekk internettilkoblingen og prøv på nytt"</string>
diff --git a/photopicker/res/values-ne/core_strings.xml b/photopicker/res/values-ne/core_strings.xml
index 0ef8f57c4..a05e7f958 100644
--- a/photopicker/res/values-ne/core_strings.xml
+++ b/photopicker/res/values-ne/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"फोटो तथा भिडियोहरू"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"मिडिया"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"चयन गरिएको"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> हाल्नुहोस्"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"सम्पन्न भयो"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"सबैको चयन रद्द गर्नुहोस्"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"बढीमा <xliff:g id="COUNT">%1$s</xliff:g> वटा वस्तु चयन गर्नुहोस्"</string>
diff --git a/photopicker/res/values-ne/feature_cloud_strings.xml b/photopicker/res/values-ne/feature_cloud_strings.xml
index cf58d55ed..14e091827 100644
--- a/photopicker/res/values-ne/feature_cloud_strings.xml
+++ b/photopicker/res/values-ne/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> मध्ये <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> वटा फोटो तयार छन्"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"केही फोटोहरू लोड गर्न सकिएन"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"पछि फेरि प्रयास गर्नुहोस्। समस्या समाधान हुनेबित्तिकै तपाईंका फोटो उपलब्ध हुने छन्।"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-ne/feature_preview_strings.xml b/photopicker/res/values-ne/feature_preview_strings.xml
index e336f92c2..e624a9a46 100644
--- a/photopicker/res/values-ne/feature_preview_strings.xml
+++ b/photopicker/res/values-ne/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"चयन गर्नुहोस्"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"चयन रद्द गर्नुहोस्"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"सबै <xliff:g id="COUNT">(%1$s)</xliff:g> वटा चयन गर्नुहोस्"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"सबै <xliff:g id="COUNT">(%1$s)</xliff:g> वटाको चयन रद्द गर्नुहोस्"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"प्रिभ्यू गर्नुहोस्"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"भिडियो प्ले गर्दा समस्या भयो"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"इन्टरनेट कनेक्सन जाँच्नुहोस् र फेरि प्रयास गर्नुहोस्"</string>
diff --git a/photopicker/res/values-nl/core_strings.xml b/photopicker/res/values-nl/core_strings.xml
index 9ff040089..90def44ec 100644
--- a/photopicker/res/values-nl/core_strings.xml
+++ b/photopicker/res/values-nl/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Foto\'s en video\'s"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Geselecteerd"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> toevoegen"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"Klaar"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Alles deselecteren"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Selecteer maximaal <xliff:g id="COUNT">%1$s</xliff:g> items"</string>
diff --git a/photopicker/res/values-nl/feature_cloud_strings.xml b/photopicker/res/values-nl/feature_cloud_strings.xml
index 701fb6bb9..88a901fed 100644
--- a/photopicker/res/values-nl/feature_cloud_strings.xml
+++ b/photopicker/res/values-nl/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> van <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> klaar"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Kan bepaalde foto\'s niet laden"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Probeer het later opnieuw. Je foto\'s komen beschikbaar nadat het probleem is opgelost."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-nl/feature_preview_strings.xml b/photopicker/res/values-nl/feature_preview_strings.xml
index 39eb0937d..e4a735022 100644
--- a/photopicker/res/values-nl/feature_preview_strings.xml
+++ b/photopicker/res/values-nl/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Selecteren"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Deselecteren"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Alle <xliff:g id="COUNT">(%1$s)</xliff:g> selecteren"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Alle <xliff:g id="COUNT">(%1$s)</xliff:g> deselecteren"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Voorbeeld"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Probleem bij video afspelen"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Check de internetverbinding en probeer het opnieuw"</string>
diff --git a/photopicker/res/values-or/core_strings.xml b/photopicker/res/values-or/core_strings.xml
index f48fcf228..7a90f4889 100644
--- a/photopicker/res/values-or/core_strings.xml
+++ b/photopicker/res/values-or/core_strings.xml
@@ -20,18 +20,15 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ଫଟୋ ଓ ଭିଡିଓ"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"ମିଡିଆ"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"ଚୟନ କରାଯାଇଛି"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> ଯୋଗ କରନ୍ତୁ"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"ହୋଇଗଲା"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"ସମସ୍ତ ଅଚୟନ କରନ୍ତୁ"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g>ଟି ପର୍ଯ୍ୟନ୍ତ ଆଇଟମ ଚୟନ କରନ୍ତୁ"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"ଆଲବମଗୁଡ଼ିକ"</string>
<string name="photopicker_photos_empty_state_title" msgid="7018770515431149456">"ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଫଟୋ ନାହିଁ"</string>
<string name="photopicker_photos_empty_state_body" msgid="5959729294856198675">"ଫଟୋ ଏବଂ ଭିଡିଓଗୁଡ଼ିକୁ କେପଚର କରିବା ଆରମ୍ଭ କରନ୍ତୁ"</string>
<string name="photopicker_favorites_empty_state_title" msgid="3855048169943856242">"ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ପସନ୍ଦର ଆଲବମ ନାହିଁ"</string>
- <string name="photopicker_favorites_empty_state_body" msgid="3265404004750778533">"ଏହା ପସନ୍ଦର ଭାବେ ଷ୍ଟାରଯୁକ୍ତ କିମ୍ବା ଚିହିତ ଆଇଟମଗୁଡ଼ିକୁ ସଂଗ୍ରହ କରୁଥିବା ଏକ ସ୍ୱତଃ ଆଲବମ ଅଟେ"</string>
+ <string name="photopicker_favorites_empty_state_body" msgid="3265404004750778533">"ଏହା ପସନ୍ଦର ଭାବେ ଷ୍ଟାରଯୁକ୍ତ କିମ୍ବା ଚିହ୍ନିତ ଆଇଟମଗୁଡ଼ିକୁ ସଂଗ୍ରହ କରୁଥିବା ଏକ ସ୍ୱତଃ ଆଲବମ ଅଟେ"</string>
<string name="photopicker_videos_empty_state_title" msgid="159181717463348909">"ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଭିଡିଓ ନାହିଁ"</string>
<string name="photopicker_videos_empty_state_body" msgid="5149028843414577039">"ଏହା ଆପଣଙ୍କର ସମସ୍ତ ଭିଡିଓକୁ ସଂଗ୍ରହ କରୁଥିବା ଏକ ସ୍ୱତଃ ଆଲବମ ଅଟେ"</string>
<string name="photopicker_back_option" msgid="986374743479020214">"ପଛକୁ ଫେରନ୍ତୁ"</string>
diff --git a/photopicker/res/values-or/feature_cloud_strings.xml b/photopicker/res/values-or/feature_cloud_strings.xml
index a4a3607d2..edf357c0d 100644
--- a/photopicker/res/values-or/feature_cloud_strings.xml
+++ b/photopicker/res/values-or/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>ରୁ <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> ପ୍ରସ୍ତୁତ ଅଛି"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"କିଛି ଫଟୋ ଲୋଡ କରାଯାଇପାରିବ ନାହିଁ"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ। ସମସ୍ୟାର ସମାଧାନ ହେବା ପରେ ଆପଣଙ୍କ ଫଟୋଗୁଡ଼ିକ ଉପଲବ୍ଧ ହେବ।"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-or/feature_preview_strings.xml b/photopicker/res/values-or/feature_preview_strings.xml
index 9899e72b4..fe655f3bd 100644
--- a/photopicker/res/values-or/feature_preview_strings.xml
+++ b/photopicker/res/values-or/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"ଚୟନ କରନ୍ତୁ"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"ଅଚୟନ କରନ୍ତୁ"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"ସମସ୍ତ <xliff:g id="COUNT">(%1$s)</xliff:g>କୁ ଚୟନ କରନ୍ତୁ"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"ସମସ୍ତ <xliff:g id="COUNT">(%1$s)</xliff:g>କୁ ଅଚୟନ କରନ୍ତୁ"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"ପ୍ରିଭ୍ୟୁ"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"ଭିଡିଓ ପ୍ଲେ କରିବାରେ ସମସ୍ୟା"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"ଆପଣଙ୍କ ଇଣ୍ଟରନେଟ କନେକ୍ସନ ଯାଞ୍ଚ କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
diff --git a/photopicker/res/values-pa/core_strings.xml b/photopicker/res/values-pa/core_strings.xml
index 245f3d97c..61beef1e6 100644
--- a/photopicker/res/values-pa/core_strings.xml
+++ b/photopicker/res/values-pa/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ਫ਼ੋਟੋਆਂ ਅਤੇ ਵੀਡੀਓ"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"ਮੀਡੀਆ"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"ਚੁਣਿਆ ਗਿਆ"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> ਸ਼ਾਮਲ ਕਰੋ"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"ਹੋ ਗਿਆ"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"ਸਭ ਅਣ-ਚੁਣਿਆ ਕਰੋ"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> ਤੱਕ ਆਈਟਮਾਂ ਚੁਣੋ"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"ਐਲਬਮਾਂ"</string>
diff --git a/photopicker/res/values-pa/feature_cloud_strings.xml b/photopicker/res/values-pa/feature_cloud_strings.xml
index e2752bdf2..90f8244ee 100644
--- a/photopicker/res/values-pa/feature_cloud_strings.xml
+++ b/photopicker/res/values-pa/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> ਵਿੱਚੋਂ <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> ਤਿਆਰ ਹੈ"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"ਕੁਝ ਫ਼ੋਟੋਆਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ। ਸਮੱਸਿਆ ਹੱਲ ਹੋਣ ਤੋਂ ਬਾਅਦ, ਤੁਹਾਡੀਆਂ ਫ਼ੋਟੋਆਂ ਉਪਲਬਧ ਹੋ ਜਾਣਗੀਆਂ।"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-pa/feature_preview_strings.xml b/photopicker/res/values-pa/feature_preview_strings.xml
index 2246d5fe7..3cf2f09b3 100644
--- a/photopicker/res/values-pa/feature_preview_strings.xml
+++ b/photopicker/res/values-pa/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"ਚੁਣੋ"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"ਅਣ-ਚੁਣਿਆ ਕਰੋ"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"ਸਭ ਚੁਣੋ <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"ਸਭ ਅਣਚੁਣਿਆ ਕਰੋ <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"ਪੂਰਵ-ਝਲਕ ਦੇਖੋ"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"ਵੀਡੀਓ ਚਲਾਉਣ ਵਿੱਚ ਸਮੱਸਿਆ ਆ ਰਹੀ ਹੈ"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"ਆਪਣੇ ਇੰਟਰਨੈੱਟ ਕਨੈਕਸ਼ਨ ਦੀ ਜਾਂਚ ਕਰ ਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
diff --git a/photopicker/res/values-pl/core_strings.xml b/photopicker/res/values-pl/core_strings.xml
index 6e120b59c..8749df9d5 100644
--- a/photopicker/res/values-pl/core_strings.xml
+++ b/photopicker/res/values-pl/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Zdjęcia i filmy"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Multimedia"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Wybrano"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Dodaj <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Gotowe"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Odznacz wszystkie"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Maksymalna liczba elementów, które można wybrać: <xliff:g id="COUNT">%1$s</xliff:g>"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Zdjęcia"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumy"</string>
diff --git a/photopicker/res/values-pl/feature_cloud_strings.xml b/photopicker/res/values-pl/feature_cloud_strings.xml
index 522101900..ca6e94c29 100644
--- a/photopicker/res/values-pl/feature_cloud_strings.xml
+++ b/photopicker/res/values-pl/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Gotowe <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> z <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Nie udało się wczytać niektórych zdjęć"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Spróbuj ponownie później. Zdjęcia będą dostępne po rozwiązaniu problemu."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-pl/feature_preview_strings.xml b/photopicker/res/values-pl/feature_preview_strings.xml
index c7db685b7..8dd511a49 100644
--- a/photopicker/res/values-pl/feature_preview_strings.xml
+++ b/photopicker/res/values-pl/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Zaznacz"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Odznacz"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Zaznacz wszystkie <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Odznacz wszystkie <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Podgląd"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Wystąpiły problemy przy odtwarzaniu filmu"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Sprawdź połączenie z internetem i spróbuj ponownie"</string>
diff --git a/photopicker/res/values-pt-rBR/core_strings.xml b/photopicker/res/values-pt-rBR/core_strings.xml
index 6d0323f8f..3b77bd9ef 100644
--- a/photopicker/res/values-pt-rBR/core_strings.xml
+++ b/photopicker/res/values-pt-rBR/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotos e vídeos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Mídia"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Selecionado"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Adicionar <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Concluir"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Desmarcar tudo"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Selecione até <xliff:g id="COUNT">%1$s</xliff:g> itens"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Álbuns"</string>
diff --git a/photopicker/res/values-pt-rBR/feature_cloud_strings.xml b/photopicker/res/values-pt-rBR/feature_cloud_strings.xml
index c324be583..436af8c6b 100644
--- a/photopicker/res/values-pt-rBR/feature_cloud_strings.xml
+++ b/photopicker/res/values-pt-rBR/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> de <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> itens prontos"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Não é possível carregar algumas fotos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Tente de novo mais tarde. Suas fotos vão ficar disponíveis assim que o problema for resolvido."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-pt-rBR/feature_preview_strings.xml b/photopicker/res/values-pt-rBR/feature_preview_strings.xml
index 4a37c35e3..fa21b16b0 100644
--- a/photopicker/res/values-pt-rBR/feature_preview_strings.xml
+++ b/photopicker/res/values-pt-rBR/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Selecionar"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Desmarcar"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Selecionar tudo (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Desmarcar tudo (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Visualizar"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Ocorreu um problema ao iniciar o vídeo"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Confira sua conexão de Internet e tente de novo"</string>
diff --git a/photopicker/res/values-pt-rPT/core_strings.xml b/photopicker/res/values-pt-rPT/core_strings.xml
index c4798672d..9dd7d2de3 100644
--- a/photopicker/res/values-pt-rPT/core_strings.xml
+++ b/photopicker/res/values-pt-rPT/core_strings.xml
@@ -20,7 +20,6 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotos e vídeos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Multimédia"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Selecionado"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Adicionar <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_done_button_label" msgid="2641444126618862287">"Concluir"</string>
<string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Desmarcar tudo"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Selecione até <xliff:g id="COUNT">%1$s</xliff:g> itens"</string>
diff --git a/photopicker/res/values-pt-rPT/feature_cloud_strings.xml b/photopicker/res/values-pt-rPT/feature_cloud_strings.xml
index c9f1d65a7..ee54b876c 100644
--- a/photopicker/res/values-pt-rPT/feature_cloud_strings.xml
+++ b/photopicker/res/values-pt-rPT/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> de <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> item(ns) pronto(s)"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Não é possível carregar algumas fotos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Tente mais tarde. As suas fotos vão estar disponíveis quando o problema estiver resolvido."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-pt-rPT/feature_preview_strings.xml b/photopicker/res/values-pt-rPT/feature_preview_strings.xml
index 9e6d1983b..5c0f6cefb 100644
--- a/photopicker/res/values-pt-rPT/feature_preview_strings.xml
+++ b/photopicker/res/values-pt-rPT/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Selecionar"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Desmarcar"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Selecionar <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Desmarcar <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Pré-visualizar"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Dificuldades em reproduzir o vídeo"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Verifique a ligação à Internet e tente novamente"</string>
diff --git a/photopicker/res/values-pt/core_strings.xml b/photopicker/res/values-pt/core_strings.xml
index 6d0323f8f..3b77bd9ef 100644
--- a/photopicker/res/values-pt/core_strings.xml
+++ b/photopicker/res/values-pt/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotos e vídeos"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Mídia"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Selecionado"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Adicionar <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Concluir"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Desmarcar tudo"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Selecione até <xliff:g id="COUNT">%1$s</xliff:g> itens"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Álbuns"</string>
diff --git a/photopicker/res/values-pt/feature_cloud_strings.xml b/photopicker/res/values-pt/feature_cloud_strings.xml
index c324be583..436af8c6b 100644
--- a/photopicker/res/values-pt/feature_cloud_strings.xml
+++ b/photopicker/res/values-pt/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> de <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> itens prontos"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Não é possível carregar algumas fotos"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Tente de novo mais tarde. Suas fotos vão ficar disponíveis assim que o problema for resolvido."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-pt/feature_preview_strings.xml b/photopicker/res/values-pt/feature_preview_strings.xml
index 4a37c35e3..fa21b16b0 100644
--- a/photopicker/res/values-pt/feature_preview_strings.xml
+++ b/photopicker/res/values-pt/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Selecionar"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Desmarcar"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Selecionar tudo (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Desmarcar tudo (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Visualizar"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Ocorreu um problema ao iniciar o vídeo"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Confira sua conexão de Internet e tente de novo"</string>
diff --git a/photopicker/res/values-ro/core_strings.xml b/photopicker/res/values-ro/core_strings.xml
index 49f4b4ba5..cde7c6ff7 100644
--- a/photopicker/res/values-ro/core_strings.xml
+++ b/photopicker/res/values-ro/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotografii și videoclipuri"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Selectat"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Adaugă <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Gata"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Deselectează tot"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Selectează maximum <xliff:g id="COUNT">%1$s</xliff:g> elemente"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotografii"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albume"</string>
diff --git a/photopicker/res/values-ro/feature_cloud_strings.xml b/photopicker/res/values-ro/feature_cloud_strings.xml
index 4af50bcfb..5629f871e 100644
--- a/photopicker/res/values-ro/feature_cloud_strings.xml
+++ b/photopicker/res/values-ro/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Finalizate: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> din <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Unele fotografii nu pot fi încărcate"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Încearcă din nou mai târziu. Fotografiile tale vor fi disponibile după ce se rezolvă problema."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-ro/feature_preview_strings.xml b/photopicker/res/values-ro/feature_preview_strings.xml
index 035ded54e..6372f66ef 100644
--- a/photopicker/res/values-ro/feature_preview_strings.xml
+++ b/photopicker/res/values-ro/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Selectează"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Debifează"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Selectează-le pe toate cele <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Deselectează-le pe toate cele <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Previzualizează"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Probleme la redarea videoclipului"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Verifică-ți conexiunea la internet și încearcă din nou"</string>
diff --git a/photopicker/res/values-ru/core_strings.xml b/photopicker/res/values-ru/core_strings.xml
index 84c8ff365..bf5719f96 100644
--- a/photopicker/res/values-ru/core_strings.xml
+++ b/photopicker/res/values-ru/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Фото и видео"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Медиа"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Выбрано"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Добавить <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Готово"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Отменить выбор"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Выберите объекты (не более <xliff:g id="COUNT">%1$s</xliff:g>)."</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Фото"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Альбомы"</string>
diff --git a/photopicker/res/values-ru/feature_cloud_strings.xml b/photopicker/res/values-ru/feature_cloud_strings.xml
index 7df0d760b..cc9fae509 100644
--- a/photopicker/res/values-ru/feature_cloud_strings.xml
+++ b/photopicker/res/values-ru/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Предзагрузка: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> из <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>…"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Не удается загрузить некоторые фотографии"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Повторите попытку позже. Ваши фотографии станут доступны после устранения проблемы."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-ru/feature_preview_strings.xml b/photopicker/res/values-ru/feature_preview_strings.xml
index c30c12899..be21100d1 100644
--- a/photopicker/res/values-ru/feature_preview_strings.xml
+++ b/photopicker/res/values-ru/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Выбрать"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Отменить выбор"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Выбрать все <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Отменить выбор всех <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Посмотреть"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Не удалось воспроизвести видео"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Проверьте подключение к интернету и повторите попытку."</string>
diff --git a/photopicker/res/values-si/core_strings.xml b/photopicker/res/values-si/core_strings.xml
index 60120313f..3f0460696 100644
--- a/photopicker/res/values-si/core_strings.xml
+++ b/photopicker/res/values-si/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ඡායාරූප සහ වීඩියෝ"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"මාධ්‍ය"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"තේරිණි"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> එක් කරන්න"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"නිමයි"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"සියල්ල නොතෝරන්න"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"අයිතම <xliff:g id="COUNT">%1$s</xliff:g>ක් දක්වා තෝරන්න"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"ඡායාරූප"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"ඇල්බම"</string>
diff --git a/photopicker/res/values-si/feature_cloud_strings.xml b/photopicker/res/values-si/feature_cloud_strings.xml
index 2a3da29ce..a1fd882f7 100644
--- a/photopicker/res/values-si/feature_cloud_strings.xml
+++ b/photopicker/res/values-si/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>කින් <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g>ක් සූදානම්"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"සමහර ඡායාරූප පූරණය කළ නොහැක"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"පසුව නැවත උත්සාහ කරන්න. ගැටලුව විසඳූ පසු ඔබේ ඡායාරූප ලබා ගත හැකි වනු ඇත."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-si/feature_preview_strings.xml b/photopicker/res/values-si/feature_preview_strings.xml
index 053b8e33e..3669e3ea0 100644
--- a/photopicker/res/values-si/feature_preview_strings.xml
+++ b/photopicker/res/values-si/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"තෝරන්න"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"නොතෝරන්න"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"සියල්ල තෝරන්න <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"සියල්ල නොතෝරන්න <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"පෙරදසුන"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"වීඩියෝව වාදනය කිරීමේ ගැටලුවකි"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"ඔබේ අන්තර්ජාල සම්බන්ධතාව පරීක්ෂා කර නැවත උත්සහ කරන්න"</string>
diff --git a/photopicker/res/values-sk/core_strings.xml b/photopicker/res/values-sk/core_strings.xml
index e8ad54272..992f118f7 100644
--- a/photopicker/res/values-sk/core_strings.xml
+++ b/photopicker/res/values-sk/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotky a videá"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Médiá"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Vybrané"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Pridať <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Hotovo"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Zrušiť výber všetkého"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Vyberte maximálne <xliff:g id="COUNT">%1$s</xliff:g> položiek"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotky"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumy"</string>
diff --git a/photopicker/res/values-sk/feature_cloud_strings.xml b/photopicker/res/values-sk/feature_cloud_strings.xml
index 4381ab081..2fab8ed6b 100644
--- a/photopicker/res/values-sk/feature_cloud_strings.xml
+++ b/photopicker/res/values-sk/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Pripravené: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> z <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Niektoré fotky sa nedajú načítať"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Skúste to neskôr. Po vyriešení problému budú vaše fotky k dispozícii."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-sk/feature_preview_strings.xml b/photopicker/res/values-sk/feature_preview_strings.xml
index 11e602d7b..6f31a677b 100644
--- a/photopicker/res/values-sk/feature_preview_strings.xml
+++ b/photopicker/res/values-sk/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Vybrať"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Zrušiť výber"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Vybrať všetky položky (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Zrušiť výber všetkých položiek (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Zobraziť ukážku"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Ťažkosti s prehrávaním videa"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Skontrolujte internetové pripojenie a skúste to znova"</string>
diff --git a/photopicker/res/values-sl/core_strings.xml b/photopicker/res/values-sl/core_strings.xml
index e2a135f94..dc81f6ba9 100644
--- a/photopicker/res/values-sl/core_strings.xml
+++ b/photopicker/res/values-sl/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotografije in videoposnetki"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Predstavnost"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Izbrano"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Dodaj <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Končano"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Preklic celotnega izbora"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Izberite največ toliko elementov: <xliff:g id="COUNT">%1$s</xliff:g>"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotografije"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumi"</string>
diff --git a/photopicker/res/values-sl/feature_cloud_strings.xml b/photopicker/res/values-sl/feature_cloud_strings.xml
index a878cc76e..b214e39fa 100644
--- a/photopicker/res/values-sl/feature_cloud_strings.xml
+++ b/photopicker/res/values-sl/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Pripravljenih: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> od <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Nekaterih fotografij ni mogoče naložiti"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Poskusite znova pozneje. Fotografije bodo na voljo, ko bo težava odpravljena."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-sl/feature_preview_strings.xml b/photopicker/res/values-sl/feature_preview_strings.xml
index a4fcfd627..4e3a54388 100644
--- a/photopicker/res/values-sl/feature_preview_strings.xml
+++ b/photopicker/res/values-sl/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Izberi"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Počisti izbiro"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Izberi vse (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Odznači vse (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Predogled"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Težave pri predvajanju videoposnetka"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Preverite internetno povezavo in poskusite znova"</string>
diff --git a/photopicker/res/values-sq/core_strings.xml b/photopicker/res/values-sq/core_strings.xml
index ae418f0ad..97f39a163 100644
--- a/photopicker/res/values-sq/core_strings.xml
+++ b/photopicker/res/values-sq/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotografitë dhe videot"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Zgjedhur"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Shto \"<xliff:g id="COUNT">(%1$s)</xliff:g>\""</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"U krye"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Anulo zgjedhjen për të gjitha"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Zgjidh deri në <xliff:g id="COUNT">%1$s</xliff:g> artikuj"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotografitë"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albumet"</string>
diff --git a/photopicker/res/values-sq/feature_cloud_strings.xml b/photopicker/res/values-sq/feature_cloud_strings.xml
index ba153def8..fcf3e0330 100644
--- a/photopicker/res/values-sq/feature_cloud_strings.xml
+++ b/photopicker/res/values-sq/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> nga <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> gati"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Disa fotografi nuk mund të ngarkohen"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Provo përsëri më vonë. Fotografitë e tua do të ofrohen pasi të zgjidhet problemi."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-sq/feature_preview_strings.xml b/photopicker/res/values-sq/feature_preview_strings.xml
index ab12ee1ae..d452f9dff 100644
--- a/photopicker/res/values-sq/feature_preview_strings.xml
+++ b/photopicker/res/values-sq/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Zgjidh"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Hiq përzgjedhjen"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Zgjidh të gjitha (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Anulo zgjedhjen për të gjitha (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Shiko paraprakisht"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Problem me luajtjen e videos"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Kontrollo lidhjen e internetit dhe provo përsëri"</string>
diff --git a/photopicker/res/values-sr/core_strings.xml b/photopicker/res/values-sr/core_strings.xml
index e2e30f0a6..bf88b436c 100644
--- a/photopicker/res/values-sr/core_strings.xml
+++ b/photopicker/res/values-sr/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Слике и видеи"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Медији"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Изабрано"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Додај <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Готово"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Поништите све"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Највећи број ставки које можете да изаберете је <xliff:g id="COUNT">%1$s</xliff:g>"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Слике"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Албуми"</string>
diff --git a/photopicker/res/values-sr/feature_cloud_strings.xml b/photopicker/res/values-sr/feature_cloud_strings.xml
index 8acf3a161..4a25d1532 100644
--- a/photopicker/res/values-sr/feature_cloud_strings.xml
+++ b/photopicker/res/values-sr/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Спремно:<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> од <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Учитавање неких слика није успело"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Пробајте поново касније. Слике ће бити доступне када се проблем реши."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-sr/feature_preview_strings.xml b/photopicker/res/values-sr/feature_preview_strings.xml
index 4b32f2a43..178f9c484 100644
--- a/photopicker/res/values-sr/feature_preview_strings.xml
+++ b/photopicker/res/values-sr/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Изабери"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Опозови избор"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Изабери све <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Поништи избор <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Преглед"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Дошло је до грешке при пуштању видеа"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Проверите интернет везу и пробајте поново"</string>
diff --git a/photopicker/res/values-sv/core_strings.xml b/photopicker/res/values-sv/core_strings.xml
index ca3155cac..b12f80bb6 100644
--- a/photopicker/res/values-sv/core_strings.xml
+++ b/photopicker/res/values-sv/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Foton och videor"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Markerat"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Lägg till <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Klar"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Avmarkera alla"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Välj upp till <xliff:g id="COUNT">%1$s</xliff:g> objekt"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Foto"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Album"</string>
diff --git a/photopicker/res/values-sv/feature_cloud_strings.xml b/photopicker/res/values-sv/feature_cloud_strings.xml
index e232c7fa1..51ab10f45 100644
--- a/photopicker/res/values-sv/feature_cloud_strings.xml
+++ b/photopicker/res/values-sv/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> av <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> är redo"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Det gick inte att läsa in vissa foton"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Försök igen senare. Dina foton blir tillgängliga när problemet har lösts."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-sv/feature_preview_strings.xml b/photopicker/res/values-sv/feature_preview_strings.xml
index e9efd1145..ef502e520 100644
--- a/photopicker/res/values-sv/feature_preview_strings.xml
+++ b/photopicker/res/values-sv/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Markera"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Avmarkera"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Markera alla <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Avmarkera alla <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Förhandsgranska"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Det gick inte att spela upp videon"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Kontrollera internetanslutningen och försök igen"</string>
diff --git a/photopicker/res/values-sw/core_strings.xml b/photopicker/res/values-sw/core_strings.xml
index 129ff7e8a..e1c1b951d 100644
--- a/photopicker/res/values-sw/core_strings.xml
+++ b/photopicker/res/values-sw/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Picha na video"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Maudhui"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Imechaguliwa"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Weka <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Nimemaliza"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Acha kuchagua zote"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Chagua hadi vipengee <xliff:g id="COUNT">%1$s</xliff:g>"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Picha"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albamu"</string>
diff --git a/photopicker/res/values-sw/feature_cloud_strings.xml b/photopicker/res/values-sw/feature_cloud_strings.xml
index 25c7bba58..c2f99bcfc 100644
--- a/photopicker/res/values-sw/feature_cloud_strings.xml
+++ b/photopicker/res/values-sw/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> kati ya <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> ziko tayari"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Imeshindwa kupakia baadhi ya Picha"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Jaribu tena baadaye. Picha zako zitapatikana tatizo hilo likitatuliwa."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-sw/feature_preview_strings.xml b/photopicker/res/values-sw/feature_preview_strings.xml
index 7529edcfa..54cb31036 100644
--- a/photopicker/res/values-sw/feature_preview_strings.xml
+++ b/photopicker/res/values-sw/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Chagua"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Acha kuchagua"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Chagua zote <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Acha kuchagua zote <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Kagua kwanza"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Tatizo limetokea wakati wa kucheza video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Angalia muunganisho wako wa intaneti kisha ujaribu tena"</string>
diff --git a/photopicker/res/values-ta/core_strings.xml b/photopicker/res/values-ta/core_strings.xml
index b1e7b8d05..d82993300 100644
--- a/photopicker/res/values-ta/core_strings.xml
+++ b/photopicker/res/values-ta/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"படங்கள் &amp; வீடியோக்கள்"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"மீடியா"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"தேர்ந்தெடுக்கப்பட்டது"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g>ஐச் சேர்"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"முடிந்தது"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"அனைத்தையும் தேர்வு நீக்கும்"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> ஆவணங்கள் வரை தேர்ந்தெடுங்கள்"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"ஆல்பங்கள்"</string>
diff --git a/photopicker/res/values-ta/feature_cloud_strings.xml b/photopicker/res/values-ta/feature_cloud_strings.xml
index fe32c0af1..34293bdcd 100644
--- a/photopicker/res/values-ta/feature_cloud_strings.xml
+++ b/photopicker/res/values-ta/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> / <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> தயாராக உள்ளது"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"சில படங்களை ஏற்ற முடியவில்லை"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"பிறகு மீண்டும் முயலவும். சிக்கல் சரிசெய்யப்பட்டதும் உங்கள் படங்கள் கிடைக்கும்."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-ta/feature_preview_strings.xml b/photopicker/res/values-ta/feature_preview_strings.xml
index 69527a4c7..2bb483f7c 100644
--- a/photopicker/res/values-ta/feature_preview_strings.xml
+++ b/photopicker/res/values-ta/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"தேர்ந்தெடு"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"தேர்வு நீக்கு"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"<xliff:g id="COUNT">(%1$s)</xliff:g>ஐயும் தேர்ந்தெடு"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"<xliff:g id="COUNT">(%1$s)</xliff:g>ஐயும் தேர்வுநீக்கு"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"மாதிரிக்காட்சி"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"வீடியோவைப் பிளே செய்வதில் சிக்கல்"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"இணைய இணைப்பைச் சரிபார்த்து மீண்டும் முயலவும்"</string>
diff --git a/photopicker/res/values-te/core_strings.xml b/photopicker/res/values-te/core_strings.xml
index 51e458c54..34fd3de00 100644
--- a/photopicker/res/values-te/core_strings.xml
+++ b/photopicker/res/values-te/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"ఫోటోలు &amp; వీడియోలు"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"మీడియా"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"ఎంచుకోబడింది"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g>ను జోడించండి"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"పూర్తయింది"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"అన్నింటి ఎంపికను తొలగించండి"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"గరిష్ఠంగా <xliff:g id="COUNT">%1$s</xliff:g> ఐటెమ్‌లను ఎంచుకోండి"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"ఫోటోలు"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"ఆల్బమ్‌లు"</string>
diff --git a/photopicker/res/values-te/feature_cloud_strings.xml b/photopicker/res/values-te/feature_cloud_strings.xml
index ed2695662..665ee5861 100644
--- a/photopicker/res/values-te/feature_cloud_strings.xml
+++ b/photopicker/res/values-te/feature_cloud_strings.xml
@@ -21,4 +21,12 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>లో <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> సిద్ధంగా ఉన్నాయి"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"కొన్ని ఫోటోలను లోడ్ చేయడం సాధ్యపడదు"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"తర్వాత మళ్లీ ట్రై చేయండి. సమస్య పరిష్కరించబడిన తర్వాత మీ ఫోటోలు అందుబాటులో ఉంటాయి."</string>
+ <string name="photopicker_banner_cloud_media_available_title" msgid="3630564281302679941">"బ్యాకప్ చేసిన ఫోటోలు ఇప్పుడు జోడించబడ్డాయి"</string>
+ <string name="photopicker_banner_cloud_media_available_message" msgid="6224134038092008505">"మీరు <xliff:g id="APP_NAME">%1$s</xliff:g> ఖాతా <xliff:g id="USER_ACCOUNT">%2$s</xliff:g> నుండి ఫోటోలను ఎంచుకోవచ్చు"</string>
+ <string name="photopicker_banner_cloud_choose_account_title" msgid="286679907089224434">"<xliff:g id="APP_NAME">%1$s</xliff:g> ఖాతాను ఎంచుకోండి"</string>
+ <string name="photopicker_banner_cloud_choose_account_message" msgid="5173210485511611652">"<xliff:g id="APP_NAME">%1$s</xliff:g> నుండి ఫోటోలను ఇక్కడ జోడించడానికి, యాప్‌లో ఖాతాను ఎంచుకోండి"</string>
+ <string name="photopicker_banner_cloud_choose_account_button" msgid="3407910358624445230">"ఖాతాను ఎంచుకోండి"</string>
+ <string name="photopicker_banner_cloud_choose_provider_title" msgid="992613053341538886">"క్లౌడ్ మీడియా యాప్‌ను ఎంచుకోండి"</string>
+ <string name="photopicker_banner_cloud_choose_provider_message" msgid="1102889303996108506">"బ్యాకప్ చేసిన ఫోటోలను ఇక్కడజోడించడానికి, సెట్టింగ్‌లలో క్లౌడ్ మీడియా యాప్‌ను ఎంచుకోండి"</string>
+ <string name="photopicker_banner_cloud_choose_app_button" msgid="8228365266860220123">"యాప్‌ను ఎంచుకోండి"</string>
</resources>
diff --git a/photopicker/res/values-te/feature_preview_strings.xml b/photopicker/res/values-te/feature_preview_strings.xml
index 274aa56f5..12610c193 100644
--- a/photopicker/res/values-te/feature_preview_strings.xml
+++ b/photopicker/res/values-te/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"ఎంచుకోండి"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"ఎంపికను తొలగించండి"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"మొత్తం <xliff:g id="COUNT">(%1$s)</xliff:g>‌ను ఎంచుకోండి"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"మొత్తం <xliff:g id="COUNT">(%1$s)</xliff:g> ఎంపిక రద్దు చేయండి"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"ప్రివ్యూ చూడండి"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"వీడియోను ప్లే చేయడంలో సమస్య ఏర్పడింది"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"మీ ఇంటర్నెట్ కనెక్షన్‌ను చెక్ చేసి, మళ్లీ ట్రై చేయండి"</string>
diff --git a/photopicker/res/values-th/core_strings.xml b/photopicker/res/values-th/core_strings.xml
index 43b0c54ed..954db2fc5 100644
--- a/photopicker/res/values-th/core_strings.xml
+++ b/photopicker/res/values-th/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"รูปภาพและวิดีโอ"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"สื่อ"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"เลือกแล้ว"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"เพิ่ม <xliff:g id="COUNT">(%1$s)</xliff:g> รายการ"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"เสร็จสิ้น"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"ยกเลิกการเลือกทั้งหมด"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"เลือกได้สูงสุด <xliff:g id="COUNT">%1$s</xliff:g> รายการ"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"รูปภาพ"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"อัลบั้ม"</string>
diff --git a/photopicker/res/values-th/feature_cloud_strings.xml b/photopicker/res/values-th/feature_cloud_strings.xml
index 98a8c98d8..4e180d437 100644
--- a/photopicker/res/values-th/feature_cloud_strings.xml
+++ b/photopicker/res/values-th/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"พร้อมแล้ว <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> จาก <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> รายการ"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"โหลดรูปภาพบางรูปไม่ได้"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"โปรดลองอีกครั้งในภายหลัง รูปภาพจะพร้อมใช้งานเมื่อปัญหาได้รับการแก้ไขแล้ว"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-th/feature_preview_strings.xml b/photopicker/res/values-th/feature_preview_strings.xml
index 5ac9b2aec..f34bf4e27 100644
--- a/photopicker/res/values-th/feature_preview_strings.xml
+++ b/photopicker/res/values-th/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"เลือก"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"ยกเลิกการเลือก"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"เลือกทั้งหมด <xliff:g id="COUNT">(%1$s)</xliff:g> รายการ"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"ยกเลิกการเลือกทั้งหมด <xliff:g id="COUNT">(%1$s)</xliff:g> รายการ"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"ตัวอย่าง"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"เกิดปัญหาขณะเล่นวิดีโอ"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ตแล้วลองอีกครั้ง"</string>
diff --git a/photopicker/res/values-tl/core_strings.xml b/photopicker/res/values-tl/core_strings.xml
index 7fddf2728..8331d5c72 100644
--- a/photopicker/res/values-tl/core_strings.xml
+++ b/photopicker/res/values-tl/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Mga larawan at video"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Napili"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Idagdag ang <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Tapos na"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"I-deselect lahat"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Pumili ng hanggang <xliff:g id="COUNT">%1$s</xliff:g> (na) item"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Mga Album"</string>
diff --git a/photopicker/res/values-tl/feature_cloud_strings.xml b/photopicker/res/values-tl/feature_cloud_strings.xml
index 1ac7661be..9126c7072 100644
--- a/photopicker/res/values-tl/feature_cloud_strings.xml
+++ b/photopicker/res/values-tl/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Handa na ang <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> sa <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Hindi ma-load ang ilang Larawan"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Subukan ulit sa ibang pagkakataon. Magiging available ang iyong mga larawan kapag nalutas na ang isyu."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-tl/feature_preview_strings.xml b/photopicker/res/values-tl/feature_preview_strings.xml
index 7727d45d6..b742d610a 100644
--- a/photopicker/res/values-tl/feature_preview_strings.xml
+++ b/photopicker/res/values-tl/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Piliin"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"I-deselect"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Piliin lahat <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"I-unselect lahat <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"I-preview"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Nagkaproblema sa pag-play ng video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Suriin ang iyong koneksyon sa internet at subukan ulit"</string>
diff --git a/photopicker/res/values-tr/core_strings.xml b/photopicker/res/values-tr/core_strings.xml
index aa8f17d9c..3fe96cb06 100644
--- a/photopicker/res/values-tr/core_strings.xml
+++ b/photopicker/res/values-tr/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Fotoğraflar ve videolar"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Medya"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Seçili"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> tane ekle"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Bitti"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Tümünün seçimini kaldır"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"En fazla <xliff:g id="COUNT">%1$s</xliff:g> öğe seçin"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Fotoğraflar"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albümler"</string>
diff --git a/photopicker/res/values-tr/feature_cloud_strings.xml b/photopicker/res/values-tr/feature_cloud_strings.xml
index 9c2ea1487..7a42fb19e 100644
--- a/photopicker/res/values-tr/feature_cloud_strings.xml
+++ b/photopicker/res/values-tr/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> adetten <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> adedi hazır"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Bazı fotoğraflar yüklenemiyor"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Daha sonra tekrar deneyin. Fotoğraflarınız, sorun çözüldükten sonra kullanılabilir."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-tr/feature_preview_strings.xml b/photopicker/res/values-tr/feature_preview_strings.xml
index 5907c25af..24f094c55 100644
--- a/photopicker/res/values-tr/feature_preview_strings.xml
+++ b/photopicker/res/values-tr/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Seç"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Seçimi kaldır"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Tümünü seç: <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Tümünün seçimini kaldır: <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Önizle"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Video oynatılırken sorun oluştu"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"İnternet bağlantınızı kontrol edip tekrar deneyin"</string>
diff --git a/photopicker/res/values-uk/core_strings.xml b/photopicker/res/values-uk/core_strings.xml
index 784adf942..051142ce7 100644
--- a/photopicker/res/values-uk/core_strings.xml
+++ b/photopicker/res/values-uk/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Фото й відео"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Медіа"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Вибрано"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Додати <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Готово"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Не вибирати нічого"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Виберіть не більше стількох об’єктів: <xliff:g id="COUNT">%1$s</xliff:g>"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Фото"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Альбоми"</string>
diff --git a/photopicker/res/values-uk/feature_cloud_strings.xml b/photopicker/res/values-uk/feature_cloud_strings.xml
index 80b8c36e5..44db5caf0 100644
--- a/photopicker/res/values-uk/feature_cloud_strings.xml
+++ b/photopicker/res/values-uk/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Готово: <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> з <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Не вдається завантажити деякі фотографії"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Спробуйте пізніше. Ваші фотографії стануть доступними, коли проблему буде вирішено."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-uk/feature_preview_strings.xml b/photopicker/res/values-uk/feature_preview_strings.xml
index 450d4dae7..5b607f9e7 100644
--- a/photopicker/res/values-uk/feature_preview_strings.xml
+++ b/photopicker/res/values-uk/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Вибрати"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Не вибирати"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Вибрати всі <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Скасувати вибір усіх <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Переглянути"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Проблема з відтворенням відео"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Перевірте інтернет-з’єднання й повторіть спробу"</string>
diff --git a/photopicker/res/values-ur/core_strings.xml b/photopicker/res/values-ur/core_strings.xml
index be14b82ba..00f84af70 100644
--- a/photopicker/res/values-ur/core_strings.xml
+++ b/photopicker/res/values-ur/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"تصاویر اور ویڈیوز"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"میڈیا"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"منتخب کردہ"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> کو شامل کریں"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"ہو گیا"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"سبھی کو غیر منتخب کریں"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> آئٹمز تک منتخب کریں"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"تصاویر"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"البمز"</string>
diff --git a/photopicker/res/values-ur/feature_cloud_strings.xml b/photopicker/res/values-ur/feature_cloud_strings.xml
index 6bfe9ce03..a970d9860 100644
--- a/photopicker/res/values-ur/feature_cloud_strings.xml
+++ b/photopicker/res/values-ur/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> میں سے <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> تیار ہیں"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"کچھ تصاویر لوڈ نہیں کی جا سکتیں"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"بعد میں دوبارہ کوشش کریں۔ مسئلہ حل ہو جانے کے بعد آپ کی تصاویر دستیاب ہوں گی۔"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-ur/feature_preview_strings.xml b/photopicker/res/values-ur/feature_preview_strings.xml
index bd171e5d7..cb5cb5535 100644
--- a/photopicker/res/values-ur/feature_preview_strings.xml
+++ b/photopicker/res/values-ur/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"منتخب کریں"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"غیر منتخب کریں"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"سبھی <xliff:g id="COUNT">(%1$s)</xliff:g> کو منتخب کریں"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"سبھی <xliff:g id="COUNT">(%1$s)</xliff:g> کو غیر منتخب کریں"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"پیش منظر دیکھیں"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"ویڈیو چلانے میں دشواری"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"اپنا انٹرنیٹ کنکشن چیک کریں اور دوبارہ کوشش کریں"</string>
diff --git a/photopicker/res/values-uz/core_strings.xml b/photopicker/res/values-uz/core_strings.xml
index f117db51f..39f4bdbc2 100644
--- a/photopicker/res/values-uz/core_strings.xml
+++ b/photopicker/res/values-uz/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Suratlar va videolar"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Media"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Tanlangan"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"<xliff:g id="COUNT">(%1$s)</xliff:g> ta qoʻshish"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Tayyor"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Hammasini bekor qilish"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"<xliff:g id="COUNT">%1$s</xliff:g> tagacha elementni tanlang"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Photos"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Albomlar"</string>
diff --git a/photopicker/res/values-uz/feature_cloud_strings.xml b/photopicker/res/values-uz/feature_cloud_strings.xml
index 95cadac6e..008b54df7 100644
--- a/photopicker/res/values-uz/feature_cloud_strings.xml
+++ b/photopicker/res/values-uz/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g>/<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> tayyor"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Ayrim suratlar yuklanmadi"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Keyinroq qayta urining. Suratlaringiz muammo hal boʻlgandan keyin chiqadi."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-uz/feature_preview_strings.xml b/photopicker/res/values-uz/feature_preview_strings.xml
index 36c0701ad..cd00be202 100644
--- a/photopicker/res/values-uz/feature_preview_strings.xml
+++ b/photopicker/res/values-uz/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Tanlash"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Tanlovni bekor qilish"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Hammasini tanlash (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Hammasini bekor qilish (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Razm solish"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Video ijrosida muammo"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Internet aloqasini tekshiring va qayta urining"</string>
diff --git a/photopicker/res/values-vi/core_strings.xml b/photopicker/res/values-vi/core_strings.xml
index 4f6091aa9..f3268f183 100644
--- a/photopicker/res/values-vi/core_strings.xml
+++ b/photopicker/res/values-vi/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Ảnh và video"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Nội dung nghe nhìn"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Đã chọn"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Thêm <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Xong"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Bỏ chọn tất cả"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Chọn tối đa <xliff:g id="COUNT">%1$s</xliff:g> mục"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Ảnh"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Album"</string>
diff --git a/photopicker/res/values-vi/feature_cloud_strings.xml b/photopicker/res/values-vi/feature_cloud_strings.xml
index 7b45f0818..a158512d2 100644
--- a/photopicker/res/values-vi/feature_cloud_strings.xml
+++ b/photopicker/res/values-vi/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"Đã sẵn sàng <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g>/<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g>"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Không tải được một số ảnh"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Hãy thử lại sau. Ảnh của bạn sẽ xuất hiện sau khi vấn đề được giải quyết."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-vi/feature_preview_strings.xml b/photopicker/res/values-vi/feature_preview_strings.xml
index 89ad8690c..0001cda4e 100644
--- a/photopicker/res/values-vi/feature_preview_strings.xml
+++ b/photopicker/res/values-vi/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Chọn"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Bỏ chọn"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Chọn tất cả <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Bỏ chọn tất cả <xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Xem trước"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Sự cố khi phát video"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Hãy kiểm tra kết nối Internet rồi thử lại"</string>
diff --git a/photopicker/res/values-zh-rCN/core_strings.xml b/photopicker/res/values-zh-rCN/core_strings.xml
index a9a0dbe9e..00092d798 100644
--- a/photopicker/res/values-zh-rCN/core_strings.xml
+++ b/photopicker/res/values-zh-rCN/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"照片和视频"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"媒体"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"已选择"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"添加了 <xliff:g id="COUNT">(%1$s)</xliff:g> 张"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"完成"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"取消全选"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"最多选择 <xliff:g id="COUNT">%1$s</xliff:g> 项"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"照片"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"相册"</string>
diff --git a/photopicker/res/values-zh-rCN/feature_cloud_strings.xml b/photopicker/res/values-zh-rCN/feature_cloud_strings.xml
index 65f1de757..7e9d4ed9f 100644
--- a/photopicker/res/values-zh-rCN/feature_cloud_strings.xml
+++ b/photopicker/res/values-zh-rCN/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> 个已准备就绪,共 <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> 个"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"部分照片无法加载"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"请稍后再试。问题解决后,您的照片就可用了。"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-zh-rCN/feature_preview_strings.xml b/photopicker/res/values-zh-rCN/feature_preview_strings.xml
index 1eb1dab07..1fe703281 100644
--- a/photopicker/res/values-zh-rCN/feature_preview_strings.xml
+++ b/photopicker/res/values-zh-rCN/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"选择"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"取消选择"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"全选 (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"取消全选 (<xliff:g id="COUNT">(%1$s)</xliff:g>)"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"预览"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"播放视频时遇到问题"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"请检查互联网连接,然后重试"</string>
diff --git a/photopicker/res/values-zh-rHK/core_strings.xml b/photopicker/res/values-zh-rHK/core_strings.xml
index dd53edac9..283e170b9 100644
--- a/photopicker/res/values-zh-rHK/core_strings.xml
+++ b/photopicker/res/values-zh-rHK/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"相片和影片"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"媒體"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"揀咗"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"新增 <xliff:g id="COUNT">(%1$s)</xliff:g> 張相片"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"完成"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"全部取消揀"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"選取最多 <xliff:g id="COUNT">%1$s</xliff:g> 個項目"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"相片"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"相簿"</string>
diff --git a/photopicker/res/values-zh-rHK/feature_cloud_strings.xml b/photopicker/res/values-zh-rHK/feature_cloud_strings.xml
index 6c01305bc..98413f8eb 100644
--- a/photopicker/res/values-zh-rHK/feature_cloud_strings.xml
+++ b/photopicker/res/values-zh-rHK/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> 個項目已就緒,共 <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> 個"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"無法載入部分相片"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"請稍後再試。相片會在問題解決後顯示。"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-zh-rHK/feature_preview_strings.xml b/photopicker/res/values-zh-rHK/feature_preview_strings.xml
index 25f364169..90d30335a 100644
--- a/photopicker/res/values-zh-rHK/feature_preview_strings.xml
+++ b/photopicker/res/values-zh-rHK/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"選取"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"取消選取"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"選取全部共 <xliff:g id="COUNT">(%1$s)</xliff:g> 個"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"取消選取全部共 <xliff:g id="COUNT">(%1$s)</xliff:g> 個"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"預覽"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"播放影片時發生問題"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"請檢查你的互聯網連線,然後再試一次"</string>
diff --git a/photopicker/res/values-zh-rTW/core_strings.xml b/photopicker/res/values-zh-rTW/core_strings.xml
index 41863190f..66b6a1ed7 100644
--- a/photopicker/res/values-zh-rTW/core_strings.xml
+++ b/photopicker/res/values-zh-rTW/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"相片和影片"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"媒體"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"已選取"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"新增 <xliff:g id="COUNT">(%1$s)</xliff:g> 張相片"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"完成"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"取消全選"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"最多可選取 <xliff:g id="COUNT">%1$s</xliff:g> 個項目"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"相片"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"相簿"</string>
diff --git a/photopicker/res/values-zh-rTW/feature_cloud_strings.xml b/photopicker/res/values-zh-rTW/feature_cloud_strings.xml
index dc411809b..2253df722 100644
--- a/photopicker/res/values-zh-rTW/feature_cloud_strings.xml
+++ b/photopicker/res/values-zh-rTW/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"已備妥 <xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> 個項目,共 <xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> 個項目"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"無法載入部分相片"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"請稍後再試。問題解決後即可存取相片。"</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-zh-rTW/feature_preview_strings.xml b/photopicker/res/values-zh-rTW/feature_preview_strings.xml
index 98f089b1e..3b2894388 100644
--- a/photopicker/res/values-zh-rTW/feature_preview_strings.xml
+++ b/photopicker/res/values-zh-rTW/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"選取"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"取消選取"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"選取全部 <xliff:g id="COUNT">(%1$s)</xliff:g> 個項目"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"取消選取全部 <xliff:g id="COUNT">(%1$s)</xliff:g> 個項目"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"預覽"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"播放影片時發生問題"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"請檢查網路連線,然後再試一次"</string>
diff --git a/photopicker/res/values-zu/core_strings.xml b/photopicker/res/values-zu/core_strings.xml
index ba7d74dde..2a3222028 100644
--- a/photopicker/res/values-zu/core_strings.xml
+++ b/photopicker/res/values-zu/core_strings.xml
@@ -20,11 +20,8 @@
<string name="photopicker_application_label" msgid="7272391964836190376">"Izithombe namavidiyo"</string>
<string name="photopicker_media_item" msgid="3592234718212377636">"Imidiya"</string>
<string name="photopicker_item_selected" msgid="3741045642641682375">"Okukhethiwe"</string>
- <string name="photopicker_add_button_label" msgid="6805332693977632142">"Engeza i-<xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
- <!-- no translation found for photopicker_done_button_label (2641444126618862287) -->
- <skip />
- <!-- no translation found for photopicker_clear_selection_button_description (8614016909754648208) -->
- <skip />
+ <string name="photopicker_done_button_label" msgid="2641444126618862287">"Kwenziwe"</string>
+ <string name="photopicker_clear_selection_button_description" msgid="8614016909754648208">"Ungakhethi konke"</string>
<string name="photopicker_selection_limit_exceeded_snackbar" msgid="9136196524514670280">"Khetha izinto ezifika kwezingu-<xliff:g id="COUNT">%1$s</xliff:g>"</string>
<string name="photopicker_photos_nav_button_label" msgid="8716403708343738371">"Izithombe"</string>
<string name="photopicker_albums_nav_button_label" msgid="4738165281285221320">"Ama-albhamu"</string>
diff --git a/photopicker/res/values-zu/feature_cloud_strings.xml b/photopicker/res/values-zu/feature_cloud_strings.xml
index fff028785..06043499c 100644
--- a/photopicker/res/values-zu/feature_cloud_strings.xml
+++ b/photopicker/res/values-zu/feature_cloud_strings.xml
@@ -21,4 +21,20 @@
<string name="photopicker_preloading_progress_message" msgid="2040744085284344354">"U-<xliff:g id="NUMBER_PRELOADED">%1$d</xliff:g> wokungu-<xliff:g id="NUMBER_TOTAL">%2$d</xliff:g> ulungile"</string>
<string name="photopicker_preloading_dialog_error_title" msgid="9143648403177687062">"Ayikwazi ukulayisha ezinye Izithombe"</string>
<string name="photopicker_preloading_dialog_error_message" msgid="1467289671708199538">"Zama futhi emuva kwesikhathi. Izithombe zakho zizotholakala uma inkinga isixazululiwe."</string>
+ <!-- no translation found for photopicker_banner_cloud_media_available_title (3630564281302679941) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_media_available_message (6224134038092008505) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_title (286679907089224434) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_message (5173210485511611652) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_account_button (3407910358624445230) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_title (992613053341538886) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_provider_message (1102889303996108506) -->
+ <skip />
+ <!-- no translation found for photopicker_banner_cloud_choose_app_button (8228365266860220123) -->
+ <skip />
</resources>
diff --git a/photopicker/res/values-zu/feature_preview_strings.xml b/photopicker/res/values-zu/feature_preview_strings.xml
index 8eb8d9696..d60eb0647 100644
--- a/photopicker/res/values-zu/feature_preview_strings.xml
+++ b/photopicker/res/values-zu/feature_preview_strings.xml
@@ -17,8 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="photopicker_select_button_label" msgid="770981849239352214">"Khetha"</string>
- <string name="photopicker_deselect_button_label" msgid="4642861301796573559">"Susa ukukhetha"</string>
+ <string name="photopicker_select_button_label" msgid="658102027531907034">"Khetha konke okungu-<xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
+ <string name="photopicker_deselect_button_label" msgid="7353310112181878711">"Yekisa ukukhetha konke okungu-<xliff:g id="COUNT">(%1$s)</xliff:g>"</string>
<string name="photopicker_preview_button_label" msgid="3567318300811305531">"Ukuhlola kuqala"</string>
<string name="photopicker_preview_dialog_error_title" msgid="3285599941790002206">"Inkinga yokudlala ividiyo"</string>
<string name="photopicker_preview_dialog_error_message" msgid="1945612118302563356">"Hlola ukuxhumeka kwe-inthanethi kwakho uphinde uzame futhi"</string>
diff --git a/photopicker/res/values/core_strings.xml b/photopicker/res/values/core_strings.xml
index c84777f1d..9fb3cfef5 100644
--- a/photopicker/res/values/core_strings.xml
+++ b/photopicker/res/values/core_strings.xml
@@ -46,6 +46,9 @@
<!-- Empty state message when the photo grid has no photos -->
<string name="photopicker_photos_empty_state_body" translation_description="Message shown to the user when no photos are able to be shown underneath the primary message title">Start capturing photos and videos</string>
+ <!-- Empty state title when the camera album has no photos -->
+ <string name="photopicker_camera_empty_state_body" translation_description="Title of the message shown to the user when there are no photos in the camera album">Photos and videos captured by your camera app will appear here</string>
+
<!-- Empty state title when the favorites album has no photos -->
<string name="photopicker_favorites_empty_state_title" translation_description="Title of the message shown to the user when there are no favorite photos to show">No favorites yet</string>
diff --git a/photopicker/src/com/android/photopicker/MainActivity.kt b/photopicker/src/com/android/photopicker/MainActivity.kt
index cf658c0c7..9e1b66113 100644
--- a/photopicker/src/com/android/photopicker/MainActivity.kt
+++ b/photopicker/src/com/android/photopicker/MainActivity.kt
@@ -28,6 +28,7 @@ import android.os.UserHandle
import android.provider.MediaStore
import android.util.Log
import androidx.activity.ComponentActivity
+import androidx.activity.OnBackPressedCallback
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
@@ -47,17 +48,27 @@ import com.android.photopicker.core.banners.BannerManager
import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.configuration.IllegalIntentExtraException
import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.PhotopickerEventLogger
+import com.android.photopicker.core.events.Telemetry
import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.features.LocalFeatureManager
+import com.android.photopicker.core.navigation.PhotopickerDestinations
import com.android.photopicker.core.selection.LocalSelection
import com.android.photopicker.core.selection.Selection
+import com.android.photopicker.core.theme.AccentColorHelper
import com.android.photopicker.core.theme.PhotopickerTheme
+import com.android.photopicker.core.user.UserMonitor
+import com.android.photopicker.core.user.UserProfile
import com.android.photopicker.data.DataService
import com.android.photopicker.data.model.Media
+import com.android.photopicker.data.model.MediaSource
import com.android.photopicker.extensions.canHandleGetContentIntentMimeTypes
+import com.android.photopicker.extensions.getUserProfilesVisibleToPhotopicker
import com.android.photopicker.features.cloudmedia.CloudMediaFeature
import dagger.Lazy
import dagger.hilt.android.AndroidEntryPoint
@@ -65,8 +76,11 @@ import dagger.hilt.android.scopes.ActivityRetainedScoped
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -88,6 +102,7 @@ class MainActivity : Hilt_MainActivity() {
// on the ConfigurationManager.
@Inject @ActivityRetainedScoped lateinit var featureManager: Lazy<FeatureManager>
@Inject @Background lateinit var background: CoroutineDispatcher
+ @Inject lateinit var userMonitor: UserMonitor
// Events requires the feature manager, so initialize this lazily until the action is set.
@Inject lateinit var events: Lazy<Events>
@@ -97,13 +112,27 @@ class MainActivity : Hilt_MainActivity() {
}
/**
+ * Keeps track of the result set for the calling activity that launched the photopicker for
+ * logging purposes
+ */
+ private var activityResultSet = 0
+
+ /**
+ * Keeps track of whether or not the picker was closed by using the standard android back
+ * gesture instead of the picker bottom sheet swipe down
+ */
+ private var isPickerClosedByBackGesture = false
+
+ private lateinit var photopickerEventLogger: PhotopickerEventLogger
+
+ /**
* A flow used to trigger the preloader. When media is ready to be preloaded it should be
* provided to the preloader by emitting into this flow.
*
* The main activity should create a new [_preloadDeferred] before emitting, and then monitor
* that deferred to obtain the result of the preload operation that this flow will trigger.
*/
- val preloadMedia: MutableSharedFlow<Set<Media>> = MutableSharedFlow()
+ private val preloadMedia: MutableSharedFlow<Set<Media>> = MutableSharedFlow()
/**
* A deferred which tracks the current state of any preload operation requested by the main
@@ -115,11 +144,24 @@ class MainActivity : Hilt_MainActivity() {
* Public access to the deferred, behind a getter. (To ensure any access to this property always
* obtains the latest value)
*/
- public val preloadDeferred: CompletableDeferred<Boolean>
+ val preloadDeferred: CompletableDeferred<Boolean>
get() {
return _preloadDeferred
}
+ /**
+ * A top level flow that listens for disruptive data events from the [DataService]. This flow
+ * will emit when the DataService detects that its data is inaccurate or stale and will be used
+ * to force refresh the UI and navigate the user back to the start destination.
+ */
+ private val disruptiveDataNotification: Flow<Int> by lazy {
+ dataService.get().disruptiveDataUpdateChannel.receiveAsFlow().runningFold(initial = 0) {
+ prev,
+ _ ->
+ prev + 1
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -145,6 +187,7 @@ class MainActivity : Hilt_MainActivity() {
// configuration, then cancel the activity and close.
Log.e(TAG, "Unable to start Photopicker with illegal configuration", exception)
setResult(RESULT_CANCELED)
+ activityResultSet = RESULT_CANCELED
finish()
}
@@ -154,6 +197,10 @@ class MainActivity : Hilt_MainActivity() {
// Begin listening for events before starting the UI.
listenForEvents()
+ // Picker event logger starts listening for events dispatched throughout the app
+ photopickerEventLogger = PhotopickerEventLogger(dataService)
+ photopickerEventLogger.start(lifecycleScope, background, events.get())
+
/*
* In single select sessions, the activity needs to end after a media object is selected,
* so register a listener to the selection so the activity can handle calling
@@ -178,7 +225,6 @@ class MainActivity : Hilt_MainActivity() {
PhotopickerTheme(config = photopickerConfiguration) {
PhotopickerAppWithBottomSheet(
onDismissRequest = ::finish,
- bannerManager = bannerManager.get(),
onMediaSelectionConfirmed = {
lifecycleScope.launch {
// Move the work off the UI dispatcher.
@@ -186,11 +232,24 @@ class MainActivity : Hilt_MainActivity() {
}
},
preloadMedia = preloadMedia,
- obtainPreloaderDeferred = { preloadDeferred }
+ obtainPreloaderDeferred = { preloadDeferred },
+ disruptiveDataNotification,
)
}
}
}
+ // Check if the picker was closed by the back gesture instead of simply swiping it down
+ onBackPressedDispatcher.addCallback(
+ this,
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ isPickerClosedByBackGesture = true
+ }
+ }
+ )
+
+ // Log the picker launch details
+ reportPhotopickerApiInfo()
}
override fun onResume() {
@@ -209,6 +268,96 @@ class MainActivity : Hilt_MainActivity() {
}
}
+ /** Dispatches an event to log all details with which the photopicker launched */
+ private fun reportPhotopickerApiInfo() {
+ val intent = getIntent()
+ val dispatcherToken = FeatureToken.CORE.token
+ val sessionId = configurationManager.configuration.value.sessionId
+ val intentAction =
+ when (intent.action) {
+ MediaStore.ACTION_PICK_IMAGES -> Telemetry.PickerIntentAction.ACTION_PICK_IMAGES
+ Intent.ACTION_GET_CONTENT -> Telemetry.PickerIntentAction.ACTION_GET_CONTENT
+ MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP ->
+ Telemetry.PickerIntentAction.ACTION_USER_SELECT
+ else -> Telemetry.PickerIntentAction.UNSET_PICKER_INTENT_ACTION
+ }
+ // We always launch the picker in collapsed state. We track change in the picker bottom
+ // sheet as UI event
+ val pickerSize = Telemetry.PickerSize.COLLAPSED
+ val mediaFilters = configurationManager.configuration.value.mimeTypes
+ val pickItemsMax = configurationManager.configuration.value.selectionLimit
+ val pickerConfig = configurationManager.configuration.value
+ val launchTab = configurationManager.configuration.value.startDestination
+ val selectedTab =
+ when (launchTab) {
+ PhotopickerDestinations.PHOTO_GRID -> Telemetry.SelectedTab.PHOTOS
+ PhotopickerDestinations.ALBUM_GRID -> Telemetry.SelectedTab.ALBUMS
+ else -> Telemetry.SelectedTab.UNSET_SELECTED_TAB
+ }
+
+ val selectedAlbum = Telemetry.SelectedAlbum.UNSET_SELECTED_ALBUM
+ val isOrderedSelectionSet = pickerConfig.pickImagesInOrder
+ // TODO Creating a new instance of AccentColorHelper() to check color seems unnecessary.
+ // Fix later
+ val isAccentColorSet = AccentColorHelper.withIntent(intent).isValidAccentColorSet()
+ val isLaunchTabSet = pickerConfig.startDestination != PhotopickerDestinations.DEFAULT
+ // TODO Update when search is added
+ val isSearchEnabled = false
+ var mediaFilter = Telemetry.MediaType.UNSET_MEDIA_TYPE
+ if (mediaFilters.size > 1) {
+ for (filter in mediaFilters) {
+ if (filter.contains("image") && filter.contains("video")) {
+ mediaFilter = Telemetry.MediaType.PHOTO_VIDEO
+ } else if (filter.startsWith("image/")) {
+ mediaFilter = Telemetry.MediaType.PHOTO
+ } else if (filter.startsWith("video/")) {
+ mediaFilter = Telemetry.MediaType.VIDEO
+ }
+ lifecycleScope.launch {
+ events
+ .get()
+ .dispatch(
+ Event.ReportPhotopickerApiInfo(
+ dispatcherToken,
+ sessionId,
+ intentAction,
+ pickerSize,
+ mediaFilter,
+ pickItemsMax,
+ selectedTab,
+ selectedAlbum,
+ isOrderedSelectionSet,
+ isAccentColorSet,
+ isLaunchTabSet,
+ isSearchEnabled
+ )
+ )
+ }
+ }
+ } else {
+ lifecycleScope.launch {
+ events
+ .get()
+ .dispatch(
+ Event.ReportPhotopickerApiInfo(
+ dispatcherToken,
+ sessionId,
+ intentAction,
+ pickerSize,
+ mediaFilter,
+ pickItemsMax,
+ selectedTab,
+ selectedAlbum,
+ isOrderedSelectionSet,
+ isAccentColorSet,
+ isLaunchTabSet,
+ isSearchEnabled
+ )
+ )
+ }
+ }
+ }
+
/**
* A collector that starts when Photopicker is running in single-select mode. This collector
* will trigger [onMediaSelectionConfirmed] when the first (and only) item is selected.
@@ -241,6 +390,90 @@ class MainActivity : Hilt_MainActivity() {
}
}
+ override fun finish() {
+ reportSessionInfo()
+ super.finish()
+ }
+
+ /** Dispatches an event to log all the final state details of the picker */
+ private fun reportSessionInfo() {
+ val configuration = configurationManager.configuration.value
+ val pickerSelection =
+ if (configuration.selectionLimit == 1) {
+ Telemetry.PickerSelection.SINGLE
+ } else {
+ Telemetry.PickerSelection.MULTIPLE
+ }
+ val cloudProviderUid =
+ dataService
+ .get()
+ .availableProviders
+ .value
+ .filter { provider -> provider.mediaSource == MediaSource.REMOTE }
+ .firstOrNull()
+ ?.uid ?: -1
+ val userProfileType = userMonitor.userStatus.value.activeUserProfile.profileType
+ val currentActiveProfile =
+ when (userProfileType) {
+ UserProfile.ProfileType.PRIMARY -> Telemetry.UserProfile.PERSONAL
+ UserProfile.ProfileType.MANAGED -> Telemetry.UserProfile.WORK
+ else -> Telemetry.UserProfile.UNKNOWN
+ }
+ val pickedMediaItemsSet = selection.get().flow.value
+ val pickerStatus =
+ if (activityResultSet == RESULT_CANCELED) {
+ Telemetry.PickerStatus.CANCELED
+ } else {
+ Telemetry.PickerStatus.CONFIRMED
+ }
+ val pickedItemsCount = pickedMediaItemsSet.size
+ var pickedItemsSize = 0
+ for (mediaItem in pickedMediaItemsSet) {
+ pickedItemsSize += mediaItem.sizeInBytes.toInt()
+ }
+ val pickerMode =
+ when {
+ configuration.action.equals(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP) ->
+ Telemetry.PickerMode.PERMISSION_MODE_PICKER
+ configuration.runtimeEnv.equals(PhotopickerRuntimeEnv.ACTIVITY) ->
+ Telemetry.PickerMode.REGULAR_PICKER
+ configuration.runtimeEnv.equals(PhotopickerRuntimeEnv.EMBEDDED) ->
+ Telemetry.PickerMode.EMBEDDED_PICKER
+ else -> Telemetry.PickerMode.UNSET_PICKER_MODE
+ }
+ val pickerCloseMethod =
+ if (isPickerClosedByBackGesture) {
+ Telemetry.PickerCloseMethod.BACK_BUTTON
+ } else if (pickerStatus == Telemetry.PickerStatus.CONFIRMED) {
+ Telemetry.PickerCloseMethod.SELECTION_CONFIRMED
+ } else {
+ Telemetry.PickerCloseMethod.SWIPE_DOWN
+ }
+
+ lifecycleScope.launch {
+ val profileSwitchButtonVisible =
+ userMonitor.userStatus.getUserProfilesVisibleToPhotopicker().first().size > 1
+ events
+ .get()
+ .dispatch(
+ Event.ReportPhotopickerSessionInfo(
+ FeatureToken.CORE.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ pickerSelection,
+ cloudProviderUid,
+ currentActiveProfile,
+ pickerStatus,
+ pickedItemsCount,
+ pickedItemsSize,
+ profileSwitchButtonVisible,
+ pickerMode,
+ pickerCloseMethod
+ )
+ )
+ }
+ }
+
/**
* Sets the caller related fields in [PhotopickerConfiguration] with the calling application's
* information, if available. This should only be called once and will cause a configuration
@@ -427,6 +660,7 @@ class MainActivity : Hilt_MainActivity() {
} else {
// The selection is empty, and there is no data to return to the caller.
setResult(RESULT_CANCELED)
+ activityResultSet = RESULT_CANCELED
return
}
@@ -434,6 +668,57 @@ class MainActivity : Hilt_MainActivity() {
resultData.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
setResult(RESULT_OK, resultData)
+ activityResultSet = RESULT_OK
+ dispatchSelectedMediaItemsStatusEvent(selection)
+ }
+
+ /** Dispatches an Event to log details of all the picked media items */
+ private fun dispatchSelectedMediaItemsStatusEvent(selection: Set<Media>) {
+ val sessionId = configurationManager.configuration.value.sessionId
+ val mediaStatus = Telemetry.MediaStatus.SELECTED
+
+ for (mediaItem in selection) {
+ // TODO Update the media item position here once the Media class holds the resultIndex
+ // property: b/342555096
+ val itemPosition = 0
+ val mimeType = mediaItem.mimeType
+ // TODO find live photo format
+ val mediaType =
+ if (mimeType.startsWith("image/")) {
+ if (mimeType.contains("gif")) {
+ Telemetry.MediaType.GIF
+ } else {
+ Telemetry.MediaType.PHOTO
+ }
+ } else if (mimeType.startsWith("video/")) {
+ Telemetry.MediaType.VIDEO
+ } else {
+ Telemetry.MediaType.OTHER
+ }
+
+ val cloudOnly = mediaItem.mediaSource == MediaSource.REMOTE
+ // TODO Keeping for now while the field still exists in the actual atom to prevent the
+ // picker from crashing on selection with a null value
+ val pickerSize = Telemetry.PickerSize.EXPANDED
+ lifecycleScope.launch {
+ events
+ .get()
+ .dispatch(
+ Event.ReportPhotopickerMediaItemStatus(
+ FeatureToken.CORE.token,
+ sessionId,
+ mediaStatus,
+ mediaItem.selectionSource
+ ?: Telemetry.MediaLocation.UNSET_MEDIA_LOCATION,
+ itemPosition,
+ mediaItem.mediaItemAlbum,
+ mediaType,
+ cloudOnly,
+ pickerSize
+ )
+ )
+ }
+ }
}
/**
@@ -469,6 +754,7 @@ class MainActivity : Hilt_MainActivity() {
// No need to send any data back to the PermissionController, just send an OK signal
// back to indicate the MediaGrants are available.
setResult(RESULT_OK)
+ activityResultSet = RESULT_OK
}
/**
diff --git a/photopicker/src/com/android/photopicker/PhotopickerDeviceConfigReceiver.kt b/photopicker/src/com/android/photopicker/PhotopickerDeviceConfigReceiver.kt
index 5236df2ab..1b0120f14 100644
--- a/photopicker/src/com/android/photopicker/PhotopickerDeviceConfigReceiver.kt
+++ b/photopicker/src/com/android/photopicker/PhotopickerDeviceConfigReceiver.kt
@@ -40,7 +40,8 @@ class PhotopickerDeviceConfigReceiver : BroadcastReceiver() {
companion object {
const val TAG = "PhotopickerDeviceConfigReceiver"
/** A list of activities that need to be enabled or disabled based on flag state. */
- val activities = listOf("MainActivity", "PhotopickerGetContentActivity")
+ val activities = listOf("MainActivity", "PhotopickerGetContentActivity",
+ "PhotopickerUserSelectActivity")
}
override fun onReceive(context: Context, intent: Intent) {
diff --git a/photopicker/src/com/android/photopicker/core/PhotopickerApp.kt b/photopicker/src/com/android/photopicker/core/PhotopickerApp.kt
index fab6a63a9..374fbacc0 100644
--- a/photopicker/src/com/android/photopicker/core/PhotopickerApp.kt
+++ b/photopicker/src/com/android/photopicker/core/PhotopickerApp.kt
@@ -16,7 +16,7 @@
package com.android.photopicker.core
-import androidx.compose.animation.animateContentSize
+import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -38,6 +38,7 @@ import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.material3.rememberStandardBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -50,15 +51,21 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.rememberNavController
-import com.android.photopicker.core.banners.Banner
-import com.android.photopicker.core.banners.BannerDefinitions
-import com.android.photopicker.core.banners.BannerManager
+import com.android.modules.utils.build.SdkLevel
+import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
+import com.android.photopicker.core.embedded.LocalEmbeddedState
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.features.LocalFeatureManager
import com.android.photopicker.core.features.Location
import com.android.photopicker.core.features.LocationParams
import com.android.photopicker.core.navigation.LocalNavController
import com.android.photopicker.core.navigation.PhotopickerNavGraph
import com.android.photopicker.data.model.Media
+import com.android.photopicker.extensions.transferTouchesToHostInEmbedded
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
@@ -69,7 +76,7 @@ private val MEASUREMENT_BANNER_PADDING =
/* Spacing around the selection bar and the edges of the screen */
private val SELECTION_BAR_PADDING =
- PaddingValues(start = 16.dp, end = 16.dp, top = 0.dp, bottom = 64.dp)
+ PaddingValues(start = 16.dp, end = 16.dp, top = 0.dp, bottom = 48.dp)
/**
* This is an entrypoint of the Photopicker Compose UI. This is called from the MainActivity and is
@@ -77,19 +84,29 @@ private val SELECTION_BAR_PADDING =
* an Activity's [setContent] block.
*
* @param onDismissRequest handler for when the BottomSheet is dismissed.
+ * @param onMediaSelectionConfirmed A callback to pass to the [Location.SELECTION_BAR] to indicate
+ * the user has indicated the media selection is final.
+ * @param preloadMedia A flow of Media that the [MEDIA_PRELOADER] should begin preloading.
+ * @param obtainPreloaderDeferred A callback to obtain a deferred for the currently requested media
+ * preload.
+ * @param disruptiveDataNotification The data disruption flow that emits when the underlying data
+ * the UI has been created with is invalid
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PhotopickerAppWithBottomSheet(
onDismissRequest: () -> Unit,
- bannerManager: BannerManager,
onMediaSelectionConfirmed: () -> Unit,
preloadMedia: Flow<Set<Media>>,
obtainPreloaderDeferred: () -> CompletableDeferred<Boolean>,
+ disruptiveDataNotification: Flow<Int>,
) {
// Initialize and remember the NavController. This needs to be provided before the call to
// the NavigationGraph, so this is done at the top.
val navController = rememberNavController()
+ val scope = rememberCoroutineScope()
+ val events = LocalEvents.current
+ val configuration = LocalPhotopickerConfiguration.current
val state =
rememberBottomSheetScaffoldState(
@@ -100,7 +117,29 @@ fun PhotopickerAppWithBottomSheet(
when (sheetValue) {
// When the sheet is hidden, trigger the onDismissRequest
SheetValue.Hidden -> onDismissRequest()
- else -> {}
+ // Log picker state change
+ SheetValue.Expanded ->
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.CORE.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.EXPAND_PICKER
+ )
+ )
+ }
+ SheetValue.PartiallyExpanded ->
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.CORE.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.COLLAPSE_PICKER
+ )
+ )
+ }
}
true // allow all value changes
},
@@ -115,7 +154,9 @@ fun PhotopickerAppWithBottomSheet(
val sheetPeekHeight = remember(localConfig) { (localConfig.screenHeightDp * .75).dp }
// Provide the NavController to the rest of the Compose stack.
- CompositionLocalProvider(LocalNavController provides navController) {
+ CompositionLocalProvider(
+ LocalNavController provides navController,
+ ) {
Column(
modifier =
// Apply WindowInsets to this wrapping column to prevent the Bottom Sheet
@@ -135,7 +176,7 @@ fun PhotopickerAppWithBottomSheet(
modifier = Modifier.fillMaxHeight(),
contentAlignment = Alignment.BottomCenter
) {
- PhotopickerMain(bannerManager)
+ PhotopickerMain(disruptiveDataNotification)
Column(
modifier =
// Some elements needs to be drawn over the UI inside of the
@@ -185,26 +226,44 @@ fun PhotopickerAppWithBottomSheet(
}
/**
- * This is an entrypoint of the Photopicker Compose UI. This is called from a hosting View and is
+ * This is an entry point of the Photopicker Compose UI. This is called from a hosting View and is
* the top-most [@Composable] in the view based application. This should not be called by any
* Activity code, and should only be called inside of the ComposeView [setContent] block.
+ *
+ * @param disruptiveDataNotification The data disruption flow that emits when the underlying data
+ * the UI has been created with is invalid
*/
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun PhotopickerApp(bannerManager: BannerManager) {
+fun PhotopickerApp(disruptiveDataNotification: Flow<Int>) {
// Initialize and remember the NavController. This needs to be provided before the call to
// the NavigationGraph, so this is done at the top.
val navController = rememberNavController()
// Provide the NavController to the rest of the Compose stack.
CompositionLocalProvider(LocalNavController provides navController) {
- PhotopickerMain(bannerManager)
+ Box(modifier = Modifier.fillMaxHeight(), contentAlignment = Alignment.BottomCenter) {
+ PhotopickerMain(disruptiveDataNotification)
+ Column {
+ LocalFeatureManager.current.composeLocation(
+ Location.SNACK_BAR,
+ maxSlots = 1,
+ )
+ hideWhenState(StateSelector.EmbeddedAndCollapsed) {
+ LocalFeatureManager.current.composeLocation(
+ Location.SELECTION_BAR,
+ maxSlots = 1,
+ modifier = Modifier.padding(SELECTION_BAR_PADDING),
+ params = LocationParams.None
+ )
+ }
+ }
+ }
}
}
/**
- * This is the shared entrypoint for the Photopicker compose-UI. Composables above this function
- * must provide the required dependencies to the compose UI before calling this entrypoint.
+ * This is the shared entry point for the Photopicker compose-UI. Composables above this function
+ * must provide the required dependencies to the compose UI before calling this entry point.
*
* It is presumed after this composable the compose UI can either be running inside of a wrapped
* View or an Activity lifecycle.
@@ -216,38 +275,41 @@ fun PhotopickerApp(bannerManager: BannerManager) {
* - LocalPhotopickerConfiguration
* - LocalSelection
* - PhotopickerTheme
+ *
+ * @param disruptiveDataNotification The data disruption flow that emits when the underlying data
+ * the UI has been created with is invalid
*/
@Composable
-fun PhotopickerMain(bannerManager: BannerManager) {
-
- val currentBanner by bannerManager.flow.collectAsStateWithLifecycle()
- val scope = rememberCoroutineScope()
+fun PhotopickerMain(disruptiveDataNotification: Flow<Int>) {
+ // Collect the data disrupt flow so that Photopicker will navigate on disruptive data changes.
+ // The data service can detect when the providers that are supplying grid data have changed
+ // in such a way that the grid should immediately be cleared as the new list of providers
+ // does not include the providers that have populated the currently displayed view. When
+ // this DisruptionSignal is sent, we collect the value here to force recomposition to rebuild
+ // the view immediately.
+ val disruptCounter by disruptiveDataNotification.collectAsStateWithLifecycle(initialValue = 0)
+ watchForDataDisruptions(disruptCounter)
+ val isEmbedded =
+ LocalPhotopickerConfiguration.current.runtimeEnv == PhotopickerRuntimeEnv.EMBEDDED
+ val host = LocalEmbeddedState.current?.host
Box(modifier = Modifier.fillMaxSize()) {
Column {
// The navigation bar and banners are drawn above the navigation graph
- LocalFeatureManager.current.composeLocation(
- Location.NAVIGATION_BAR,
- maxSlots = 1,
- modifier = Modifier.fillMaxWidth()
- )
-
- Box(modifier = Modifier.animateContentSize()) {
- currentBanner?.let {
- Banner(
- it,
- modifier = Modifier.padding(MEASUREMENT_BANNER_PADDING),
- onDismiss = {
- scope.launch {
- val declaration = it.declaration
- if (declaration is BannerDefinitions) {
- bannerManager.markBannerAsDismissed(declaration)
- }
- bannerManager.refreshBanners()
- }
+ hideWhenState(selector = StateSelector.EmbeddedAndCollapsed) {
+ LocalFeatureManager.current.composeLocation(
+ Location.NAVIGATION_BAR,
+ maxSlots = 1,
+ modifier =
+ if (SdkLevel.isAtLeastU() && isEmbedded && host != null) {
+ Modifier.fillMaxWidth()
+ .transferTouchesToHostInEmbedded(
+ host = host,
+ )
+ } else {
+ Modifier.fillMaxWidth()
}
- )
- }
+ )
}
// Initialize the navigation graph.
@@ -255,3 +317,55 @@ fun PhotopickerMain(bannerManager: BannerManager) {
}
}
}
+
+/**
+ * Attaches a [LaunchedEffect] to the compose hierarchy that runs whenever the disruptionCounter is
+ * changed. This will attempt to navigate the session back to the navigation graph's starting route
+ * since a Data Disruption means that the current view is unstable and likely stale / obsolete.
+ *
+ * To prevent showing data that is irrelevant to the user in a route that may no longer exist (i.e
+ * inside of an Album in a provider that is no longer attached), the session is force-navigated to
+ * the main route.
+ *
+ * @param disruptionCounter the current disruptionCounter value from the data service.
+ */
+@Composable
+private fun watchForDataDisruptions(disruptionCounter: Int) {
+
+ val navController = LocalNavController.current
+ LaunchedEffect(disruptionCounter) {
+ if (disruptionCounter > 0) {
+ Log.d("Photopicker", "DisruptiveData notification received.")
+
+ try {
+ val startDestination =
+ checkNotNull(navController.graph.startDestinationRoute) {
+ "startDestination was Null"
+ }
+ if (navController.currentBackStackEntry?.destination?.route != startDestination) {
+
+ // Try to return to the start destination for the data reload, by attempting to
+ // move backwards via the backstack.
+ val navigated =
+ navController.popBackStack(
+ startDestination,
+ /* inclusive= */ false,
+ /* saveState = */ false,
+ )
+
+ // The start route is not on the backstack, so as a last resort, navigate
+ // directly.
+ if (!navigated) {
+ navController.navigate(startDestination, /* navOptions= */ null)
+ }
+ }
+ } catch (e: IllegalStateException) {
+ Log.e(
+ "Photopicker",
+ "disruptiveDataNotification was received, but unable to resolve the graph.",
+ e
+ )
+ }
+ }
+ }
+}
diff --git a/photopicker/src/com/android/photopicker/core/banners/Banner.kt b/photopicker/src/com/android/photopicker/core/banners/Banner.kt
index 2a59ee91e..9b3fbaefb 100644
--- a/photopicker/src/com/android/photopicker/core/banners/Banner.kt
+++ b/photopicker/src/com/android/photopicker/core/banners/Banner.kt
@@ -30,6 +30,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
@@ -37,6 +39,13 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.android.photopicker.R
+import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.Telemetry.BannerType
+import com.android.photopicker.core.events.Telemetry.UserBannerInteraction
+import com.android.photopicker.core.features.FeatureToken.CORE
+import kotlinx.coroutines.launch
/**
* Object interface to generate a banner element for the UI. This abstracts the appearance of the
@@ -123,6 +132,9 @@ fun Banner(
onDismiss: () -> Unit = {},
) {
+ val config = LocalPhotopickerConfiguration.current
+ val events = LocalEvents.current
+
Card(
// Consume the incoming modifier for positioning the banner.
modifier = modifier,
@@ -170,6 +182,8 @@ fun Banner(
// The action Row, which sometimes may be empty if the banner is not dismissable and
// does not provide its own Action
if (banner.declaration.dismissable || banner.actionLabel() != null) {
+ val scope = rememberCoroutineScope()
+
Row(
horizontalArrangement =
Arrangement.spacedBy(MEASUREMENT_BANNER_BUTTON_ROW_SPACING),
@@ -183,7 +197,23 @@ fun Banner(
banner.actionLabel()?.let {
val context = LocalContext.current
TextButton(
- onClick = { banner.onAction(context) },
+ onClick = {
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerBannerInteraction(
+ dispatcherToken = CORE.token,
+ sessionId = config.sessionId,
+ bannerType =
+ BannerType.fromBannerDeclaration(
+ banner.declaration
+ ),
+ userInteraction =
+ UserBannerInteraction.CLICK_BANNER_ACTION_BUTTON
+ )
+ )
+ }
+ banner.onAction(context)
+ },
) {
Text(it)
}
@@ -194,7 +224,25 @@ fun Banner(
// clicked is up to the caller. A core string is used here to ensure consistency
// between banners.
if (banner.declaration.dismissable) {
- TextButton(onClick = onDismiss) {
+ TextButton(
+ onClick = {
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerBannerInteraction(
+ dispatcherToken = CORE.token,
+ sessionId = config.sessionId,
+ bannerType =
+ BannerType.fromBannerDeclaration(
+ banner.declaration
+ ),
+ userInteraction =
+ UserBannerInteraction.CLICK_BANNER_DISMISS_BUTTON
+ )
+ )
+ }
+ onDismiss()
+ }
+ ) {
Text(stringResource(R.string.photopicker_dismiss_banner_button_label))
}
}
@@ -202,4 +250,17 @@ fun Banner(
}
}
}
+
+ // Add a log that the banner was shown.
+ LaunchedEffect(banner) {
+ events.dispatch(
+ Event.LogPhotopickerBannerInteraction(
+ dispatcherToken = CORE.token,
+ sessionId = config.sessionId,
+ bannerType = BannerType.fromBannerDeclaration(banner.declaration),
+ // TODO(b/357010907): Add banner shown interaction when the atom exists.
+ userInteraction = UserBannerInteraction.UNSET_BANNER_INTERACTION
+ )
+ )
+ }
}
diff --git a/photopicker/src/com/android/photopicker/core/components/mediagrid/MediaGrid.kt b/photopicker/src/com/android/photopicker/core/components/mediagrid/MediaGrid.kt
index 912cf0fa7..60ccab1eb 100644
--- a/photopicker/src/com/android/photopicker/core/components/mediagrid/MediaGrid.kt
+++ b/photopicker/src/com/android/photopicker/core/components/mediagrid/MediaGrid.kt
@@ -50,7 +50,6 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Gif
import androidx.compose.material.icons.filled.MotionPhotosOn
import androidx.compose.material.icons.filled.PlayCircle
@@ -64,6 +63,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.AbsoluteAlignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -72,6 +72,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.onLongClick
import androidx.compose.ui.semantics.semantics
@@ -82,9 +83,12 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
+import com.android.modules.utils.build.SdkLevel
import com.android.photopicker.R
import com.android.photopicker.core.components.MediaGridItem.Companion.defaultBuildContentType
import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
+import com.android.photopicker.core.embedded.LocalEmbeddedState
import com.android.photopicker.core.glide.Resolution
import com.android.photopicker.core.glide.loadMedia
import com.android.photopicker.core.theme.CustomAccentColorScheme
@@ -94,6 +98,7 @@ import com.android.photopicker.extensions.circleBackground
import com.android.photopicker.extensions.insertMonthSeparators
import com.android.photopicker.extensions.toMediaGridItemFromAlbum
import com.android.photopicker.extensions.toMediaGridItemFromMedia
+import com.android.photopicker.extensions.transferGridTouchesToHostInEmbedded
import java.text.NumberFormat
/** The number of grid cells per row for Phone / narrow layouts */
@@ -148,7 +153,7 @@ val MEASUREMENT_DEFAULT_ALBUM_THUMBNAIL_ICON_PADDING = 16.dp
val MEASUREMENT_DEFAULT_ALBUM_BOTTOM_PADDING = 16.dp
/** Size of the spacer between the album icon and the album display label */
-val MEASUREMENT_DEFAULT_ALBUM_LABEL_SPACER_SIZE = 8.dp
+val MEASUREMENT_DEFAULT_ALBUM_LABEL_SPACER_SIZE = 12.dp
/**
* Composable for creating a MediaItemGrid from a [PagingData] source of data that implements
@@ -201,7 +206,7 @@ fun mediaGrid(
item: MediaGridItem,
isSelected: Boolean,
onClick: ((item: MediaGridItem) -> Unit)?,
- onLongPress: ((item: MediaGridItem) -> Unit)?,
+ onLongPress: ((item: MediaGridItem) -> Unit)?
) -> Unit =
{ item, isSelected, onClick, onLongPress,
->
@@ -221,16 +226,46 @@ fun mediaGrid(
contentSeparatorFactory: @Composable (item: MediaGridItem.SeparatorItem) -> Unit = { item ->
defaultBuildSeparator(item)
},
+ bannerContent: (@Composable () -> Unit)? = null,
) {
+ // To know whether the request in coming from Embedded or PhotoPicker
+ val isEmbedded =
+ LocalPhotopickerConfiguration.current.runtimeEnv == PhotopickerRuntimeEnv.EMBEDDED
+ val host = LocalEmbeddedState.current?.host
+ /**
+ * Bottom sheet current state in runtime Embedded Photopicker. This assignment is necessary to
+ * get the regular updates of bottom sheet current state inside [LazyVerticalGrid]
+ */
+ val isExpanded = rememberUpdatedState(LocalEmbeddedState.current?.isExpanded ?: false)
LazyVerticalGrid(
columns = columns,
- modifier = modifier,
+ modifier =
+ if (SdkLevel.isAtLeastU() && isEmbedded && host != null) {
+ modifier.transferGridTouchesToHostInEmbedded(state, isExpanded, host)
+ } else {
+ modifier
+ },
state = state,
contentPadding = contentPadding,
userScrollEnabled = userScrollEnabled,
horizontalArrangement = Arrangement.spacedBy(gridCellPadding),
verticalArrangement = Arrangement.spacedBy(gridCellPadding),
) {
+
+ // If banner content was passed add it to the grid as a full span item
+ // so that it appears inside the scroll container.
+ bannerContent?.let {
+ item(
+ span = {
+ if (isExpandedScreen) GridItemSpan(CELLS_PER_ROW_EXPANDED)
+ else GridItemSpan(CELLS_PER_ROW)
+ }
+ ) {
+ it()
+ }
+ }
+
+ // Add the media items from the LazyPagingItems
items(
count = items.itemCount,
key = { index -> MediaGridItem.keyFactory(items.peek(index), index) },
@@ -477,7 +512,7 @@ private fun SelectedIconOverlay(isSelected: Boolean, selectedIndex: Int) {
}
false ->
Icon(
- Icons.Filled.CheckCircle,
+ ImageVector.vectorResource(R.drawable.photopicker_selected_media),
modifier =
Modifier
// Background is necessary because the icon has negative
@@ -487,7 +522,7 @@ private fun SelectedIconOverlay(isSelected: Boolean, selectedIndex: Int) {
// the image.
.border(
MEASUREMENT_SELECTED_ICON_BORDER,
- MaterialTheme.colorScheme.surfaceVariant,
+ MaterialTheme.colorScheme.surfaceContainerHighest,
CircleShape
),
contentDescription = stringResource(R.string.photopicker_item_selected),
@@ -580,7 +615,7 @@ private fun defaultBuildAlbumItem(
@Composable
private fun defaultBuildSeparator(item: MediaGridItem.SeparatorItem) {
Box(Modifier.padding(MEASUREMENT_SEPARATOR_PADDING).semantics(mergeDescendants = true) {}) {
- Text(item.label)
+ Text(item.label, style = MaterialTheme.typography.titleSmall)
}
}
diff --git a/photopicker/src/com/android/photopicker/core/configuration/ConfigurationManager.kt b/photopicker/src/com/android/photopicker/core/configuration/ConfigurationManager.kt
index e6c810b1f..4d86c22ad 100644
--- a/photopicker/src/com/android/photopicker/core/configuration/ConfigurationManager.kt
+++ b/photopicker/src/com/android/photopicker/core/configuration/ConfigurationManager.kt
@@ -17,8 +17,11 @@
package com.android.photopicker.core.configuration
import android.content.Intent
+import android.os.Build
import android.provider.DeviceConfig
+import android.provider.EmbeddedPhotopickerFeatureInfo
import android.util.Log
+import androidx.annotation.RequiresApi
import androidx.compose.ui.graphics.isUnspecified
import com.android.photopicker.core.navigation.PhotopickerDestinations
import com.android.photopicker.core.theme.AccentColorHelper
@@ -58,12 +61,14 @@ import kotlinx.coroutines.launch
* in.
* @property deviceConfigProxy This is provided to the ConfigurationManager to better support
* testing various device flags, without relying on the device's actual flags at test time.
+ * @property sessionId A randomly generated integer to identify the current photopicker session
*/
class ConfigurationManager(
private val runtimeEnv: PhotopickerRuntimeEnv,
private val scope: CoroutineScope,
private val dispatcher: CoroutineDispatcher,
private val deviceConfigProxy: DeviceConfigProxy,
+ private val sessionId: Int,
) {
companion object {
@@ -123,6 +128,55 @@ class ConfigurationManager(
}
/**
+ * Updates the [PhotopickerConfiguration] with the [EmbeddedPhotopickerFeatureInfo] that the
+ * Embedded Photopicker is running with.
+ *
+ * Since [ConfigurationManager] is bound to the [EmbeddedServiceComponent], it does not have a
+ * reference to the currently running Session (if there is one). This allows the session to set
+ * the current FeatureInfo externally once the session is available.
+ *
+ * It's important that this method is called before the FeatureManager is started to prevent the
+ * feature manager from being re-initialized.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun setEmbeddedPhotopickerFeatureInfo(featureInfo: EmbeddedPhotopickerFeatureInfo) {
+ Log.d(TAG, "New featureInfo received: $featureInfo : Configuration will now update.")
+
+ val selectionLimit = featureInfo.maxSelectionLimit
+ val mimeTypes = featureInfo.mimeTypes
+ val preSelectedUris = featureInfo.preSelectedUris
+
+ /**
+ * Pick images in order is a combination of circumstances:
+ * - selectionLimit mode must be multiselect (more than 1)
+ * - The feature must be requested from the caller in the featureInfo
+ */
+ val pickImagesInOrder = featureInfo.isOrderedSelection && (selectionLimit > 1)
+
+ /** Check if the accent color was set and is valid. */
+ val accentColor =
+ with(AccentColorHelper(featureInfo.accentColor)) {
+ if (getAccentColor().isUnspecified) {
+ null
+ } else {
+ inputColor
+ }
+ }
+
+ // Use updateAndGet to ensure that the values are set before this method returns so that
+ // the new configuration is immediately available to the new subscribers.
+ _configuration.updateAndGet {
+ it.copy(
+ selectionLimit = selectionLimit,
+ accentColor = accentColor,
+ mimeTypes = mimeTypes.toCollection(ArrayList()),
+ preSelectedUris = preSelectedUris.toCollection(ArrayList()),
+ pickImagesInOrder = pickImagesInOrder,
+ )
+ }
+ }
+
+ /**
* Sets the current intent & action Photopicker is running under.
*
* Since [ConfigurationManager] is bound to the [ActivityRetainedComponent] it does not have a
@@ -210,6 +264,7 @@ class ConfigurationManager(
runtimeEnv = runtimeEnv,
action = "",
flags = getFlagsFromDeviceConfig(),
+ sessionId = sessionId,
)
Log.d(TAG, "Startup configuration: $config")
diff --git a/photopicker/src/com/android/photopicker/core/configuration/PhotopickerConfiguration.kt b/photopicker/src/com/android/photopicker/core/configuration/PhotopickerConfiguration.kt
index a96dbe520..3682b3849 100644
--- a/photopicker/src/com/android/photopicker/core/configuration/PhotopickerConfiguration.kt
+++ b/photopicker/src/com/android/photopicker/core/configuration/PhotopickerConfiguration.kt
@@ -19,6 +19,7 @@ package com.android.photopicker.core.configuration
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
+import android.net.Uri
import android.os.SystemProperties
import com.android.photopicker.core.navigation.PhotopickerDestinations
@@ -53,10 +54,13 @@ enum class PhotopickerRuntimeEnv {
* set or set to too large a limit.
* @property startDestination the start destination that should be consider the "home" view the user
* is shown for the session.
+ * @property preSelectedUris an [ArrayList] of the [Uri]s of the items selected by the user in the
+ * previous photopicker sessions launched via the same calling app.
* @property flags a snapshot of the relevant flags in [DeviceConfig]. These are not live values.
* @property deviceIsDebuggable if the device is running a build which has [ro.debuggable == 1]
* @property intent the [Intent] that Photopicker was launched with. This property is private to
* restrict access outside of this class.
+ * @property sessionId identifies the current photopicker session
*/
data class PhotopickerConfiguration(
val runtimeEnv: PhotopickerRuntimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
@@ -69,8 +73,10 @@ data class PhotopickerConfiguration(
val pickImagesInOrder: Boolean = false,
val selectionLimit: Int = DEFAULT_SELECTION_LIMIT,
val startDestination: PhotopickerDestinations = PhotopickerDestinations.DEFAULT,
+ val preSelectedUris: ArrayList<Uri>? = null,
val deviceIsDebuggable: Boolean = buildIsDebuggable,
val flags: PhotopickerFlags = PhotopickerFlags(),
+ val sessionId: Int,
private val intent: Intent? = null,
) {
diff --git a/photopicker/src/com/android/photopicker/core/embedded/EmbeddedService.kt b/photopicker/src/com/android/photopicker/core/embedded/EmbeddedService.kt
index fa70c2153..26d4c4652 100644
--- a/photopicker/src/com/android/photopicker/core/embedded/EmbeddedService.kt
+++ b/photopicker/src/com/android/photopicker/core/embedded/EmbeddedService.kt
@@ -17,6 +17,7 @@ package com.android.photopicker.core.embedded
import android.app.Service
import android.content.Intent
+import android.net.Uri
import android.os.Build
import android.os.IBinder
import android.provider.EmbeddedPhotopickerFeatureInfo
@@ -25,7 +26,8 @@ import android.util.Log
import androidx.annotation.RequiresApi
import com.android.modules.utils.build.SdkLevel
import com.android.photopicker.core.EmbeddedServiceComponentBuilder
-import com.android.providers.media.flags.Flags.enableEmbeddedPhotopicker
+import com.android.photopicker.core.configuration.DeviceConfigProxyImpl
+import com.android.photopicker.core.configuration.NAMESPACE_MEDIAPROVIDER
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@@ -56,13 +58,25 @@ class EmbeddedService : Hilt_EmbeddedService() {
// EmbeddedService.
private val allSessions: MutableList<Session> = mutableListOf()
+ private val FEATURE_MODERN_PICKER_ENABLED = Pair("enable_modern_picker", true)
+
+ /** To check if modern photopicker is enabled on the device */
+ private val isModernPickerEnabled =
+ DeviceConfigProxyImpl()
+ .getFlag(
+ NAMESPACE_MEDIAPROVIDER,
+ /* key= */ FEATURE_MODERN_PICKER_ENABLED.first,
+ /* defaultValue= */ FEATURE_MODERN_PICKER_ENABLED.second
+ )
+
companion object {
val TAG: String = "PhotopickerEmbeddedService"
}
// The binder object that is sent to all clients that bind this service.
private val _binder: IBinder? =
- if (SdkLevel.isAtLeastU() && enableEmbeddedPhotopicker()) {
+ if (SdkLevel.isAtLeastU() && isModernPickerEnabled) {
+ // TODO(b/357048672): Check embedded picker aconfig flag before the API release
EmbeddedPhotopickerImpl(sessionFactory = ::buildSession)
} else {
// Embedded Photopicker is only available on U+ devices when the build flag is enabled.
@@ -72,7 +86,6 @@ class EmbeddedService : Hilt_EmbeddedService() {
}
override fun onBind(intent: Intent?): IBinder? {
-
// If _binder is null, the device Sdk is too low, or a required flag was not enabled, and so
// this session will be ignored.
if (_binder == null) {
@@ -136,8 +149,48 @@ class EmbeddedService : Hilt_EmbeddedService() {
hostToken = hostToken,
featureInfo = featureInfo,
clientCallback = clientCallback,
+ grantUriPermission = ::grantUriToClient,
+ revokeUriPermission = ::revokeUriToClient,
)
allSessions.add(newSession)
return newSession
}
+
+ /**
+ * Grants [Intent.FLAG_GRANT_READ_URI_PERMISSION] to uri for given client.
+ *
+ * This happens during selection of new items recorded in [Session.listenForSelectionEvents]
+ */
+ fun grantUriToClient(clientPackageName: String, uri: Uri): GrantResult {
+ try {
+ this.grantUriPermission(clientPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ } catch (e: SecurityException) {
+ return GrantResult.FAILURE
+ }
+
+ return GrantResult.SUCCESS
+ }
+
+ /**
+ * Revokes [Intent.FLAG_GRANT_READ_URI_PERMISSION] to uri for given client.
+ *
+ * This happens during deselection of items recorded in [Session.listenForSelectionEvents]
+ */
+ fun revokeUriToClient(clientPackageName: String, uri: Uri): GrantResult {
+ try {
+ this.revokeUriPermission(clientPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ } catch (e: SecurityException) {
+ return GrantResult.FAILURE
+ }
+ return GrantResult.SUCCESS
+ }
+
+ /**
+ * Enum that denotes if MediaProvider was able to successfully grant uri permission to a given
+ * package or not.
+ */
+ enum class GrantResult {
+ SUCCESS,
+ FAILURE
+ }
}
diff --git a/photopicker/src/com/android/photopicker/core/embedded/EmbeddedState.kt b/photopicker/src/com/android/photopicker/core/embedded/EmbeddedState.kt
index 25dc2ff8d..a0131e9ef 100644
--- a/photopicker/src/com/android/photopicker/core/embedded/EmbeddedState.kt
+++ b/photopicker/src/com/android/photopicker/core/embedded/EmbeddedState.kt
@@ -16,9 +16,20 @@
package com.android.photopicker.core.embedded
+import android.view.SurfaceControlViewHost
+import androidx.appcompat.app.AppCompatDelegate
+
/**
- * Data object that represents the state of Photopicker in embedded runtime.
+ * Data object that represents the state of Photopicker and hold the instance of
+ * [SurfaceControlViewHost] in embedded runtime.
*
+ * @param host the Instance of [SurfaceControlViewHost]
* @property isExpanded true if photopicker is expanded/full-view, false if collapsed/half-view.
*/
-data class EmbeddedState(val isExpanded: Boolean = false)
+data class EmbeddedState(
+ val isExpanded: Boolean = false,
+ val isDarkTheme: Boolean =
+ AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES,
+ val recomposeToggle: Boolean = false,
+ val host: SurfaceControlViewHost? = null,
+)
diff --git a/photopicker/src/com/android/photopicker/core/embedded/EmbeddedStateManager.kt b/photopicker/src/com/android/photopicker/core/embedded/EmbeddedStateManager.kt
index 9a206c283..077ce9312 100644
--- a/photopicker/src/com/android/photopicker/core/embedded/EmbeddedStateManager.kt
+++ b/photopicker/src/com/android/photopicker/core/embedded/EmbeddedStateManager.kt
@@ -17,6 +17,7 @@
package com.android.photopicker.core.embedded
import android.util.Log
+import android.view.SurfaceControlViewHost
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
@@ -28,12 +29,16 @@ import kotlinx.coroutines.flow.update
* See [EmbeddedState] for details about all the various pieces that make up the session state.
*
* Provides a long-living [StateFlow] that emits the currently known state.
+ *
+ * @param host the Instance of [SurfaceControlViewHost] for the current session
*/
-class EmbeddedStateManager {
+class EmbeddedStateManager(host: SurfaceControlViewHost? = null) {
companion object {
const val TAG: String = "PhotopickerEmbeddedStateManager"
}
+ private val _host = host
+
/*
* Internal [EmbeddedState] flow. When the embedded state changes, this is what should
* be updated to ensure all listeners are notified.
@@ -46,16 +51,43 @@ class EmbeddedStateManager {
*/
val state: StateFlow<EmbeddedState> = _state
+ private var _recomposeToggle = state.value.recomposeToggle
+
/** Assembles an initial state upon embedded photopicker session launch. */
private fun generateInitialEmbeddedState(): EmbeddedState {
- val initialEmbeddedState = EmbeddedState()
+ val initialEmbeddedState = EmbeddedState(host = _host)
Log.d(TAG, "Initial embedded state: $initialEmbeddedState")
return initialEmbeddedState
}
- /** Sets the current expanded or collapsed state of the embedded photopicker. */
+ /**
+ * Updates the expanded state of the embedded photopicker.
+ *
+ * @param isExpanded true if the photopicker is expanded (full-screen view), false if it is
+ * collapsed (half-screen view).
+ */
fun setIsExpanded(isExpanded: Boolean) {
Log.d(TAG, "Expanded state updated to $isExpanded")
_state.update { it.copy(isExpanded = isExpanded) }
}
+
+ /**
+ * Sets the dark theme preference of the embedded photopicker
+ *
+ * @param isDarkTheme true to apply a dark theme, false for a light theme.
+ */
+ fun setIsDarkTheme(isDarkTheme: Boolean) {
+ Log.d(TAG, "Dark theme state updated to $isDarkTheme")
+ _state.update { it.copy(isDarkTheme = isDarkTheme) }
+ }
+
+ /**
+ * Updates the [_recomposeToggle] causing the photopicker to recompose its UI, to respond to
+ * change in config.
+ */
+ fun triggerRecompose() {
+ _recomposeToggle = !_recomposeToggle
+ Log.d(TAG, "Recompose toggle updated to $_recomposeToggle")
+ _state.update { it.copy(recomposeToggle = _recomposeToggle) }
+ }
}
diff --git a/photopicker/src/com/android/photopicker/core/embedded/EmbeddedViewModelFactory.kt b/photopicker/src/com/android/photopicker/core/embedded/EmbeddedViewModelFactory.kt
index 14f28cb73..1f101a68f 100644
--- a/photopicker/src/com/android/photopicker/core/embedded/EmbeddedViewModelFactory.kt
+++ b/photopicker/src/com/android/photopicker/core/embedded/EmbeddedViewModelFactory.kt
@@ -19,6 +19,7 @@ import androidx.compose.runtime.getValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.android.photopicker.core.Background
+import com.android.photopicker.core.banners.BannerManager
import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.features.FeatureManager
@@ -60,9 +61,11 @@ import kotlinx.coroutines.CoroutineDispatcher
* @property selection
* @property userMonitor
*/
+@Suppress("UNCHECKED_CAST")
class EmbeddedViewModelFactory(
@Background val backgroundDispatcher: CoroutineDispatcher,
val configurationManager: Lazy<ConfigurationManager>,
+ val bannerManager: Lazy<BannerManager>,
val dataService: Lazy<DataService>,
val events: Lazy<Events>,
val featureManager: Lazy<FeatureManager>,
@@ -73,28 +76,45 @@ class EmbeddedViewModelFactory(
with(modelClass) {
return when {
isAssignableFrom(AlbumGridViewModel::class.java) ->
- @Suppress("UNCHECKED_CAST")
AlbumGridViewModel(null, selection.get(), dataService.get(), events.get()) as T
isAssignableFrom(MediaPreloaderViewModel::class.java) ->
- @Suppress("UNCHECKED_CAST")
MediaPreloaderViewModel(
null,
backgroundDispatcher,
selection.get(),
userMonitor.get(),
configurationManager.get(),
+ events.get(),
)
as T
isAssignableFrom(PhotoGridViewModel::class.java) ->
- @Suppress("UNCHECKED_CAST")
- PhotoGridViewModel(null, selection.get(), dataService.get(), events.get()) as T
+ PhotoGridViewModel(
+ null,
+ selection.get(),
+ dataService.get(),
+ events.get(),
+ bannerManager.get(),
+ )
+ as T
isAssignableFrom(PreviewViewModel::class.java) ->
- @Suppress("UNCHECKED_CAST")
- PreviewViewModel(null, selection.get(), userMonitor.get(), dataService.get())
+ PreviewViewModel(
+ null,
+ selection.get(),
+ userMonitor.get(),
+ dataService.get(),
+ events.get(),
+ configurationManager.get(),
+ )
as T
isAssignableFrom(ProfileSelectorViewModel::class.java) ->
- @Suppress("UNCHECKED_CAST")
- ProfileSelectorViewModel(null, selection.get(), userMonitor.get()) as T
+ ProfileSelectorViewModel(
+ null,
+ selection.get(),
+ userMonitor.get(),
+ events.get(),
+ configurationManager.get()
+ )
+ as T
else ->
throw IllegalArgumentException(
"Unknown ViewModel class: ${modelClass.simpleName}"
diff --git a/photopicker/src/com/android/photopicker/core/embedded/Session.kt b/photopicker/src/com/android/photopicker/core/embedded/Session.kt
index 618baa178..773757ce5 100644
--- a/photopicker/src/com/android/photopicker/core/embedded/Session.kt
+++ b/photopicker/src/com/android/photopicker/core/embedded/Session.kt
@@ -20,6 +20,7 @@ import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Configuration
import android.hardware.display.DisplayManager
+import android.net.Uri
import android.os.Build
import android.os.IBinder
import android.provider.EmbeddedPhotopickerFeatureInfo
@@ -33,8 +34,11 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
+import com.android.photopicker.core.Background
import com.android.photopicker.core.EmbeddedServiceComponent
import com.android.photopicker.core.Main
import com.android.photopicker.core.PhotopickerApp
@@ -58,6 +62,11 @@ import dagger.hilt.EntryPoints
import dagger.hilt.InstallIn
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.runningFold
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
/** Alias that describes a factory function that creates a Session. */
@@ -112,6 +121,8 @@ open class Session(
private val height: Int,
private val featureInfo: EmbeddedPhotopickerFeatureInfo,
private val clientCallback: IEmbeddedPhotopickerClient,
+ private val grantUriPermission: (packageName: String, uri: Uri) -> EmbeddedService.GrantResult,
+ private val revokeUriPermission: (packageName: String, uri: Uri) -> EmbeddedService.GrantResult,
// TODO(b/354929684): Replace AIDL implementations with wrapper classes.
) : IEmbeddedPhotopickerSession.Stub() {
@@ -145,6 +156,8 @@ open class Session(
@Main fun scope(): CoroutineScope
+ @Background fun backgroundScope(): CoroutineScope
+
fun selection(): Lazy<Selection<Media>>
fun userMonitor(): Lazy<UserMonitor>
@@ -155,11 +168,26 @@ open class Session(
EntryPoints.get(component, EmbeddedEntryPoint::class.java)
private val _embeddedViewLifecycle: EmbeddedLifecycle = _dependencies.lifecycle()
- private val _scope: CoroutineScope = _dependencies.scope()
private val _main: CoroutineDispatcher = _dependencies.mainDispatcher()
+ private val _backgroundScope: CoroutineScope = _dependencies.backgroundScope()
+
+ // Wrap this in a lazy to prevent the [DataService] from getting initialized before the
+ // ComposeView is started.
+ // This flow is used to signal the UI when the DataService detects a provider update (or other
+ // data change which should disrupt the UI)
+ private val disruptiveDataNotification: Flow<Int> by lazy {
+ _dependencies.dataService().get().disruptiveDataUpdateChannel.receiveAsFlow().runningFold(
+ initial = 0
+ ) { prev, _ ->
+ prev + 1
+ }
+ }
private val _host: SurfaceControlViewHost
private val _view: ComposeView
+ private val _stateManager: EmbeddedStateManager
+
+ fun getView() = _view
open val surfacePackage: SurfaceControlViewHost.SurfacePackage
get() {
@@ -208,8 +236,9 @@ open class Session(
callingPackageLabel = clientPackageLabel
)
- // TODO(b/350965066): set featureInfo in the ConfigurationManager and hand any errors back
- // to the client
+ // Update the [PhotopickerConfiguration] associated with the session using the
+ // [EmbeddedPhotopickerFeatureInfo].
+ _dependencies.configurationManager().get().setEmbeddedPhotopickerFeatureInfo(featureInfo)
// Configuration is now stable, so the view can be created.
// NOTE: Do not update the configuration after this line, it will cause the UI to
@@ -217,7 +246,13 @@ open class Session(
Log.d(TAG, "EmbeddedConfiguration is stable, UI will now start.")
_view = createPhotopickerComposeView(context)
_host = createSurfaceControlViewHost(context, displayId, hostToken)
+ // This initialization should happen only after receiving the [_host]
+ _stateManager = EmbeddedStateManager(_host)
runBlocking(_main) { _host.setView(_view, width, height) }
+
+ // Start listening to selection/deselection events for this Session so
+ // we can grant/revoke permission to selected/deselected uris immediately.
+ listenForSelectionEvents()
}
override fun close() {
@@ -287,6 +322,8 @@ open class Session(
.configuration
.collectAsStateWithLifecycle()
+ val embeddedState by _stateManager.state.collectAsStateWithLifecycle()
+
// Provide values to the entire compose stack.
CompositionLocalProvider(
LocalFeatureManager provides _dependencies.featureManager().get(),
@@ -298,9 +335,19 @@ open class Session(
LocalEmbeddedLifecycle provides _embeddedViewLifecycle,
LocalViewModelStoreOwner provides _embeddedViewLifecycle,
LocalOnBackPressedDispatcherOwner provides _embeddedViewLifecycle,
+ LocalEmbeddedState provides embeddedState
) {
- PhotopickerTheme(config = photopickerConfiguration) {
- PhotopickerApp(_dependencies.bannerManager().get())
+ val currentEmbeddedState =
+ checkNotNull(LocalEmbeddedState.current) {
+ "Embedded state cannot be null when runtime env is embedded."
+ }
+ PhotopickerTheme(
+ isDarkTheme = currentEmbeddedState.isDarkTheme,
+ config = photopickerConfiguration
+ ) {
+ PhotopickerApp(
+ disruptiveDataNotification,
+ )
}
}
}
@@ -314,6 +361,65 @@ open class Session(
}
}
+ /**
+ * A collector that starts for a Session in embedded mode. This collector will grant/revoke uri
+ * permission when item is selected/deselected respectively.
+ *
+ * It emits both the previous and new selection of media items.
+ */
+ fun listenForSelectionEvents() {
+ _backgroundScope.launch {
+ _dependencies
+ .selection()
+ .get()
+ .flow
+ .flowWithLifecycle(_embeddedViewLifecycle.lifecycle, Lifecycle.State.STARTED)
+ .runningFold(initial = emptySet<Media>()) { _prevSelection, _newSelection ->
+ // Get list of items removed/deselected by user so that we can revoke access to
+ // those uris.
+ var unselectedMedia: Set<Media> = _prevSelection.subtract(_newSelection)
+ Log.d(TAG, "Revoking uri permission to $unselectedMedia")
+
+ // Get list of items added/selected by user so that we can grant access to
+ // those uris.
+ var newlySelectedMedia: Set<Media> = _newSelection.subtract(_prevSelection)
+ Log.d(TAG, "Granting uri permission to $newlySelectedMedia")
+
+ // Grant uri to newly selected media and notify client
+ newlySelectedMedia.iterator().forEach { item ->
+ val result = grantUriPermission(clientPackageName, item.mediaUri)
+ if (result == EmbeddedService.GrantResult.SUCCESS) {
+ clientCallback.onItemSelected(item.mediaUri)
+ } else {
+ Log.w(
+ TAG,
+ "Error granting permission to uri ${item.mediaUri} " +
+ "for package $clientPackageName"
+ )
+ }
+ }
+
+ // Revoke uri to newly selected media and notify client
+ unselectedMedia.iterator().forEach { item ->
+ val result = revokeUriPermission(clientPackageName, item.mediaUri)
+ if (result == EmbeddedService.GrantResult.SUCCESS) {
+ clientCallback.onItemDeselected(item.mediaUri)
+ } else {
+ Log.w(
+ TAG,
+ "Error revoking permission to uri ${item.mediaUri} " +
+ "for package $clientPackageName"
+ )
+ }
+ }
+
+ // Update previous selection to current flow
+ _newSelection
+ }
+ .collect()
+ }
+ }
+
override fun notifyVisibilityChanged(isVisible: Boolean) {
Log.d(TAG, "Session visibility has changed: $isVisible")
when (isVisible) {
@@ -323,14 +429,26 @@ open class Session(
}
override fun notifyResized(width: Int, height: Int) {
- TODO("Not yet implemented")
+ _host.relayout(width, height)
+ _stateManager.triggerRecompose()
}
override fun notifyConfigurationChanged(configuration: Configuration?) {
- TODO("Not yet implemented")
+ if (configuration == null) return
+
+ // Check for dark theme
+ val isNewThemeDark =
+ (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
+ Configuration.UI_MODE_NIGHT_YES
+
+ // Update embedded state manager
+ _stateManager.setIsDarkTheme(isNewThemeDark)
+
+ // Pass the configuration change along to the view
+ _view.dispatchConfigurationChanged(configuration)
}
override fun notifyPhotopickerExpanded(isExpanded: Boolean) {
- TODO("Not yet implemented")
+ _stateManager.setIsExpanded(isExpanded)
}
}
diff --git a/photopicker/src/com/android/photopicker/core/events/Event.kt b/photopicker/src/com/android/photopicker/core/events/Event.kt
index dcb3be568..3dfb854a6 100644
--- a/photopicker/src/com/android/photopicker/core/events/Event.kt
+++ b/photopicker/src/com/android/photopicker/core/events/Event.kt
@@ -16,6 +16,9 @@
package com.android.photopicker.core.events
+import com.android.photopicker.core.banners.BannerDeclaration
+import com.android.photopicker.core.banners.BannerDefinitions
+import com.android.photopicker.data.model.Group
import com.android.providers.media.MediaProviderStatsLog
/* Convenience alias for classes that implement [Event] */
@@ -105,14 +108,21 @@ interface Event {
val uiEvent: Telemetry.UiEvent
) : Event
+ data class LogPhotopickerAlbumOpenedUIEvent(
+ override val dispatcherToken: String,
+ val sessionId: Int,
+ val packageUid: Int,
+ val albumOpened: Group.Album
+ ) : Event
+
/** Details out the information of a picker media item */
data class ReportPhotopickerMediaItemStatus(
override val dispatcherToken: String,
val sessionId: Int,
val mediaStatus: Telemetry.MediaStatus,
- val mediaLocation: Telemetry.MediaLocation,
+ val selectionSource: Telemetry.MediaLocation,
val itemPosition: Int,
- val selectedAlbum: Telemetry.SelectedAlbum,
+ val selectedAlbum: Group.Album?,
val mediaType: Telemetry.MediaType,
val cloudOnly: Boolean,
val pickerSize: Telemetry.PickerSize
@@ -228,6 +238,7 @@ interface Telemetry {
/*
Number of items allowed to be picked
*/
+ @Suppress("ktlint:standard:max-line-length")
enum class PickerSelection(val selection: Int) {
SINGLE(
MediaProviderStatsLog
@@ -236,6 +247,10 @@ interface Telemetry {
MULTIPLE(
MediaProviderStatsLog
.PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_PERMITTED_SELECTION__MULTIPLE
+ ),
+ UNSET_PICKER_SELECTION(
+ MediaProviderStatsLog
+ .PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_PERMITTED_SELECTION__UNSET_PICKER_PERMITTED_SELECTION
)
}
@@ -248,7 +263,11 @@ interface Telemetry {
PRIVATE_SPACE(
MediaProviderStatsLog.PHOTOPICKER_SESSION_INFO_REPORTED__USER_PROFILE__PRIVATE_SPACE
),
- UNKNOWN(MediaProviderStatsLog.PHOTOPICKER_SESSION_INFO_REPORTED__USER_PROFILE__UNKNOWN)
+ UNKNOWN(MediaProviderStatsLog.PHOTOPICKER_SESSION_INFO_REPORTED__USER_PROFILE__UNKNOWN),
+ UNSET_USER_PROFILE(
+ MediaProviderStatsLog
+ .PHOTOPICKER_SESSION_INFO_REPORTED__USER_PROFILE__UNSET_USER_PROFILE
+ )
}
/*
@@ -257,7 +276,13 @@ interface Telemetry {
enum class PickerStatus(val status: Int) {
OPENED(MediaProviderStatsLog.PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_STATUS__OPENED),
CANCELED(MediaProviderStatsLog.PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_STATUS__CANCELED),
- CONFIRMED(MediaProviderStatsLog.PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_STATUS__CONFIRMED)
+ CONFIRMED(
+ MediaProviderStatsLog.PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_STATUS__CONFIRMED
+ ),
+ UNSET_PICKER_STATUS(
+ MediaProviderStatsLog
+ .PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_STATUS__UNSET_PICKER_STATUS
+ )
}
/*
@@ -274,6 +299,9 @@ interface Telemetry {
PERMISSION_MODE_PICKER(
MediaProviderStatsLog
.PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_MODE__PERMISSION_MODE_PICKER
+ ),
+ UNSET_PICKER_MODE(
+ MediaProviderStatsLog.PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_MODE__UNSET_PICKER_MODE
)
}
@@ -292,6 +320,14 @@ interface Telemetry {
BACK_BUTTON(
MediaProviderStatsLog
.PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_CLOSE_METHOD__BACK_BUTTON
+ ),
+ SELECTION_CONFIRMED(
+ MediaProviderStatsLog
+ .PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_CLOSE_METHOD__PICKER_SELECTION_CONFIRMED
+ ),
+ UNSET_PICKER_CLOSE_METHOD(
+ MediaProviderStatsLog
+ .PHOTOPICKER_SESSION_INFO_REPORTED__PICKER_CLOSE_METHOD__UNSET_PICKER_CLOSE_METHOD
)
}
@@ -300,7 +336,10 @@ interface Telemetry {
*/
enum class PickerSize(val size: Int) {
COLLAPSED(MediaProviderStatsLog.PHOTOPICKER_API_INFO_REPORTED__SCREEN_SIZE__COLLAPSED),
- EXPANDED(MediaProviderStatsLog.PHOTOPICKER_API_INFO_REPORTED__SCREEN_SIZE__EXPANDED)
+ EXPANDED(MediaProviderStatsLog.PHOTOPICKER_API_INFO_REPORTED__SCREEN_SIZE__EXPANDED),
+ UNSET_PICKER_SIZE(
+ MediaProviderStatsLog.PHOTOPICKER_API_INFO_REPORTED__SCREEN_SIZE__UNSET_PICKER_SIZE
+ )
}
/*
@@ -314,6 +353,14 @@ interface Telemetry {
ACTION_GET_CONTENT(
MediaProviderStatsLog
.PHOTOPICKER_API_INFO_REPORTED__PICKER_INTENT_ACTION__ACTION_GET_CONTENT
+ ),
+ ACTION_USER_SELECT(
+ MediaProviderStatsLog
+ .PHOTOPICKER_API_INFO_REPORTED__PICKER_INTENT_ACTION__ACTION_USER_SELECT
+ ),
+ UNSET_PICKER_INTENT_ACTION(
+ MediaProviderStatsLog
+ .PHOTOPICKER_API_INFO_REPORTED__PICKER_INTENT_ACTION__UNSET_PICKER_INTENT_ACTION
)
}
@@ -323,11 +370,18 @@ interface Telemetry {
enum class MediaType(val type: Int) {
PHOTO(MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_TYPE__PHOTO),
VIDEO(MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_TYPE__VIDEO),
+ PHOTO_VIDEO(
+ MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_TYPE__PHOTO_VIDEO
+ ),
GIF(MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_TYPE__GIF),
LIVE_PHOTO(
MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_TYPE__LIVE_PHOTO
),
- OTHER(MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_TYPE__OTHER)
+ OTHER(MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_TYPE__OTHER),
+ UNSET_MEDIA_TYPE(
+ MediaProviderStatsLog
+ .PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_TYPE__UNSET_MEDIA_TYPE
+ )
}
/*
@@ -336,7 +390,10 @@ interface Telemetry {
enum class SelectedTab(val tab: Int) {
PHOTOS(MediaProviderStatsLog.PHOTOPICKER_API_INFO_REPORTED__SELECTED_TAB__PHOTOS),
ALBUMS(MediaProviderStatsLog.PHOTOPICKER_API_INFO_REPORTED__SELECTED_TAB__ALBUMS),
- COLLECTIONS(MediaProviderStatsLog.PHOTOPICKER_API_INFO_REPORTED__SELECTED_TAB__COLLECTIONS)
+ COLLECTIONS(MediaProviderStatsLog.PHOTOPICKER_API_INFO_REPORTED__SELECTED_TAB__COLLECTIONS),
+ UNSET_SELECTED_TAB(
+ MediaProviderStatsLog.PHOTOPICKER_API_INFO_REPORTED__SELECTED_TAB__UNSET_SELECTED_TAB
+ )
}
/*
@@ -355,6 +412,10 @@ interface Telemetry {
),
UNDEFINED_CLOUD(
MediaProviderStatsLog.PHOTOPICKER_API_INFO_REPORTED__SELECTED_ALBUM__UNDEFINED_CLOUD
+ ),
+ UNSET_SELECTED_ALBUM(
+ MediaProviderStatsLog
+ .PHOTOPICKER_API_INFO_REPORTED__SELECTED_ALBUM__UNSET_SELECTED_ALBUM
)
}
@@ -462,7 +523,8 @@ interface Telemetry {
),
SELECT_SEARCH_CATEGORY(
MediaProviderStatsLog.PHOTOPICKER_UIEVENT_LOGGED__UI_EVENT__SELECT_SEARCH_CATEGORY
- )
+ ),
+ UNSET_UI_EVENT(MediaProviderStatsLog.PHOTOPICKER_UIEVENT_LOGGED__UI_EVENT__UNSET_UI_EVENT)
}
/*
@@ -474,6 +536,10 @@ interface Telemetry {
),
UNSELECTED(
MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_STATUS__UNSELECTED
+ ),
+ UNSET_MEDIA_STATUS(
+ MediaProviderStatsLog
+ .PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_STATUS__UNSET_MEDIA_STATUS
)
}
@@ -485,7 +551,11 @@ interface Telemetry {
MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_LOCATION__MAIN_GRID
),
ALBUM(MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_LOCATION__ALBUM),
- GROUP(MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_LOCATION__GROUP)
+ GROUP(MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_LOCATION__GROUP),
+ UNSET_MEDIA_LOCATION(
+ MediaProviderStatsLog
+ .PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED__MEDIA_LOCATION__UNSET_MEDIA_LOCATION
+ )
}
/*
@@ -497,16 +567,25 @@ interface Telemetry {
),
LONG_PRESS(
MediaProviderStatsLog.PHOTOPICKER_PREVIEW_INFO_LOGGED__PREVIEW_MODE_ENTRY__LONG_PRESS
+ ),
+ UNSET_PREVIEW_MODE_ENTRY(
+ MediaProviderStatsLog
+ .PHOTOPICKER_PREVIEW_INFO_LOGGED__PREVIEW_MODE_ENTRY__UNSET_PREVIEW_MODE_ENTRY
)
}
/*
Defines different video playback user interactions
*/
+ @Suppress("ktlint:standard:max-line-length")
enum class VideoPlayBackInteractions(val interaction: Int) {
PLAY(MediaProviderStatsLog.PHOTOPICKER_PREVIEW_INFO_LOGGED__VIDEO_INTERACTIONS__PLAY),
PAUSE(MediaProviderStatsLog.PHOTOPICKER_PREVIEW_INFO_LOGGED__VIDEO_INTERACTIONS__PAUSE),
- MUTE(MediaProviderStatsLog.PHOTOPICKER_PREVIEW_INFO_LOGGED__VIDEO_INTERACTIONS__MUTE)
+ MUTE(MediaProviderStatsLog.PHOTOPICKER_PREVIEW_INFO_LOGGED__VIDEO_INTERACTIONS__MUTE),
+ UNSET_VIDEO_PLAYBACK_INTERACTION(
+ MediaProviderStatsLog
+ .PHOTOPICKER_PREVIEW_INFO_LOGGED__VIDEO_INTERACTIONS__UNSET_VIDEO_PLAYBACK_INTERACTION
+ )
}
/*
@@ -519,6 +598,10 @@ interface Telemetry {
CLOUD_SETTINGS(
MediaProviderStatsLog
.PHOTOPICKER_MENU_INTERACTION_LOGGED__MENU_ITEM_SELECTED__CLOUD_SETTINGS
+ ),
+ UNSET_MENU_ITEM_SELECTED(
+ MediaProviderStatsLog
+ .PHOTOPICKER_MENU_INTERACTION_LOGGED__MENU_ITEM_SELECTED__UNSET_MENU_ITEM_SELECTED
)
}
@@ -539,7 +622,35 @@ interface Telemetry {
),
CHOOSE_APP(
MediaProviderStatsLog.PHOTOPICKER_BANNER_INTERACTION_LOGGED__BANNER_TYPE__CHOOSE_APP
- )
+ ),
+ UNSET_BANNER_TYPE(
+ MediaProviderStatsLog
+ .PHOTOPICKER_BANNER_INTERACTION_LOGGED__BANNER_TYPE__UNSET_BANNER_TYPE
+ );
+
+ companion object {
+
+ /**
+ * Attempts to map a [BannerDeclaration] to the [BannerType] enum for logging banner
+ * related data. At worst, will return [UNSET_BANNER_TYPE] for a banner without a
+ * mapping.
+ *
+ * @param declaration The [BannerDeclaration] to convert to a [BannerType]
+ * @return The corresponding [BannerType] or [UNSET_BANNER_TYPE] if a mapping isn't
+ * found.
+ */
+ fun fromBannerDeclaration(declaration: BannerDeclaration): BannerType {
+ return when (declaration.id) {
+ BannerDefinitions.CLOUD_CHOOSE_ACCOUNT.id -> BannerType.CHOOSE_ACCOUNT
+ BannerDefinitions.CLOUD_CHOOSE_PROVIDER.id -> BannerType.CHOOSE_APP
+ BannerDefinitions.CLOUD_MEDIA_AVAILABLE.id -> BannerType.CLOUD_MEDIA_AVAILABLE
+ BannerDefinitions.CLOUD_UPDATED_ACCOUNT.id -> BannerType.ACCOUNT_UPDATED
+ // TODO(b/357010907): add a BannerType enum for the PRIVACY_EXPLAINER
+ BannerDefinitions.PRIVACY_EXPLAINER.id -> BannerType.UNSET_BANNER_TYPE
+ else -> BannerType.UNSET_BANNER_TYPE
+ }
+ }
+ }
}
/*
@@ -558,6 +669,10 @@ interface Telemetry {
CLICK_BANNER(
MediaProviderStatsLog
.PHOTOPICKER_BANNER_INTERACTION_LOGGED__USER_BANNER_INTERACTION__CLICK_BANNER
+ ),
+ UNSET_BANNER_INTERACTION(
+ MediaProviderStatsLog
+ .PHOTOPICKER_BANNER_INTERACTION_LOGGED__BANNER_TYPE__UNSET_BANNER_TYPE
)
}
@@ -574,6 +689,10 @@ interface Telemetry {
SUGGESTED_SEARCHES(
MediaProviderStatsLog
.PHOTOPICKER_SEARCH_INFO_REPORTED__SEARCH_METHOD__SUGGESTED_SEARCHES
+ ),
+ UNSET_SEARCH_METHOD(
+ MediaProviderStatsLog
+ .PHOTOPICKER_SEARCH_INFO_REPORTED__SEARCH_METHOD__UNSET_SEARCH_METHOD
)
}
}
diff --git a/photopicker/src/com/android/photopicker/core/events/PhotopickerEventLogger.kt b/photopicker/src/com/android/photopicker/core/events/PhotopickerEventLogger.kt
index 9e44f5745..b547d9692 100644
--- a/photopicker/src/com/android/photopicker/core/events/PhotopickerEventLogger.kt
+++ b/photopicker/src/com/android/photopicker/core/events/PhotopickerEventLogger.kt
@@ -16,8 +16,18 @@
package com.android.photopicker.core.events
+import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA
+import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_DOWNLOADS
+import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_FAVORITES
+import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_SCREENSHOTS
+import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS
+import android.util.Log
import com.android.photopicker.core.Background
+import com.android.photopicker.data.DataService
+import com.android.photopicker.data.model.Group
+import com.android.photopicker.data.model.MediaSource
import com.android.providers.media.MediaProviderStatsLog
+import dagger.Lazy
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -26,7 +36,30 @@ import kotlinx.coroutines.launch
* Photopicker telemetry class which intercepts the incoming events dispatched by various components
* and maps them to their respective logging proto. All the logging occurs in background scope.
*/
-class PhotopickerEventLogger {
+class PhotopickerEventLogger(val dataService: Lazy<DataService>) {
+
+ private val TAG = "PhotopickerEventLogger"
+
+ /** Maps album id to the corresponding selected album enum values */
+ private val mapAlbumIdToSelectedAlbum =
+ hashMapOf(
+ ALBUM_ID_CAMERA to Telemetry.SelectedAlbum.CAMERA,
+ ALBUM_ID_SCREENSHOTS to Telemetry.SelectedAlbum.SCREENSHOTS,
+ ALBUM_ID_FAVORITES to Telemetry.SelectedAlbum.FAVOURITES,
+ ALBUM_ID_VIDEOS to Telemetry.SelectedAlbum.VIDEOS,
+ ALBUM_ID_DOWNLOADS to Telemetry.SelectedAlbum.DOWNLOADS
+ )
+
+ /** Maps album id to the corresponding selected album enum values */
+ private val mapAlbumIdToAlbumOpened =
+ hashMapOf(
+ ALBUM_ID_CAMERA to Telemetry.UiEvent.ALBUM_CAMERA_OPEN,
+ ALBUM_ID_SCREENSHOTS to Telemetry.UiEvent.ALBUM_SCREENSHOTS_OPEN,
+ ALBUM_ID_FAVORITES to Telemetry.UiEvent.ALBUM_FAVOURITES_OPEN,
+ ALBUM_ID_VIDEOS to Telemetry.UiEvent.ALBUM_VIDEOS_OPEM,
+ ALBUM_ID_DOWNLOADS to Telemetry.UiEvent.ALBUM_DOWNLOADS_OPEN
+ )
+
fun start(
scope: CoroutineScope,
@Background backgroundDispatcher: CoroutineDispatcher,
@@ -69,20 +102,56 @@ class PhotopickerEventLogger {
}
is Event.LogPhotopickerUIEvent -> {
MediaProviderStatsLog.write(
- MediaProviderStatsLog.UI_EVENT_REPORTED,
+ MediaProviderStatsLog.PHOTOPICKER_UI_EVENT_LOGGED,
event.sessionId,
event.packageUid,
event.uiEvent.event
)
}
+ is Event.LogPhotopickerAlbumOpenedUIEvent -> {
+ val album = event.albumOpened
+ val albumOpened =
+ mapAlbumIdToAlbumOpened.getOrDefault(
+ album.id,
+ when (getAlbumDataSource(album)) {
+ MediaSource.REMOTE -> Telemetry.UiEvent.ALBUM_FROM_CLOUD_OPEN
+ // TODO replace with LOCAL value once added
+ MediaSource.LOCAL -> Telemetry.UiEvent.ALBUM_FROM_CLOUD_OPEN
+ }
+ )
+ MediaProviderStatsLog.write(
+ MediaProviderStatsLog.PHOTOPICKER_UI_EVENT_LOGGED,
+ event.sessionId,
+ event.packageUid,
+ albumOpened.event
+ )
+ }
is Event.ReportPhotopickerMediaItemStatus -> {
+ val mediaAlbum = event.selectedAlbum
+ val selectedAlbum: Telemetry.SelectedAlbum =
+ if (
+ event.selectionSource == Telemetry.MediaLocation.ALBUM &&
+ mediaAlbum != null
+ ) {
+ mapAlbumIdToSelectedAlbum.getOrDefault(
+ mediaAlbum.id,
+ when (getAlbumDataSource(mediaAlbum)) {
+ MediaSource.REMOTE ->
+ Telemetry.SelectedAlbum.UNDEFINED_CLOUD
+ MediaSource.LOCAL -> Telemetry.SelectedAlbum.UNDEFINED_LOCAL
+ }
+ )
+ } else {
+ Telemetry.SelectedAlbum.UNSET_SELECTED_ALBUM
+ }
+
MediaProviderStatsLog.write(
MediaProviderStatsLog.PHOTOPICKER_MEDIA_ITEM_STATUS_REPORTED,
event.sessionId,
event.mediaStatus.status,
- event.mediaLocation.location,
+ event.selectionSource.location,
event.itemPosition,
- event.selectedAlbum.album,
+ selectedAlbum.album,
event.mediaType.type,
event.cloudOnly,
event.pickerSize.size
@@ -189,4 +258,26 @@ class PhotopickerEventLogger {
}
}
}
+
+ /**
+ * Fetch the data source of the album by matching it against the authority of the provider so
+ * that we do not have to depend on glide's internal implementation(by using
+ * album.getDataSource()) to fetch the album's data source
+ */
+ private fun getAlbumDataSource(album: Group.Album): MediaSource {
+ for (provider in dataService.get().availableProviders.value) {
+ if (provider.authority == album.authority) {
+ return provider.mediaSource
+ }
+ }
+ Log.w(
+ TAG,
+ "Unable to find an authority match with any provider for album " +
+ album.displayName +
+ " with authority " +
+ album.authority +
+ " while fetching the album data source"
+ )
+ return MediaSource.LOCAL
+ }
}
diff --git a/photopicker/src/com/android/photopicker/core/events/SessionId.kt b/photopicker/src/com/android/photopicker/core/events/SessionId.kt
new file mode 100644
index 000000000..2c612edb5
--- /dev/null
+++ b/photopicker/src/com/android/photopicker/core/events/SessionId.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photopicker.core.events
+
+import java.security.SecureRandom
+
+// The sessionId can contain at most 20 bits which gives ~1M possibilities for the same, so ~0.5%
+// collision probability in 100 values
+const val MAX_SESSION_ID: Int = 1 shl 20
+
+/**
+ * Generates a random integer between 1 and [MAX_SESSION_ID] to identify a particular photopicker
+ * session. The id gets attached to all the picker atoms so that it is easy to identify logs that
+ * are session specific.
+ *
+ * @return photopicker sessionId
+ */
+fun generatePickerSessionId(): Int {
+ val getRandom = SecureRandom()
+ return 1 + getRandom.nextInt(MAX_SESSION_ID)
+}
diff --git a/photopicker/src/com/android/photopicker/core/features/FeatureManager.kt b/photopicker/src/com/android/photopicker/core/features/FeatureManager.kt
index 42d5186eb..74c8168c8 100644
--- a/photopicker/src/com/android/photopicker/core/features/FeatureManager.kt
+++ b/photopicker/src/com/android/photopicker/core/features/FeatureManager.kt
@@ -36,7 +36,6 @@ import com.android.photopicker.features.snackbar.SnackbarFeature
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.drop
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
/**
@@ -95,6 +94,7 @@ class FeatureManager(
Event.ReportPhotopickerSessionInfo::class.java,
Event.ReportPhotopickerApiInfo::class.java,
Event.LogPhotopickerUIEvent::class.java,
+ Event.LogPhotopickerAlbumOpenedUIEvent::class.java,
Event.ReportPhotopickerMediaItemStatus::class.java,
Event.LogPhotopickerPreviewInfo::class.java,
Event.LogPhotopickerMenuInteraction::class.java,
diff --git a/photopicker/src/com/android/photopicker/core/selection/GrantsAwareSelectionImpl.kt b/photopicker/src/com/android/photopicker/core/selection/GrantsAwareSelectionImpl.kt
index e0bb21286..3fa4a421f 100644
--- a/photopicker/src/com/android/photopicker/core/selection/GrantsAwareSelectionImpl.kt
+++ b/photopicker/src/com/android/photopicker/core/selection/GrantsAwareSelectionImpl.kt
@@ -16,6 +16,7 @@
package com.android.photopicker.core.selection
+import android.util.Log
import androidx.annotation.GuardedBy
import com.android.photopicker.core.configuration.PhotopickerConfiguration
import com.android.photopicker.core.selection.SelectionModifiedResult.FAILURE_SELECTION_LIMIT_EXCEEDED
@@ -26,8 +27,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -55,17 +58,20 @@ import kotlinx.coroutines.sync.withLock
* @property scope A [CoroutineScope] that the flow is shared and updated in.
* @property initialSelection A collection to include initial selection value.
* @property configuration a collectable [StateFlow] of configuration changes
- * @property preGrantedItemsCount represents the total number of grants help by the current package.
+ * @property preGrantedItemsCount represents the flow for total number of grants help by the current
+ * package.
*/
class GrantsAwareSelectionImpl<T : Grantable>(
val scope: CoroutineScope,
val initialSelection: Collection<T>? = null,
private val configuration: StateFlow<PhotopickerConfiguration>,
- val preGrantedItemsCount: Int = 0,
+ private val preGrantedItemsCount: StateFlow<Int?>,
) : Selection<T> {
+ private val TAG = "GrantsAwareSelection"
// An internal mutex is used to enforce thread-safe access of the selection set.
private val mutex = Mutex()
+
private val _deSelection: LinkedHashSet<T> = LinkedHashSet()
private val _selection: LinkedHashSet<T> = LinkedHashSet()
@@ -73,10 +79,24 @@ class GrantsAwareSelectionImpl<T : Grantable>(
override val flow: StateFlow<GrantsAwareSet<T>>
init {
+ scope.launch {
+ // Observe the refresh of the stateFlow that holds the count of pre-granted media.
+ // Note that this will always be null in case the intent action is anything other than
+ // [MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP].
+ preGrantedItemsCount
+ .filter { it != null }
+ .collect {
+ Log.i(TAG, "Received notification for preGranted media count. ")
+ updateFlow()
+ }
+ }
if (initialSelection != null) {
_selection.addAll(initialSelection)
}
- _flow = MutableStateFlow(GrantsAwareSet(_selection, _deSelection, preGrantedItemsCount))
+ _flow =
+ MutableStateFlow(
+ GrantsAwareSet(_selection, _deSelection, preGrantedItemsCount.value ?: 0)
+ )
flow =
_flow.stateIn(
scope,
@@ -134,8 +154,8 @@ class GrantsAwareSelectionImpl<T : Grantable>(
val itemsWithPregrants = LinkedHashSet<T>()
val itemsToAdd = LinkedHashSet<T>()
- for (item in items){
- if (item.isPreGranted){
+ for (item in items) {
+ if (item.isPreGranted) {
itemsWithPregrants.add(item)
} else {
itemsToAdd.add(item)
@@ -153,7 +173,8 @@ class GrantsAwareSelectionImpl<T : Grantable>(
}
}
- /** Empties the current selection of objects, returning the selection to an empty state.
+ /**
+ * Empties the current selection of objects, returning the selection to an empty state.
*
* Also, any pre-granted item that was de-selected will now reset i.e. no grants will be
* revoked.
@@ -171,8 +192,7 @@ class GrantsAwareSelectionImpl<T : Grantable>(
@GuardedBy("mutex")
override suspend fun contains(item: T): Boolean {
return mutex.withLock {
- _selection.contains(item) ||
- (item.isPreGranted && !_deSelection.contains(item))
+ _selection.contains(item) || (item.isPreGranted && !_deSelection.contains(item))
}
}
@@ -203,6 +223,7 @@ class GrantsAwareSelectionImpl<T : Grantable>(
/**
* Removes the requested item from the selection. If the item is not in the selection, this has
* no effect. Afterwards, will emit the new selection into the exposed flow.
+ *
* @return [SelectionModifiedResult] of the outcome of the removal.
*/
@GuardedBy("mutex")
@@ -224,6 +245,7 @@ class GrantsAwareSelectionImpl<T : Grantable>(
*
* If one or more items are not present in the selection, this has no effect. Afterwards, will
* emit the new selection into the exposed flow.
+ *
* @return [SelectionModifiedResult] of the outcome of the removal.
*/
@GuardedBy("mutex")
@@ -231,8 +253,7 @@ class GrantsAwareSelectionImpl<T : Grantable>(
return mutex.withLock {
_selection.removeAll(items)
for (item in items) {
- if (item.isPreGranted)
- _deSelection.add(item)
+ if (item.isPreGranted) _deSelection.add(item)
}
updateFlow()
SUCCESS
@@ -249,7 +270,11 @@ class GrantsAwareSelectionImpl<T : Grantable>(
override suspend fun snapshot(): Set<T> {
return mutex.withLock {
// Create a new [grantsSet] to emit updated values.
- GrantsAwareSet(_selection.toSet(), _deSelection.toSet(), preGrantedItemsCount)
+ GrantsAwareSet(
+ _selection.toSet(),
+ _deSelection.toSet(),
+ preGrantedItemsCount.value ?: 0
+ )
}
}
@@ -260,8 +285,8 @@ class GrantsAwareSelectionImpl<T : Grantable>(
* such an item is toggled, if it is not part of _deSelection then it is added to _deselection
* otherwise removed from it.
*
- * For non preGranted items: if the item is already in the selection, it is removed.
- * If the item is not in the selection, it is added.
+ * For non preGranted items: if the item is already in the selection, it is removed. If the item
+ * is not in the selection, it is added.
*
* Afterwards, will emit the new selection into the exposed flow.
*
@@ -354,14 +379,12 @@ class GrantsAwareSelectionImpl<T : Grantable>(
*/
@GuardedBy("mutex")
override suspend fun getDeselection(): Collection<T> {
- return mutex.withLock {
- _deSelection.toSet()
- }
+ return mutex.withLock { _deSelection.toSet() }
}
/** Internal method that snapshots the current selection and emits it to the exposed flow. */
private suspend fun updateFlow() {
- _flow.update { GrantsAwareSet(_selection, _deSelection, preGrantedItemsCount) }
+ _flow.update { GrantsAwareSet(_selection, _deSelection, preGrantedItemsCount.value ?: 0) }
}
/**
diff --git a/photopicker/src/com/android/photopicker/core/selection/GrantsAwareSet.kt b/photopicker/src/com/android/photopicker/core/selection/GrantsAwareSet.kt
index 69080a1ac..dafa93c4e 100644
--- a/photopicker/src/com/android/photopicker/core/selection/GrantsAwareSet.kt
+++ b/photopicker/src/com/android/photopicker/core/selection/GrantsAwareSet.kt
@@ -21,9 +21,8 @@ import com.android.photopicker.data.model.Grantable
/**
* A specialized set implementation that is aware of both user selections and pre-granted elements.
*
- * This class extends the behavior of a standard set by incorporating pre-granted elements
- * into its logic. An element is considered to be part of the set if either:
- *
+ * This class extends the behavior of a standard set by incorporating pre-granted elements into its
+ * logic. An element is considered to be part of the set if either:
* 1. It has been explicitly selected by the user.
* 2. It is pre-granted and hasn't been explicitly de-selected by the user.
*
@@ -33,32 +32,28 @@ import com.android.photopicker.data.model.Grantable
*
* @property selection The set of elements explicitly selected by the user.
* @property deSelection The set of pre-granted elements that have been explicitly de-selected.
- * @property preGrantedelementsCount The number of pre-granted elements (not including those in `deSelection`).
+ * @property preGrantedElementsCount The number of pre-granted elements (not including those in
+ * `deSelection`).
*/
class GrantsAwareSet<T : Grantable>(
val selection: Set<T>,
val deSelection: Set<T>,
- val preGrantedelementsCount: Int = 0,
+ private val preGrantedElementsCount: Int = 0,
) : Set<T> {
- /**
- * Size of the set based on current selection and preGranted elements.
- */
- override val size: Int = selection.size - deSelection.size + preGrantedelementsCount
+ /** Size of the set based on current selection and preGranted elements. */
+ override val size: Int = selection.size - deSelection.size + preGrantedElementsCount
/**
* Checks if the set contains a specific element.
*
* This implementation considers two scenarios:
- *
* 1. **Direct Presence in the Selection:**
- * - Returns `true` if the `element` is directly present in the current user selection.
- *
+ * - Returns `true` if the `element` is directly present in the current user selection.
* 2. **Pre-Granted Media:**
- * - If the `element` is a `Media` object:
- * - Returns `true` if the `Media` is pre-granted (via `isPreGranted()`) AND
- * it is not present in the deSelection set (i.e., the user has not explicitly
- * de-selected it).
+ * - If the `element` is a `Media` object:
+ * - Returns `true` if the `Media` is pre-granted (via `isPreGranted()`) AND it is not
+ * present in the deSelection set (i.e., the user has not explicitly de-selected it).
*
* @param element The element to check for.
* @return `true` if the element is considered to be in the set, `false` otherwise.
@@ -97,9 +92,7 @@ class GrantsAwareSet<T : Grantable>(
return selection.iterator()
}
- /**
- * Checks if all elements provided in the input are present in the set.
- */
+ /** Checks if all elements provided in the input are present in the set. */
override fun containsAll(elements: Collection<T>): Boolean {
for (element in elements) {
if (!contains(element)) {
@@ -108,4 +101,4 @@ class GrantsAwareSet<T : Grantable>(
}
return true
}
-} \ No newline at end of file
+}
diff --git a/photopicker/src/com/android/photopicker/core/theme/AccentColorHelper.kt b/photopicker/src/com/android/photopicker/core/theme/AccentColorHelper.kt
index d71a59752..30fa7c535 100644
--- a/photopicker/src/com/android/photopicker/core/theme/AccentColorHelper.kt
+++ b/photopicker/src/com/android/photopicker/core/theme/AccentColorHelper.kt
@@ -135,4 +135,9 @@ class AccentColorHelper(val inputColor: Long) {
fun getTextColorForAccentComponents(): Color {
return textColorForAccentComponents
}
+
+ /** Indicates that a valid accent color is used for the photopicker theme */
+ fun isValidAccentColorSet(): Boolean {
+ return accentColor != Color.Unspecified
+ }
}
diff --git a/photopicker/src/com/android/photopicker/core/theme/AccentColorScheme.kt b/photopicker/src/com/android/photopicker/core/theme/AccentColorScheme.kt
index 798a615a4..cbcd0ec1c 100644
--- a/photopicker/src/com/android/photopicker/core/theme/AccentColorScheme.kt
+++ b/photopicker/src/com/android/photopicker/core/theme/AccentColorScheme.kt
@@ -23,9 +23,7 @@ import androidx.compose.ui.graphics.isUnspecified
/** CompositionLocal used to pass [AccentColorScheme] down the tree. */
val CustomAccentColorScheme =
staticCompositionLocalOf<AccentColorScheme> {
- throw IllegalStateException(
- "No CustomAccentColorScheme configured."
- )
+ throw IllegalStateException("No CustomAccentColorScheme configured.")
}
/**
@@ -51,6 +49,13 @@ class AccentColorScheme(accentColorHelper: AccentColorHelper) {
}
/**
+ * Returns if an accentColor is defined for this color scheme.
+ *
+ * @return true if [accentColor] is a defined color.
+ */
+ fun isAccentColorDefined() = !accentColor.isUnspecified
+
+ /**
* Returns the appropriate text color for components using the accent color as the background
* which has been passed as an input in the picker intent.
*
diff --git a/photopicker/src/com/android/photopicker/core/theme/PhotopickerTheme.kt b/photopicker/src/com/android/photopicker/core/theme/PhotopickerTheme.kt
index 466cdc3b1..44c3ebd25 100644
--- a/photopicker/src/com/android/photopicker/core/theme/PhotopickerTheme.kt
+++ b/photopicker/src/com/android/photopicker/core/theme/PhotopickerTheme.kt
@@ -76,8 +76,28 @@ fun PhotopickerTheme(
val colorScheme =
remember(isDarkTheme) {
when (isDarkTheme) {
- true -> darkTheme
- false -> lightTheme
+ true ->
+ if (accentColorHelper.getAccentColor().isUnspecified) {
+ darkTheme
+ } else {
+ // When an accent color has been specified, set primary and onPrimary
+ // in the theme to use the accent color.
+ darkTheme.copy(
+ primary = accentColorHelper.getAccentColor(),
+ onPrimary = accentColorHelper.getTextColorForAccentComponents()
+ )
+ }
+ false ->
+ if (accentColorHelper.getAccentColor().isUnspecified) {
+ lightTheme
+ } else {
+ lightTheme.copy(
+ // When an accent color has been specified, set primary and onPrimary
+ // in the theme to use the accent color.
+ primary = accentColorHelper.getAccentColor(),
+ onPrimary = accentColorHelper.getTextColorForAccentComponents()
+ )
+ }
}
}
val fixedAccentColors =
diff --git a/photopicker/src/com/android/photopicker/data/DataService.kt b/photopicker/src/com/android/photopicker/data/DataService.kt
index 457e0e969..70bccb354 100644
--- a/photopicker/src/com/android/photopicker/data/DataService.kt
+++ b/photopicker/src/com/android/photopicker/data/DataService.kt
@@ -41,6 +41,9 @@ interface DataService {
/** A [StateFlow] with a list of available [Provider]-s. */
val availableProviders: StateFlow<List<Provider>>
+ /** Count of all preGranted media for the current package and userID. */
+ val preGrantedMediaCount: StateFlow<Int?>
+
/**
* A [Channel] that emits a [Unit] when a disruptive data change is observed in the backend. The
* UI can treat this emission as a signal to reset the UI.
@@ -105,4 +108,7 @@ interface DataService {
* @return The [CollectionInfo] of the given [Provider].
*/
suspend fun getCollectionInfo(provider: Provider): CollectionInfo
+
+ /** Refreshes the [preGrantedMediaCount] with the latest value in the data source. */
+ fun refreshPreGrantedItemsCount()
}
diff --git a/photopicker/src/com/android/photopicker/data/DataServiceImpl.kt b/photopicker/src/com/android/photopicker/data/DataServiceImpl.kt
index e6e9f0257..ccc95b8b2 100644
--- a/photopicker/src/com/android/photopicker/data/DataServiceImpl.kt
+++ b/photopicker/src/com/android/photopicker/data/DataServiceImpl.kt
@@ -23,9 +23,11 @@ import android.content.pm.ResolveInfo
import android.database.ContentObserver
import android.net.Uri
import android.provider.CloudMediaProviderContract
+import android.provider.MediaStore
import android.util.Log
import androidx.paging.PagingSource
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.events.Events
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.user.UserStatus
import com.android.photopicker.data.model.CloudMediaProviderDetails
@@ -83,13 +85,18 @@ class DataServiceImpl(
private val mediaProviderClient: MediaProviderClient,
private val config: StateFlow<PhotopickerConfiguration>,
private val featureManager: FeatureManager,
- private val appContext: Context
+ private val appContext: Context,
+ private val events: Events,
) : DataService {
private val _activeContentResolver =
MutableStateFlow<ContentResolver>(userStatus.value.activeContentResolver)
- // Keep track of the photo grid media and album grid paging source so that we can invalidate
- // them in case the underlying data changes.
+ // Here default value being null signifies that the look up for the grants has not happened yet.
+ // Use [refreshPreGrantedItemsCount] to populate this with the latest value.
+ private var _preGrantedMediaCount: MutableStateFlow<Int?> = MutableStateFlow(null)
+
+ // Keep track of the photo grid media, album grid and preview media paging sources so that we
+ // can invalidate them in case the underlying data changes.
private val mediaPagingSources: MutableList<MediaPagingSource> = mutableListOf()
private val albumPagingSources: MutableList<AlbumPagingSource> = mutableListOf()
@@ -181,6 +188,12 @@ class DataServiceImpl(
override val disruptiveDataUpdateChannel = Channel<Unit>(CONFLATED)
+ /**
+ * Same as [_preGrantedMediaCount] but as an immutable StateFlow. The count contains the latest
+ * value set during the most recent [refreshPreGrantedItemsCount] call.
+ */
+ override val preGrantedMediaCount: StateFlow<Int?> = _preGrantedMediaCount
+
companion object {
const val FLOW_TIMEOUT_MILLI_SECONDS: Long = 5000
}
@@ -394,6 +407,7 @@ class DataServiceImpl(
mediaProviderClient,
dispatcher,
config.value,
+ events,
)
Log.v(
@@ -420,6 +434,7 @@ class DataServiceImpl(
mediaProviderClient,
dispatcher,
config.value,
+ events,
)
Log.v(
@@ -448,6 +463,7 @@ class DataServiceImpl(
mediaProviderClient,
dispatcher,
config.value,
+ events,
)
Log.v(DataService.TAG, "Created a media paging source that queries $availableProviders")
@@ -460,8 +476,35 @@ class DataServiceImpl(
override fun previewMediaPagingSource(
currentSelection: Set<Media>,
currentDeselection: Set<Media>
- ): PagingSource<MediaPageKey, Media> =
- throw NotImplementedError("This method is not implemented yet.")
+ ): PagingSource<MediaPageKey, Media> = runBlocking {
+ mediaPagingSourceMutex.withLock {
+ val availableProviders: List<Provider> = availableProviders.value
+ val contentResolver: ContentResolver = _activeContentResolver.value
+ val mediaPagingSource =
+ MediaPagingSource(
+ contentResolver,
+ availableProviders,
+ mediaProviderClient,
+ dispatcher,
+ config.value,
+ events,
+ /* is_preview_request */ true,
+ currentSelection.mapNotNull { it.mediaId }.toCollection(ArrayList()),
+ currentDeselection
+ .mapNotNull { it.mediaId }
+ .toCollection(
+ ArrayList(),
+ ),
+ )
+
+ Log.v(
+ DataService.TAG,
+ "Created a media paging source that queries database for" + "preview items."
+ )
+ mediaPagingSources.add(mediaPagingSource)
+ mediaPagingSource
+ }
+ }
override suspend fun refreshMedia() {
val availableProviders: List<Provider> = availableProviders.value
@@ -568,6 +611,9 @@ class DataServiceImpl(
// successful sync enables cloud queries, which then updates the UI.
refreshMedia(providers)
+ // refresh count for preGranted media.
+ refreshPreGrantedItemsCount()
+
val previouslyAvailableProviders = _availableProviders.value
_availableProviders.update { providers }
@@ -585,6 +631,24 @@ class DataServiceImpl(
collectionInfoState.clear()
}
+ override fun refreshPreGrantedItemsCount() {
+ // value for _preGrantedMediaCount being null signifies that the count has not been fetched
+ // yet for this photopicker session.
+ // This should only be used in ACTION_USER_SELECT_IMAGES_FOR_APP mode since grants only
+ // exist for this mode.
+ if (
+ _preGrantedMediaCount.value == null &&
+ MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP.equals(config.value.action)
+ ) {
+ _preGrantedMediaCount.update {
+ mediaProviderClient.fetchMediaGrantsCount(
+ _activeContentResolver.value,
+ config.value.callingPackageUid ?: -1
+ )
+ }
+ }
+ }
+
/**
* Sends a refresh media notification to the data source. This signal tells the data source to
* refresh its cache.
diff --git a/photopicker/src/com/android/photopicker/data/MediaProviderClient.kt b/photopicker/src/com/android/photopicker/data/MediaProviderClient.kt
index df14d98f7..a47feef28 100644
--- a/photopicker/src/com/android/photopicker/data/MediaProviderClient.kt
+++ b/photopicker/src/com/android/photopicker/data/MediaProviderClient.kt
@@ -47,6 +47,7 @@ open class MediaProviderClient {
private const val EXTRA_LOCAL_ONLY = "is_local_only"
private const val EXTRA_ALBUM_ID = "album_id"
private const val EXTRA_ALBUM_AUTHORITY = "album_authority"
+ private const val COLUMN_GRANTS_COUNT = "grants_count"
}
/** Contains all optional and mandatory keys required to make a Media query */
@@ -94,6 +95,7 @@ open class MediaProviderClient {
MIME_TYPE("mime_type"),
STANDARD_MIME_TYPE_EXT("standard_mime_type_extension"),
DURATION("duration_millis"),
+ IS_PRE_GRANTED("is_pre_granted"),
}
/** Contains all optional and mandatory keys for data in the Media query response extras. */
@@ -115,6 +117,13 @@ open class MediaProviderClient {
COVER_MEDIA_SOURCE("media_source")
}
+ /** Contains all optional and mandatory keys for the Preview Media Query. */
+ enum class PreviewMediaQuery(val key: String) {
+ CURRENT_SELECTION("current_selection"),
+ CURRENT_DE_SELECTION("current_de_selection"),
+ IS_FIRST_PAGE("is_first_page")
+ }
+
/** Fetch available [Provider]-s from the Media Provider process. */
fun fetchAvailableProviders(
contentResolver: ContentResolver,
@@ -158,7 +167,7 @@ open class MediaProviderClient {
pageSize: Int,
contentResolver: ContentResolver,
availableProviders: List<Provider>,
- config: PhotopickerConfiguration
+ config: PhotopickerConfiguration,
): LoadResult<MediaPageKey, Media> {
val input: Bundle =
bundleOf(
@@ -170,7 +179,8 @@ open class MediaProviderClient {
availableProviders.forEach { provider -> add(provider.authority) }
},
EXTRA_MIME_TYPES to config.mimeTypes,
- EXTRA_INTENT_ACTION to config.action
+ EXTRA_INTENT_ACTION to config.action,
+ Intent.EXTRA_UID to config.callingPackageUid,
)
try {
@@ -198,6 +208,59 @@ open class MediaProviderClient {
}
}
+ /** Fetch a list of [Media] from MediaProvider for the given page key. */
+ fun fetchPreviewMedia(
+ pageKey: MediaPageKey,
+ pageSize: Int,
+ contentResolver: ContentResolver,
+ availableProviders: List<Provider>,
+ config: PhotopickerConfiguration,
+ currentSelection: List<String> = emptyList(),
+ currentDeSelection: List<String> = emptyList(),
+ isFirstPage: Boolean = false,
+ ): LoadResult<MediaPageKey, Media> {
+ val input: Bundle =
+ bundleOf(
+ MediaQuery.PICKER_ID.key to pageKey.pickerId,
+ MediaQuery.DATE_TAKEN.key to pageKey.dateTakenMillis,
+ MediaQuery.PAGE_SIZE.key to pageSize,
+ MediaQuery.PROVIDERS.key to
+ ArrayList<String>().apply {
+ availableProviders.forEach { provider -> add(provider.authority) }
+ },
+ EXTRA_MIME_TYPES to config.mimeTypes,
+ EXTRA_INTENT_ACTION to config.action,
+ Intent.EXTRA_UID to config.callingPackageUid,
+ PreviewMediaQuery.CURRENT_SELECTION.key to currentSelection,
+ PreviewMediaQuery.CURRENT_DE_SELECTION.key to currentDeSelection,
+ PreviewMediaQuery.IS_FIRST_PAGE.key to isFirstPage,
+ )
+
+ try {
+ return contentResolver
+ .query(
+ MEDIA_PREVIEW_URI,
+ /* projection */ null,
+ input,
+ /* cancellationSignal */ null // TODO
+ )
+ .use { cursor ->
+ cursor?.let {
+ LoadResult.Page(
+ data = cursor.getListOfMedia(),
+ prevKey = cursor.getPrevPageKey(),
+ nextKey = cursor.getNextPageKey()
+ )
+ }
+ ?: throw IllegalStateException(
+ "Received a null response from Content Provider"
+ )
+ }
+ } catch (e: RuntimeException) {
+ throw RuntimeException("Could not fetch preview media", e)
+ }
+ }
+
/** Fetch a list of [Group.Album] from MediaProvider for the given page key. */
fun fetchAlbums(
pageKey: MediaPageKey,
@@ -216,9 +279,9 @@ open class MediaProviderClient {
availableProviders.forEach { provider -> add(provider.authority) }
},
EXTRA_MIME_TYPES to config.mimeTypes,
- EXTRA_INTENT_ACTION to config.action
+ EXTRA_INTENT_ACTION to config.action,
+ Intent.EXTRA_UID to config.callingPackageUid,
)
-
try {
return contentResolver
.query(
@@ -265,7 +328,8 @@ open class MediaProviderClient {
availableProviders.forEach { provider -> add(provider.authority) }
},
EXTRA_MIME_TYPES to config.mimeTypes,
- EXTRA_INTENT_ACTION to config.action
+ EXTRA_INTENT_ACTION to config.action,
+ Intent.EXTRA_UID to config.callingPackageUid,
)
try {
@@ -318,6 +382,45 @@ open class MediaProviderClient {
}
/**
+ * Fetches the count of pre-granted media for a given package from the MediaProvider.
+ *
+ * This function is designed to be used within the MediaProvider client-side context. It queries
+ * the `MEDIA_GRANTS_URI` using a Bundle containing the calling package's UID to retrieve the
+ * count of media grants.
+ *
+ * @param contentResolver The ContentResolver used to interact with the MediaProvider.
+ * @param callingPackageUid The UID of the calling package (app) for which to fetch the count.
+ * @return The count of media grants for the calling package.
+ * @throws RuntimeException if an error occurs during the query or fetching of the grants count.
+ */
+ fun fetchMediaGrantsCount(contentResolver: ContentResolver, callingPackageUid: Int): Int {
+ if (callingPackageUid < 0) {
+ // return with 0 value since the input callingUid is invalid.
+ Log.e(TAG, "invalid calling package UID.")
+ throw IllegalArgumentException("Invalid input for uid.")
+ }
+ // Create a Bundle containing the calling package's UID. This is used as a selection
+ // argument for the query.
+ val input: Bundle = bundleOf(Intent.EXTRA_UID to callingPackageUid)
+
+ try {
+ contentResolver.query(MEDIA_GRANTS_COUNT_URI, /* projection */ null, input, null).use {
+ cursor ->
+ if (cursor != null && cursor.moveToFirst()) {
+ // Move the cursor to the first row and extract the count.
+
+ return cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_GRANTS_COUNT))
+ } else {
+ // return 0 if cursor is empty.
+ return 0
+ }
+ }
+ } catch (e: Exception) {
+ throw RuntimeException("Could not fetch media grants count. ", e)
+ }
+ }
+
+ /**
* Send a refresh media request to MediaProvider. This is a signal for MediaProvider to refresh
* its cache, if required.
*/
@@ -337,6 +440,7 @@ open class MediaProviderClient {
extras.putBoolean(EXTRA_LOCAL_ONLY, initLocalOnlyMedia)
extras.putStringArrayList(EXTRA_MIME_TYPES, config.mimeTypes)
extras.putString(EXTRA_INTENT_ACTION, config.action)
+ extras.putInt(Intent.EXTRA_UID, config.callingPackageUid ?: -1)
refreshMedia(extras, resolver)
}
@@ -469,7 +573,8 @@ open class MediaProviderClient {
val mimeType: String = getString(getColumnIndexOrThrow(MediaResponse.MIME_TYPE.key))
val standardMimeTypeExtension: Int =
getInt(getColumnIndexOrThrow(MediaResponse.STANDARD_MIME_TYPE_EXT.key))
-
+ val isPregranted: Int =
+ getInt(getColumnIndexOrThrow(MediaResponse.IS_PRE_GRANTED.key))
if (mimeType.startsWith("image/")) {
result.add(
Media.Image(
@@ -483,6 +588,7 @@ open class MediaProviderClient {
sizeInBytes = sizeInBytes,
mimeType = mimeType,
standardMimeTypeExtension = standardMimeTypeExtension,
+ isPreGranted = (isPregranted == 1) // here 1 denotes true else false
)
)
} else if (mimeType.startsWith("video/")) {
@@ -499,6 +605,7 @@ open class MediaProviderClient {
mimeType = mimeType,
standardMimeTypeExtension = standardMimeTypeExtension,
duration = getInt(getColumnIndexOrThrow(MediaResponse.DURATION.key)),
+ isPreGranted = (isPregranted == 1) // here 1 denotes true else false
)
)
} else {
diff --git a/photopicker/src/com/android/photopicker/data/UriHelper.kt b/photopicker/src/com/android/photopicker/data/UriHelper.kt
index 1723865f7..b2368e6a6 100644
--- a/photopicker/src/com/android/photopicker/data/UriHelper.kt
+++ b/photopicker/src/com/android/photopicker/data/UriHelper.kt
@@ -27,6 +27,8 @@ private const val AVAILABLE_PROVIDERS_PATH_SEGMENT = "available_providers"
private const val COLLECTION_INFO_SEGMENT = "collection_info"
private const val MEDIA_PATH_SEGMENT = "media"
private const val ALBUM_PATH_SEGMENT = "album"
+private const val MEDIA_GRANTS_COUNT_PATH_SEGMENT = "media_grants_count"
+private const val PREVIEW_PATH_SEGMENT = "preview"
private val pickerUri: Uri =
Uri.Builder()
@@ -59,6 +61,20 @@ val AVAILABLE_PROVIDERS_CHANGE_NOTIFICATION_URI: Uri =
/** URI for media metadata. */
val MEDIA_URI: Uri = pickerUri.buildUpon().apply { appendPath(MEDIA_PATH_SEGMENT) }.build()
+/** URI for media_grants table. */
+val MEDIA_GRANTS_COUNT_URI: Uri =
+ pickerUri.buildUpon().apply { appendPath(MEDIA_GRANTS_COUNT_PATH_SEGMENT) }.build()
+
+/** URI for media_grants table. */
+val MEDIA_PREVIEW_URI: Uri =
+ pickerUri
+ .buildUpon()
+ .apply {
+ appendPath(MEDIA_PATH_SEGMENT)
+ appendPath(PREVIEW_PATH_SEGMENT)
+ }
+ .build()
+
/** URI that receives [ContentProvider] change notifications for media updates. */
val MEDIA_CHANGE_NOTIFICATION_URI: Uri =
MEDIA_URI.buildUpon().apply { appendPath(UPDATE_PATH_SEGMENT) }.build()
diff --git a/photopicker/src/com/android/photopicker/data/model/Media.kt b/photopicker/src/com/android/photopicker/data/model/Media.kt
index e0e31b256..43e59dbef 100644
--- a/photopicker/src/com/android/photopicker/data/model/Media.kt
+++ b/photopicker/src/com/android/photopicker/data/model/Media.kt
@@ -19,6 +19,8 @@ package com.android.photopicker.data.model
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
+import androidx.compose.material3.ExperimentalMaterial3Api
+import com.android.photopicker.core.events.Telemetry
import com.android.photopicker.core.glide.GlideLoadable
import com.android.photopicker.core.glide.Resolution
import com.android.photopicker.util.hashCodeOf
@@ -26,7 +28,7 @@ import com.bumptech.glide.load.DataSource
import com.bumptech.glide.signature.ObjectKey
/** Holds metadata for a type of media item like [Image] or [Video]. */
-sealed interface Media : GlideLoadable, Grantable, Parcelable {
+sealed interface Media : GlideLoadable, Grantable, Parcelable, Selectable {
/** This is the ID that provider has shared with Picker */
val mediaId: String
@@ -40,8 +42,23 @@ sealed interface Media : GlideLoadable, Grantable, Parcelable {
val sizeInBytes: Long
val mimeType: String
val standardMimeTypeExtension: Int
+ override val selectionSource: Telemetry.MediaLocation?
+ override val mediaItemAlbum: Group.Album?
override val isPreGranted: Boolean
+ companion object {
+ fun withSelectable(
+ item: Media,
+ selectionSource: Telemetry.MediaLocation,
+ album: Group.Album?
+ ): Media {
+ return when (item) {
+ is Image -> item.copy(selectionSource = selectionSource, mediaItemAlbum = album)
+ is Video -> item.copy(selectionSource = selectionSource, mediaItemAlbum = album)
+ }
+ }
+ }
+
override fun getSignature(resolution: Resolution): ObjectKey {
return ObjectKey("${mediaUri}_$resolution")
}
@@ -80,8 +97,10 @@ sealed interface Media : GlideLoadable, Grantable, Parcelable {
out.writeInt(standardMimeTypeExtension)
}
+ // TODO Make selectable values hold UNSET values instead of null
/** Holds metadata for an image item. */
- data class Image(
+ data class Image
+ constructor(
override val mediaId: String,
override val pickerId: Long,
override val authority: String,
@@ -93,6 +112,8 @@ sealed interface Media : GlideLoadable, Grantable, Parcelable {
override val mimeType: String,
override val standardMimeTypeExtension: Int,
override val isPreGranted: Boolean = false,
+ override val selectionSource: Telemetry.MediaLocation? = null,
+ override val mediaItemAlbum: Group.Album? = null
) : Media {
override fun writeToParcel(out: Parcel, flags: Int) {
@@ -124,6 +145,7 @@ sealed interface Media : GlideLoadable, Grantable, Parcelable {
companion object CREATOR : Parcelable.Creator<Image> {
+ @OptIn(ExperimentalMaterial3Api::class)
override fun createFromParcel(parcel: Parcel): Image {
val image =
Image(
@@ -148,8 +170,10 @@ sealed interface Media : GlideLoadable, Grantable, Parcelable {
}
}
+ // TODO Make selectable values hold UNSET values instead of null
/** Holds metadata for a video item. */
- data class Video(
+ data class Video
+ constructor(
override val mediaId: String,
override val pickerId: Long,
override val authority: String,
@@ -162,6 +186,8 @@ sealed interface Media : GlideLoadable, Grantable, Parcelable {
override val standardMimeTypeExtension: Int,
val duration: Int,
override val isPreGranted: Boolean = false,
+ override val selectionSource: Telemetry.MediaLocation? = null,
+ override val mediaItemAlbum: Group.Album? = null
) : Media {
override fun writeToParcel(out: Parcel, flags: Int) {
@@ -194,6 +220,7 @@ sealed interface Media : GlideLoadable, Grantable, Parcelable {
companion object CREATOR : Parcelable.Creator<Video> {
+ @OptIn(ExperimentalMaterial3Api::class)
override fun createFromParcel(parcel: Parcel): Video {
val video =
Video(
diff --git a/photopicker/src/com/android/photopicker/data/model/Selectable.kt b/photopicker/src/com/android/photopicker/data/model/Selectable.kt
new file mode 100644
index 000000000..aa68c45ee
--- /dev/null
+++ b/photopicker/src/com/android/photopicker/data/model/Selectable.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photopicker.data.model
+
+import com.android.photopicker.core.events.Telemetry
+
+/**
+ * The base interface to hold additional properties for any type of media object like [Image] or
+ * [Video]
+ */
+interface Selectable {
+ /** Holds whether the media items is present in the main grid or the albums grid */
+ val selectionSource: Telemetry.MediaLocation?
+ /** Holds the album the media item is part of in case it is present in the albums grid */
+ val mediaItemAlbum: Group.Album?
+}
diff --git a/photopicker/src/com/android/photopicker/data/paging/AlbumMediaPagingSource.kt b/photopicker/src/com/android/photopicker/data/paging/AlbumMediaPagingSource.kt
index d1cb099f1..ab63e0567 100644
--- a/photopicker/src/com/android/photopicker/data/paging/AlbumMediaPagingSource.kt
+++ b/photopicker/src/com/android/photopicker/data/paging/AlbumMediaPagingSource.kt
@@ -21,6 +21,9 @@ import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.data.MediaProviderClient
import com.android.photopicker.data.model.Media
import com.android.photopicker.data.model.MediaPageKey
@@ -41,38 +44,54 @@ class AlbumMediaPagingSource(
private val availableProviders: List<Provider>,
private val mediaProviderClient: MediaProviderClient,
private val dispatcher: CoroutineDispatcher,
- private val config: PhotopickerConfiguration,
+ private val configuration: PhotopickerConfiguration,
+ private val events: Events,
) : PagingSource<MediaPageKey, Media>() {
companion object {
val TAG: String = "PickerAlbumMediaPagingSource"
}
override suspend fun load(params: LoadParams<MediaPageKey>): LoadResult<MediaPageKey, Media> {
+ val pageKey = params.key ?: MediaPageKey()
+ val pageSize = params.loadSize
+
// Switch to the background thread from the main thread using [withContext].
- return withContext(dispatcher) {
- val pageKey = params.key ?: MediaPageKey()
- val pageSize = params.loadSize
+ val albumMediaFetchResult =
+ withContext(dispatcher) {
+ try {
- try {
+ if (availableProviders.isEmpty()) {
+ throw IllegalArgumentException("No available providers found.")
+ }
- if (availableProviders.isEmpty()) {
- throw IllegalArgumentException("No available providers found.")
+ mediaProviderClient.fetchAlbumMedia(
+ albumId,
+ albumAuthority,
+ pageKey,
+ pageSize,
+ contentResolver,
+ availableProviders,
+ configuration
+ )
+ } catch (e: Exception) {
+ Log.e(TAG, "Could not fetch page from MediaProvider for album $albumId", e)
+ LoadResult.Error(e)
}
-
- mediaProviderClient.fetchAlbumMedia(
- albumId,
- albumAuthority,
- pageKey,
- pageSize,
- contentResolver,
- availableProviders,
- config
- )
- } catch (e: Exception) {
- Log.e(TAG, "Could not fetch page from MediaProvider for album $albumId", e)
- LoadResult.Error(e)
}
+ if (albumMediaFetchResult is LoadResult.Page) {
+ // Dispatch a pageInfo event to log paging details for fetching album media item
+ // Keeping page number as 0 for all dispatched events for now for simplicity
+ events.dispatch(
+ Event.LogPhotopickerPageInfo(
+ FeatureToken.CORE.token,
+ configuration.sessionId,
+ /* pageNumber */ 0,
+ pageSize
+ )
+ )
}
+
+ return albumMediaFetchResult
}
override fun getRefreshKey(state: PagingState<MediaPageKey, Media>): MediaPageKey? = null
diff --git a/photopicker/src/com/android/photopicker/data/paging/AlbumPagingSource.kt b/photopicker/src/com/android/photopicker/data/paging/AlbumPagingSource.kt
index a14608519..60c5dc34d 100644
--- a/photopicker/src/com/android/photopicker/data/paging/AlbumPagingSource.kt
+++ b/photopicker/src/com/android/photopicker/data/paging/AlbumPagingSource.kt
@@ -21,6 +21,9 @@ import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.data.MediaProviderClient
import com.android.photopicker.data.model.Group.Album
import com.android.photopicker.data.model.MediaPageKey
@@ -39,35 +42,50 @@ class AlbumPagingSource(
private val availableProviders: List<Provider>,
private val mediaProviderClient: MediaProviderClient,
private val dispatcher: CoroutineDispatcher,
- private val config: PhotopickerConfiguration,
+ private val configuration: PhotopickerConfiguration,
+ private val events: Events,
) : PagingSource<MediaPageKey, Album>() {
companion object {
val TAG: String = "PickerAlbumPagingSource"
}
override suspend fun load(params: LoadParams<MediaPageKey>): LoadResult<MediaPageKey, Album> {
+ val pageKey = params.key ?: MediaPageKey()
+ val pageSize = params.loadSize
// Switch to the background thread from the main thread using [withContext].
- return withContext(dispatcher) {
- val pageKey = params.key ?: MediaPageKey()
- val pageSize = params.loadSize
+ val albumFetchResult =
+ withContext(dispatcher) {
+ try {
+ if (availableProviders.isEmpty()) {
+ throw IllegalArgumentException("No available providers found.")
+ }
- try {
- if (availableProviders.isEmpty()) {
- throw IllegalArgumentException("No available providers found.")
+ mediaProviderClient.fetchAlbums(
+ pageKey,
+ pageSize,
+ contentResolver,
+ availableProviders,
+ configuration
+ )
+ } catch (e: Exception) {
+ Log.e(TAG, "Could not fetch page from Media provider", e)
+ LoadResult.Error(e)
}
+ }
- mediaProviderClient.fetchAlbums(
- pageKey,
- pageSize,
- contentResolver,
- availableProviders,
- config
+ if (albumFetchResult is LoadResult.Page) {
+ // Dispatch a pageInfo event to log paging details for fetching albums
+ // Keeping page number as 0 for all dispatched events for now for simplicity
+ events.dispatch(
+ Event.LogPhotopickerPageInfo(
+ FeatureToken.CORE.token,
+ configuration.sessionId,
+ /* pageNumber */ 0,
+ pageSize
)
- } catch (e: Exception) {
- Log.e(TAG, "Could not fetch page from Media provider", e)
- LoadResult.Error(e)
- }
+ )
}
+ return albumFetchResult
}
override fun getRefreshKey(state: PagingState<MediaPageKey, Album>): MediaPageKey? = null
diff --git a/photopicker/src/com/android/photopicker/data/paging/MediaPagingSource.kt b/photopicker/src/com/android/photopicker/data/paging/MediaPagingSource.kt
index f3bd14848..d8bf7bcde 100644
--- a/photopicker/src/com/android/photopicker/data/paging/MediaPagingSource.kt
+++ b/photopicker/src/com/android/photopicker/data/paging/MediaPagingSource.kt
@@ -21,6 +21,9 @@ import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.data.MediaProviderClient
import com.android.photopicker.data.model.Media
import com.android.photopicker.data.model.MediaPageKey
@@ -39,35 +42,66 @@ class MediaPagingSource(
private val availableProviders: List<Provider>,
private val mediaProviderClient: MediaProviderClient,
private val dispatcher: CoroutineDispatcher,
- private val config: PhotopickerConfiguration,
+ private val configuration: PhotopickerConfiguration,
+ private val events: Events,
+ private val isPreviewSession: Boolean = false,
+ private val currentSelection: List<String> = emptyList(),
+ private val currentDeSelection: List<String> = emptyList(),
) : PagingSource<MediaPageKey, Media>() {
companion object {
val TAG: String = "PickerMediaPagingSource"
}
override suspend fun load(params: LoadParams<MediaPageKey>): LoadResult<MediaPageKey, Media> {
+ val pageKey = params.key ?: MediaPageKey()
+ val pageSize = params.loadSize
// Switch to the background thread from the main thread using [withContext].
- return withContext(dispatcher) {
- val pageKey = params.key ?: MediaPageKey()
- val pageSize = params.loadSize
-
- try {
- if (availableProviders.isEmpty()) {
- throw IllegalArgumentException("No available providers found.")
+ val mediaFetchResult =
+ withContext(dispatcher) {
+ try {
+ if (availableProviders.isEmpty()) {
+ throw IllegalArgumentException("No available providers found.")
+ }
+ if (isPreviewSession) {
+ mediaProviderClient.fetchPreviewMedia(
+ pageKey,
+ pageSize,
+ contentResolver,
+ availableProviders,
+ configuration,
+ currentSelection,
+ currentDeSelection,
+ // only true for first page or refreshes.
+ /* isFirstPage */ (params.key == null)
+ )
+ } else {
+ mediaProviderClient.fetchMedia(
+ pageKey,
+ pageSize,
+ contentResolver,
+ availableProviders,
+ configuration
+ )
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Could not fetch page from Media provider", e)
+ LoadResult.Error(e)
}
+ }
- mediaProviderClient.fetchMedia(
- pageKey,
- pageSize,
- contentResolver,
- availableProviders,
- config
+ if (mediaFetchResult is LoadResult.Page) {
+ // Dispatch a pageInfo event to log paging details for fetching media items
+ // Keeping page number as 0 for all dispatched events for now for simplicity
+ events.dispatch(
+ Event.LogPhotopickerPageInfo(
+ FeatureToken.CORE.token,
+ configuration.sessionId,
+ /* pageNumber */ 0,
+ pageSize
)
- } catch (e: Exception) {
- Log.e(TAG, "Could not fetch page from Media provider", e)
- LoadResult.Error(e)
- }
+ )
}
+ return mediaFetchResult
}
override fun getRefreshKey(state: PagingState<MediaPageKey, Media>): MediaPageKey? = null
diff --git a/photopicker/src/com/android/photopicker/extensions/Flow.kt b/photopicker/src/com/android/photopicker/extensions/Flow.kt
index 50f60aea4..8fa05bf35 100644
--- a/photopicker/src/com/android/photopicker/extensions/Flow.kt
+++ b/photopicker/src/com/android/photopicker/extensions/Flow.kt
@@ -20,6 +20,8 @@ import androidx.paging.PagingData
import androidx.paging.insertSeparators
import androidx.paging.map
import com.android.photopicker.core.components.MediaGridItem
+import com.android.photopicker.core.user.UserProfile
+import com.android.photopicker.core.user.UserStatus
import com.android.photopicker.data.model.Group
import com.android.photopicker.data.model.Media
import java.time.LocalDateTime
@@ -55,11 +57,11 @@ fun Flow<PagingData<Group.Album>>.toMediaGridItemFromAlbum(): Flow<PagingData<Me
* [Media] grid representation wrappers) and processes them inserting month separators in between
* items that have different month.
*
- * TODO(b/323830434): Update logic for separators after 4th row when UX finalizes.
- * Note: This does not include a separator for the first month of data.
- *
* @return A [PagingData<MediaGridItem] that can be processed further, or provided to the
* [MediaGrid].
+ *
+ * TODO(b/323830434): Update logic for separators after 4th row when UX finalizes. Note: This does
+ * not include a separator for the first month of data.
*/
fun Flow<PagingData<MediaGridItem.MediaItem>>.insertMonthSeparators():
Flow<PagingData<MediaGridItem>> {
@@ -99,3 +101,17 @@ fun Flow<PagingData<MediaGridItem.MediaItem>>.insertMonthSeparators():
}
}
}
+
+/**
+ * An extension function which filters all the available user profiles based on whether a profile is
+ * hidden or not.
+ *
+ * @return A list of all the user profiles available to the photopicker
+ */
+fun Flow<UserStatus>.getUserProfilesVisibleToPhotopicker(): Flow<List<UserProfile>> {
+ return this.map {
+ it.allProfiles.filterNot {
+ it.disabledReasons.contains(UserProfile.DisabledReason.QUIET_MODE_DO_NOT_SHOW)
+ }
+ }
+}
diff --git a/photopicker/src/com/android/photopicker/extensions/Intent.kt b/photopicker/src/com/android/photopicker/extensions/Intent.kt
index 5531c8302..2b874e80c 100644
--- a/photopicker/src/com/android/photopicker/extensions/Intent.kt
+++ b/photopicker/src/com/android/photopicker/extensions/Intent.kt
@@ -55,6 +55,8 @@ fun Intent.getPhotopickerSelectionLimitOrDefault(default: Int): Int {
getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false)
) {
MediaStore.getPickImagesMaxLimit()
+ } else if (getAction() == MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP) {
+ MediaStore.getPickImagesMaxLimit()
} else {
// No EXTRA_PICK_IMAGES_MAX was set, return the provided default
default
diff --git a/photopicker/src/com/android/photopicker/extensions/Modifier.kt b/photopicker/src/com/android/photopicker/extensions/Modifier.kt
index bd04906c8..4e75f4f99 100644
--- a/photopicker/src/com/android/photopicker/extensions/Modifier.kt
+++ b/photopicker/src/com/android/photopicker/extensions/Modifier.kt
@@ -16,16 +16,20 @@
package com.android.photopicker.extensions
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.setValue
+import android.os.Build
+import android.view.SurfaceControlViewHost
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.isUnspecified
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.layout
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -84,3 +88,132 @@ fun Modifier.circleBackground(
return this then backgroundModifier then layoutModifier
}
+
+/**
+ * Transfer necessary touch events occurred on Photos/Albums grid to host at runtime in Embedded
+ * Photopicker
+ *
+ * @param state the state of Photos/albums grid. If state is null means Photos/Albums grid has not
+ * requested the custom modifier
+ * @param isExpanded the updates on current status of embedded photopicker
+ * @param host the instance of [SurfaceControlViewHost]
+ * @return a [Modifier] to transfer the touch gestures at runtime in Embedded photopicker
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+fun Modifier.transferGridTouchesToHostInEmbedded(
+ state: LazyGridState,
+ isExpanded: State<Boolean>,
+ host: SurfaceControlViewHost
+): Modifier {
+ return this then
+ transferTouchesToSurfaceControlViewHost(
+ state = state,
+ isExpanded = isExpanded,
+ host = host,
+ )
+}
+
+/**
+ * Transfer necessary touch events occurred outside of Photos/Albums grid to host on runtime in
+ * Embedded Photopicker
+ *
+ * @param host the instance of [SurfaceControlViewHost]
+ * @return a [Modifier] to transfer the touch gestures at runtime in Embedded photopicker
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+fun Modifier.transferTouchesToHostInEmbedded(host: SurfaceControlViewHost): Modifier {
+ return this then
+ transferTouchesToSurfaceControlViewHost(state = null, isExpanded = null, host = host)
+}
+
+/**
+ * Transfer necessary touch events to host on runtime in Embedded Photopicker
+ *
+ * @param state the state of Photos/albums grid. If state is null means Photos/Albums grid has not
+ * requested the custom modifier
+ * @param isExpanded the updates on current status of embedded photopicker
+ * @param host the instance of [SurfaceControlViewHost]
+ * @return a [Modifier] to transfer the touch gestures at runtime in Embedded photopicker
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+private fun Modifier.transferTouchesToSurfaceControlViewHost(
+ state: LazyGridState?,
+ isExpanded: State<Boolean>?,
+ host: SurfaceControlViewHost
+): Modifier {
+
+ /**
+ * Initial y position when user touches the screen or when [PointerEventType.Press] is received
+ */
+ var initialY = 0F
+
+ /**
+ * Difference in Y position with respect to initialY as user starts scrolling on the screen, to
+ * know the direction of the movement
+ */
+ var dy = 0F
+
+ val pointerInputModifier =
+ pointerInput(Unit) {
+ awaitPointerEventScope {
+ while (true) {
+ // Suspend until next pointer event
+ val event: PointerEvent = awaitPointerEvent()
+ event.changes.forEach { change ->
+ if (state != null) {
+ when (event.type) {
+ PointerEventType.Press -> {
+ // Set initial Y position when user touches the screen
+ initialY = change.position.y
+ }
+ PointerEventType.Move -> {
+ // Position difference with respect to initial position
+ dy = change.position.y - initialY
+ }
+ PointerEventType.Release -> {
+ // Resetting the position change for next touch event
+ dy = 0F
+ }
+ }
+ }
+ }
+
+ // Todo(b/356790658) : Avoid recalculate these every time, just do it when
+ // argument changes
+ val isGridCollapsed = state != null && isExpanded != null && !isExpanded.value
+ val isGridExpanded = state != null && isExpanded != null && isExpanded.value
+
+ // Event is done being processed, make a decision about if this event should
+ // be transferred
+ val shouldTransferToHost =
+ when {
+
+ // Never transfer if the event type isn't move
+ event.type != PointerEventType.Move -> false
+
+ // Case for Not Grid attached modifiers
+ state == null -> true
+
+ // Case for grid attached when embedded is collapsed
+ isGridCollapsed && dy != 0F -> true
+
+ // Case for grid attached when embedded is expanded, and
+ // the lazy grid is at the top of its scroll container
+ isGridExpanded &&
+ (state.firstVisibleItemIndex == 0 &&
+ state.firstVisibleItemScrollOffset == 0 &&
+ dy > 0) -> true
+
+ // Otherwise don't transfer
+ else -> false
+ }
+
+ if (shouldTransferToHost) {
+ // TODO(b/356671436): Use V API when available
+ @Suppress("DEPRECATION") host.transferTouchGestureToHost()
+ }
+ }
+ }
+ }
+ return this then pointerInputModifier
+}
diff --git a/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGrid.kt b/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGrid.kt
index ed810d719..de26312da 100644
--- a/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGrid.kt
+++ b/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGrid.kt
@@ -25,6 +25,8 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material3.Text
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.stringResource
@@ -33,6 +35,11 @@ import androidx.paging.compose.collectAsLazyPagingItems
import com.android.photopicker.R
import com.android.photopicker.core.components.MediaGridItem
import com.android.photopicker.core.components.mediaGrid
+import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.features.LocalFeatureManager
import com.android.photopicker.core.navigation.LocalNavController
import com.android.photopicker.core.navigation.PhotopickerDestinations
@@ -43,6 +50,7 @@ import com.android.photopicker.extensions.navigateToAlbumMediaGrid
import com.android.photopicker.extensions.navigateToPhotoGrid
import com.android.photopicker.features.navigationbar.NavigationBarButton
import com.android.photopicker.features.photogrid.PhotoGridFeature
+import kotlinx.coroutines.launch
/** The number of grid cells per row for Phone / narrow layouts */
private val CELLS_PER_ROW_FOR_ALBUM_GRID = 2
@@ -65,6 +73,9 @@ fun AlbumGrid(viewModel: AlbumGridViewModel = obtainViewModel()) {
val state = rememberLazyGridState()
val navController = LocalNavController.current
val featureManager = LocalFeatureManager.current
+ val configuration = LocalPhotopickerConfiguration.current
+ val events = LocalEvents.current
+ val scope = rememberCoroutineScope()
// Use the expanded layout any time the Width is Medium or larger.
val isExpandedScreen: Boolean =
@@ -84,8 +95,20 @@ fun AlbumGrid(viewModel: AlbumGridViewModel = obtainViewModel()) {
// pretty well as is.
if (dragAmount > 0) {
// Positive is a right swipe
- if (featureManager.isFeatureEnabled(PhotoGridFeature::class.java))
+ if (featureManager.isFeatureEnabled(PhotoGridFeature::class.java)) {
navController.navigateToPhotoGrid()
+ // Dispatch UI event to indicate switching to photos tab
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.ALBUM_GRID.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.SWITCH_PICKER_TAB
+ )
+ )
+ }
+ }
}
}
)
@@ -96,8 +119,28 @@ fun AlbumGrid(viewModel: AlbumGridViewModel = obtainViewModel()) {
mediaGrid(
items = items,
onItemClick = { item ->
- if (item is MediaGridItem.AlbumItem)
+ if (item is MediaGridItem.AlbumItem) {
+ // Dispatch events to log album related details
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerAlbumOpenedUIEvent(
+ FeatureToken.ALBUM_GRID.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ item.album
+ )
+ )
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.ALBUM_GRID.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.PICKER_ALBUMS_INTERACTION
+ )
+ )
+ }
navController.navigateToAlbumMediaGrid(album = item.album)
+ }
},
isExpandedScreen = isExpandedScreen,
columns =
@@ -110,6 +153,19 @@ fun AlbumGrid(viewModel: AlbumGridViewModel = obtainViewModel()) {
contentPadding = PaddingValues(MEASUREMENT_HORIZONTAL_CELL_SPACING_ALBUM_GRID),
state = state,
)
+ LaunchedEffect(Unit) {
+ // Dispatch UI event to denote loading of media albums
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PHOTO_GRID.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.UI_LOADED_ALBUMS
+ )
+ )
+ }
+ }
}
}
@@ -120,9 +176,26 @@ fun AlbumGrid(viewModel: AlbumGridViewModel = obtainViewModel()) {
@Composable
fun AlbumGridNavButton(modifier: Modifier) {
val navController = LocalNavController.current
+ val scope = rememberCoroutineScope()
+ val events = LocalEvents.current
+ val sessionId = LocalPhotopickerConfiguration.current.sessionId
+ val packageUid = LocalPhotopickerConfiguration.current.callingPackageUid ?: -1
NavigationBarButton(
- onClick = navController::navigateToAlbumGrid,
+ onClick = {
+ // Dispatch UI event to denote switching to albums tab
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.ALBUM_GRID.token,
+ sessionId,
+ packageUid,
+ Telemetry.UiEvent.SWITCH_PICKER_TAB
+ )
+ )
+ }
+ navController.navigateToAlbumGrid()
+ },
modifier = modifier,
isCurrentRoute = { route -> route == PhotopickerDestinations.ALBUM_GRID.route },
) {
diff --git a/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGridFeature.kt b/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGridFeature.kt
index f52e009e2..f763737e6 100644
--- a/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGridFeature.kt
+++ b/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGridFeature.kt
@@ -64,7 +64,12 @@ class AlbumGridFeature : PhotopickerUiFeature {
override val eventsConsumed = emptySet<RegisteredEventClass>()
/** Events produced by the Album grid */
- override val eventsProduced = setOf(Event.ShowSnackbarMessage::class.java)
+ override val eventsProduced =
+ setOf(
+ Event.ShowSnackbarMessage::class.java,
+ Event.LogPhotopickerUIEvent::class.java,
+ Event.LogPhotopickerAlbumOpenedUIEvent::class.java
+ )
override fun registerLocations(): List<Pair<Location, Int>> {
return listOf(
diff --git a/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGridViewModel.kt b/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGridViewModel.kt
index ba473e05e..2ef86432e 100644
--- a/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGridViewModel.kt
+++ b/photopicker/src/com/android/photopicker/features/albumgrid/AlbumGridViewModel.kt
@@ -25,6 +25,7 @@ import androidx.paging.cachedIn
import com.android.photopicker.core.components.MediaGridItem
import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.Telemetry
import com.android.photopicker.core.features.FeatureToken.ALBUM_GRID
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.core.selection.SelectionModifiedResult.FAILURE_SELECTION_LIMIT_EXCEEDED
@@ -129,9 +130,16 @@ constructor(
* in the viewModelScope to ensure they aren't cancelled if the user navigates away from the
* AlbumMediaGrid composable.
*/
- fun handleAlbumMediaGridItemSelection(item: Media, selectionLimitExceededMessage: String) {
+ fun handleAlbumMediaGridItemSelection(
+ item: Media,
+ selectionLimitExceededMessage: String,
+ album: Group.Album
+ ) {
+ // Update the selectable values in the received media item.
+ val updatedMediaItem =
+ Media.withSelectable(item, /* selectionSource */ Telemetry.MediaLocation.ALBUM, album)
scope.launch {
- val result = selection.toggle(item)
+ val result = selection.toggle(updatedMediaItem)
if (result == FAILURE_SELECTION_LIMIT_EXCEEDED) {
events.dispatch(
Event.ShowSnackbarMessage(ALBUM_GRID.token, selectionLimitExceededMessage)
diff --git a/photopicker/src/com/android/photopicker/features/albumgrid/AlbumMediaGrid.kt b/photopicker/src/com/android/photopicker/features/albumgrid/AlbumMediaGrid.kt
index e944812b4..0f45e81b1 100644
--- a/photopicker/src/com/android/photopicker/features/albumgrid/AlbumMediaGrid.kt
+++ b/photopicker/src/com/android/photopicker/features/albumgrid/AlbumMediaGrid.kt
@@ -16,6 +16,7 @@
package com.android.photopicker.features.albumgrid
+import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA
import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_FAVORITES
import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS
import androidx.compose.foundation.layout.Column
@@ -25,12 +26,15 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Image
+import androidx.compose.material.icons.outlined.PhotoCamera
import androidx.compose.material.icons.outlined.StarOutline
import androidx.compose.material.icons.outlined.Videocam
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration
@@ -45,6 +49,10 @@ import com.android.photopicker.core.components.EmptyState
import com.android.photopicker.core.components.MediaGridItem
import com.android.photopicker.core.components.mediaGrid
import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.features.LocalFeatureManager
import com.android.photopicker.core.navigation.LocalNavController
import com.android.photopicker.core.navigation.PhotopickerDestinations
@@ -56,6 +64,7 @@ import com.android.photopicker.extensions.navigateToPreviewMedia
import com.android.photopicker.features.preview.PreviewFeature
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
/**
* Primary composable for drawing the Album content grid on
@@ -101,6 +110,9 @@ private fun AlbumMediaGrid(
val selectionLimit = LocalPhotopickerConfiguration.current.selectionLimit
val selectionLimitExceededMessage =
stringResource(R.string.photopicker_selection_limit_exceeded_snackbar, selectionLimit)
+ val scope = rememberCoroutineScope()
+ val events = LocalEvents.current
+ val configuration = LocalPhotopickerConfiguration.current
// Use the expanded layout any time the Width is Medium or larger.
val isExpandedScreen: Boolean =
@@ -144,18 +156,52 @@ private fun AlbumMediaGrid(
if (item is MediaGridItem.MediaItem) {
viewModel.handleAlbumMediaGridItemSelection(
item.media,
- selectionLimitExceededMessage
+ selectionLimitExceededMessage,
+ album
)
}
},
onItemLongPress = { item ->
+ // Dispatch UI event to log long pressing the media item
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.PICKER_LONG_SELECT_MEDIA_ITEM
+ )
+ )
+ }
// If the [PreviewFeature] is enabled, launch the preview route.
if (isPreviewEnabled && item is MediaGridItem.MediaItem) {
+ // Dispatch UI event to log entry into preview mode
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.ENTER_PICKER_PREVIEW_MODE
+ )
+ )
+ }
navController.navigateToPreviewMedia(item.media)
}
},
state = state,
)
+ LaunchedEffect(Unit) {
+ // Dispatch UI event to log loading of album contents
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PHOTO_GRID.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.UI_LOADED_ALBUM_CONTENTS
+ )
+ )
+ }
}
}
}
@@ -182,6 +228,12 @@ private fun getEmptyStateContentForAlbum(album: Group.Album): Triple<String, Str
stringResource(R.string.photopicker_videos_empty_state_body),
Icons.Outlined.Videocam,
)
+ ALBUM_ID_CAMERA ->
+ Triple(
+ stringResource(R.string.photopicker_photos_empty_state_title),
+ stringResource(R.string.photopicker_camera_empty_state_body),
+ Icons.Outlined.PhotoCamera,
+ )
// Use the empty state messages of the main photo grid in all other cases.
else ->
Triple(
diff --git a/photopicker/src/com/android/photopicker/features/cloudmedia/CloudMediaFeature.kt b/photopicker/src/com/android/photopicker/features/cloudmedia/CloudMediaFeature.kt
index a5ea043e1..bad19e6a6 100644
--- a/photopicker/src/com/android/photopicker/features/cloudmedia/CloudMediaFeature.kt
+++ b/photopicker/src/com/android/photopicker/features/cloudmedia/CloudMediaFeature.kt
@@ -19,6 +19,7 @@ package com.android.photopicker.features.cloudmedia
import android.content.Intent
import android.provider.MediaStore
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -26,8 +27,12 @@ import com.android.photopicker.R
import com.android.photopicker.core.banners.Banner
import com.android.photopicker.core.banners.BannerDefinitions
import com.android.photopicker.core.banners.BannerState
+import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.LocalEvents
import com.android.photopicker.core.events.RegisteredEventClass
+import com.android.photopicker.core.events.Telemetry
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureRegistration
import com.android.photopicker.core.features.FeatureToken
@@ -41,6 +46,7 @@ import com.android.photopicker.data.model.CollectionInfo
import com.android.photopicker.data.model.MediaSource
import com.android.photopicker.data.model.Provider
import com.android.photopicker.features.overflowmenu.OverflowMenuItem
+import kotlinx.coroutines.launch
/**
* Feature class for the Photopicker's cloud media implementation.
@@ -169,7 +175,11 @@ class CloudMediaFeature : PhotopickerUiFeature {
override val eventsConsumed = setOf<RegisteredEventClass>()
/** Events produced by the Cloud Media */
- override val eventsProduced = setOf<RegisteredEventClass>()
+ override val eventsProduced =
+ setOf<RegisteredEventClass>(
+ Event.LogPhotopickerMenuInteraction::class.java,
+ Event.LogPhotopickerUIEvent::class.java
+ )
override fun registerLocations(): List<Pair<Location, Int>> {
return listOf(
@@ -190,6 +200,9 @@ class CloudMediaFeature : PhotopickerUiFeature {
modifier: Modifier,
params: LocationParams,
) {
+ val events = LocalEvents.current
+ val scope = rememberCoroutineScope()
+ val configuration = LocalPhotopickerConfiguration.current
when (location) {
Location.MEDIA_PRELOADER -> MediaPreloader(modifier, params)
Location.OVERFLOW_MENU_ITEMS -> {
@@ -200,6 +213,18 @@ class CloudMediaFeature : PhotopickerUiFeature {
onClick = {
clickAction?.onClick()
context.startActivity(Intent(MediaStore.ACTION_PICK_IMAGES_SETTINGS))
+ // Dispatch event to log user's interactiuon with the cloud settings menu
+ // item in the photopicker
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerMenuInteraction(
+ token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.MenuItemSelected.CLOUD_SETTINGS
+ )
+ )
+ }
}
)
}
diff --git a/photopicker/src/com/android/photopicker/features/cloudmedia/MediaPreloader.kt b/photopicker/src/com/android/photopicker/features/cloudmedia/MediaPreloader.kt
index 963ae5809..ffa864de5 100644
--- a/photopicker/src/com/android/photopicker/features/cloudmedia/MediaPreloader.kt
+++ b/photopicker/src/com/android/photopicker/features/cloudmedia/MediaPreloader.kt
@@ -36,6 +36,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -43,8 +44,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.photopicker.R
+import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.features.LocationParams
import com.android.photopicker.core.obtainViewModel
+import kotlinx.coroutines.launch
/* Size of the spacer between dialog elements. */
private val MEASUREMENT_DIALOG_SPACER_SIZE = 24.dp
@@ -80,10 +87,27 @@ fun MediaPreloader(
// These must be set by the parent composable for the preloader to have any effect.
val preloaderParameters = params as? LocationParams.WithMediaPreloader
+ val configuration = LocalPhotopickerConfiguration.current
+ val scope = rememberCoroutineScope()
+ val events = LocalEvents.current
+
preloaderParameters?.let {
LaunchedEffect(params) {
// Listen for emissions of media to preload, and begin the preload when requested.
- it.preloadMedia.collect { media -> viewModel.startPreload(media, it.obtainDeferred()) }
+ it.preloadMedia.collect { media ->
+ // Dispatch UI event to log the beginning of media items preloading
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.CORE.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.PICKER_PRELOADING_START
+ )
+ )
+ }
+ viewModel.startPreload(media, it.obtainDeferred())
+ }
}
}
// If no preloaderParameters were passed to this location, there is no way to trigger
diff --git a/photopicker/src/com/android/photopicker/features/cloudmedia/MediaPreloaderViewModel.kt b/photopicker/src/com/android/photopicker/features/cloudmedia/MediaPreloaderViewModel.kt
index 634724751..7a3292eea 100644
--- a/photopicker/src/com/android/photopicker/features/cloudmedia/MediaPreloaderViewModel.kt
+++ b/photopicker/src/com/android/photopicker/features/cloudmedia/MediaPreloaderViewModel.kt
@@ -22,6 +22,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.android.photopicker.core.Background
import com.android.photopicker.core.configuration.ConfigurationManager
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.core.user.UserMonitor
import com.android.photopicker.data.model.Media
@@ -98,6 +102,7 @@ constructor(
private val selection: Selection<Media>,
private val userMonitor: UserMonitor,
private val configurationManager: ConfigurationManager,
+ private val events: Events,
) : ViewModel() {
companion object {
@@ -151,6 +156,8 @@ constructor(
initialValue = _dialogData.value
)
+ val configuration = configurationManager.configuration.value
+
init {
// If the active user's resolver changes, cancel any pending preload work.
@@ -302,6 +309,17 @@ constructor(
CloudMediaFeature.TAG,
"Failure detected, cancelling the rest of the preload operation."
)
+ // Log failure of media items preloading
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.CORE.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.PICKER_PRELOADING_FAILED
+ )
+ )
+ }
// Mark the item as failed in the result status.
mutex.withLock { remoteItems.set(item, LoadResult.FAILED) }
// Emit a new heartbeat so the monitor will react to this failure.
@@ -372,6 +390,17 @@ constructor(
// application to send the selected Media to the caller.
Log.d(CloudMediaFeature.TAG, "Preload operation was successful.")
deferred.complete(true)
+ // Dispatch UI event to mark the end of preloading of media items
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.CORE.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.PICKER_PRELOADING_FINISHED
+ )
+ )
+ }
}
}
@@ -399,6 +428,17 @@ constructor(
job?.let {
it.cancel()
Log.i(CloudMediaFeature.TAG, "Preload operation was cancelled.")
+ // Dispatch an event to log cancellation of media items preloading
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.CORE.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.PICKER_PRELOADING_CANCELLED
+ )
+ )
+ }
}
// In the event of single selection mode, the selection needs to be cleared.
diff --git a/photopicker/src/com/android/photopicker/features/navigationbar/NavigationBar.kt b/photopicker/src/com/android/photopicker/features/navigationbar/NavigationBar.kt
index 4abae8d1a..50614bfdc 100644
--- a/photopicker/src/com/android/photopicker/features/navigationbar/NavigationBar.kt
+++ b/photopicker/src/com/android/photopicker/features/navigationbar/NavigationBar.kt
@@ -147,16 +147,17 @@ fun NavigationBar(modifier: Modifier = Modifier) {
)
NavigationBarButtons(Modifier.weight(1f))
-
- LocalFeatureManager.current.composeLocation(
- Location.OVERFLOW_MENU,
- // Weight should match the profile switcher slot so they are the same size.
- modifier =
- Modifier.width(MEASUREMENT_ICON_BUTTON_WIDTH)
- .padding(end = MEASUREMENT_ICON_BUTTON_OUTSIDE_PADDING)
- )
}
}
+
+ // Always show the overflow menu, it will hide itself if it has no content.
+ LocalFeatureManager.current.composeLocation(
+ Location.OVERFLOW_MENU,
+ // Weight should match the profile switcher slot so they are the same size.
+ modifier =
+ Modifier.width(MEASUREMENT_ICON_BUTTON_WIDTH)
+ .padding(end = MEASUREMENT_ICON_BUTTON_OUTSIDE_PADDING)
+ )
}
}
diff --git a/photopicker/src/com/android/photopicker/features/overflowmenu/OverflowMenu.kt b/photopicker/src/com/android/photopicker/features/overflowmenu/OverflowMenu.kt
index 83f4da820..b0ee47f07 100644
--- a/photopicker/src/com/android/photopicker/features/overflowmenu/OverflowMenu.kt
+++ b/photopicker/src/com/android/photopicker/features/overflowmenu/OverflowMenu.kt
@@ -31,15 +31,22 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.android.photopicker.R
import com.android.photopicker.core.components.ElevationTokens
+import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.features.LocalFeatureManager
import com.android.photopicker.core.features.Location
import com.android.photopicker.core.features.LocationParams
+import kotlinx.coroutines.launch
/**
* Top of the OverflowMenu feature.
@@ -63,10 +70,30 @@ fun OverflowMenu(modifier: Modifier = Modifier) {
// Only show the overflow menu anchor if there will actually be items to select.
if (LocalFeatureManager.current.getSizeOfLocationInRegistry(Location.OVERFLOW_MENU_ITEMS) > 0) {
var expanded by remember { mutableStateOf(false) }
+ val events = LocalEvents.current
+ val scope = rememberCoroutineScope()
+ val configuration = LocalPhotopickerConfiguration.current
// Wrapped in a box to consume anything in the incoming modifier.
Box(modifier = modifier) {
- IconButton(onClick = { expanded = !expanded }) {
+ IconButton(
+ onClick = {
+ expanded = !expanded
+ // Dispatch UI event to log interaction with picker menu
+ if (expanded) {
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.OVERFLOW_MENU.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.PICKER_MENU_CLICK
+ )
+ )
+ }
+ }
+ }
+ ) {
Icon(
Icons.Filled.MoreVert,
contentDescription =
diff --git a/photopicker/src/com/android/photopicker/features/overflowmenu/OverflowMenuFeature.kt b/photopicker/src/com/android/photopicker/features/overflowmenu/OverflowMenuFeature.kt
index 6008d427c..de7f0c135 100644
--- a/photopicker/src/com/android/photopicker/features/overflowmenu/OverflowMenuFeature.kt
+++ b/photopicker/src/com/android/photopicker/features/overflowmenu/OverflowMenuFeature.kt
@@ -19,6 +19,8 @@ package com.android.photopicker.features.overflowmenu
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
+import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.RegisteredEventClass
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureRegistration
@@ -34,7 +36,8 @@ class OverflowMenuFeature : PhotopickerUiFeature {
companion object Registration : FeatureRegistration {
override val TAG: String = "PhotopickerOverflowMenuFeature"
- override fun isEnabled(config: PhotopickerConfiguration) = true
+ override fun isEnabled(config: PhotopickerConfiguration) =
+ config.runtimeEnv != PhotopickerRuntimeEnv.EMBEDDED
override fun build(featureManager: FeatureManager) = OverflowMenuFeature()
}
@@ -49,7 +52,8 @@ class OverflowMenuFeature : PhotopickerUiFeature {
override val eventsConsumed = setOf<RegisteredEventClass>()
/** Events produced by the OverflowMenu */
- override val eventsProduced = setOf<RegisteredEventClass>()
+ override val eventsProduced =
+ setOf<RegisteredEventClass>(Event.LogPhotopickerUIEvent::class.java)
@Composable
override fun compose(location: Location, modifier: Modifier, params: LocationParams) {
diff --git a/photopicker/src/com/android/photopicker/features/photogrid/PhotoGrid.kt b/photopicker/src/com/android/photopicker/features/photogrid/PhotoGrid.kt
index 3f9e5bbf0..f84b1ac91 100644
--- a/photopicker/src/com/android/photopicker/features/photogrid/PhotoGrid.kt
+++ b/photopicker/src/com/android/photopicker/features/photogrid/PhotoGrid.kt
@@ -16,8 +16,11 @@
package com.android.photopicker.features.photogrid
+import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -27,8 +30,10 @@ import androidx.compose.material.icons.outlined.Image
import androidx.compose.material3.Text
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalConfiguration
@@ -38,11 +43,20 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.android.photopicker.R
+import com.android.photopicker.core.banners.Banner
+import com.android.photopicker.core.banners.BannerDefinitions
import com.android.photopicker.core.components.EmptyState
import com.android.photopicker.core.components.MediaGridItem
import com.android.photopicker.core.components.mediaGrid
import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
+import com.android.photopicker.core.embedded.LocalEmbeddedState
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.features.LocalFeatureManager
+import com.android.photopicker.core.features.Location
import com.android.photopicker.core.navigation.LocalNavController
import com.android.photopicker.core.navigation.PhotopickerDestinations
import com.android.photopicker.core.navigation.PhotopickerDestinations.PHOTO_GRID
@@ -55,6 +69,10 @@ import com.android.photopicker.extensions.navigateToPreviewMedia
import com.android.photopicker.features.albumgrid.AlbumGridFeature
import com.android.photopicker.features.navigationbar.NavigationBarButton
import com.android.photopicker.features.preview.PreviewFeature
+import kotlinx.coroutines.launch
+
+private val MEASUREMENT_BANNER_PADDING =
+ PaddingValues(start = 16.dp, end = 16.dp, top = 0.dp, bottom = 24.dp)
/**
* Primary composable for drawing the main PhotoGrid on [PhotopickerDestinations.PHOTO_GRID]
@@ -84,22 +102,51 @@ fun PhotoGrid(viewModel: PhotoGridViewModel = obtainViewModel()) {
val selectionLimit = LocalPhotopickerConfiguration.current.selectionLimit
val selectionLimitExceededMessage =
stringResource(R.string.photopicker_selection_limit_exceeded_snackbar, selectionLimit)
+ val events = LocalEvents.current
+ val scope = rememberCoroutineScope()
+ val configuration = LocalPhotopickerConfiguration.current
- Column(
- modifier =
- Modifier.fillMaxSize().pointerInput(Unit) {
- detectHorizontalDragGestures(
- onHorizontalDrag = { _, dragAmount ->
- // This may need some additional fine tuning by looking at a certain
- // distance in dragAmount, but initial testing suggested this worked
- // pretty well as is.
- if (dragAmount < 0) {
- // Negative is a left swipe
- if (featureManager.isFeatureEnabled(AlbumGridFeature::class.java))
- navController.navigateToAlbumGrid()
+ // Modifier applied when photo grid to album grid navigation is disabled
+ val baseModifier = Modifier.fillMaxSize()
+ // Modifier applied when photo grid to album grid navigation is enabled
+ val modifierWithNavigation =
+ Modifier.fillMaxSize().pointerInput(Unit) {
+ detectHorizontalDragGestures(
+ onHorizontalDrag = { _, dragAmount ->
+ // This may need some additional fine tuning by looking at a certain
+ // distance in dragAmount, but initial testing suggested this worked
+ // pretty well as is.
+ if (dragAmount < 0) {
+ // Negative is a left swipe
+ if (featureManager.isFeatureEnabled(AlbumGridFeature::class.java)) {
+ // Dispatch UI event to indicate switching to albums tab
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.ALBUM_GRID.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.SWITCH_PICKER_TAB
+ )
+ )
+ }
+ navController.navigateToAlbumGrid()
}
}
- )
+ }
+ )
+ }
+
+ val isEmbedded =
+ LocalPhotopickerConfiguration.current.runtimeEnv == PhotopickerRuntimeEnv.EMBEDDED
+ val isExpanded = LocalEmbeddedState.current?.isExpanded ?: false
+ val isEmbeddedAndCollapsed = isEmbedded && !isExpanded
+
+ Column(
+ modifier =
+ when (isEmbeddedAndCollapsed) {
+ true -> baseModifier
+ false -> modifierWithNavigation
}
) {
val isEmptyAndNoMorePages =
@@ -121,42 +168,140 @@ fun PhotoGrid(viewModel: PhotoGridViewModel = obtainViewModel()) {
)
}
else -> {
+
+ // When the PhotoGrid is ready to show, also collect the latest banner
+ // data from [BannerManager] so it can be placed inside of the mediaGrid's
+ // scroll container.
+ val currentBanner by viewModel.banners.collectAsStateWithLifecycle()
+
mediaGrid(
items = items,
isExpandedScreen = isExpandedScreen,
selection = selection,
+ bannerContent = { AnimatedBannerWrapper(currentBanner) },
onItemClick = { item ->
if (item is MediaGridItem.MediaItem) {
viewModel.handleGridItemSelection(
item = item.media,
selectionLimitExceededMessage = selectionLimitExceededMessage
)
+ // Log user's interaction with picker's main grid(photo grid)
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PHOTO_GRID.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.PICKER_MAIN_GRID_INTERACTION
+ )
+ )
+ }
}
},
onItemLongPress = { item ->
+ // Log long pressing a media item in the photo grid
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.PICKER_LONG_SELECT_MEDIA_ITEM
+ )
+ )
+ }
// If the [PreviewFeature] is enabled, launch the preview route.
if (isPreviewEnabled) {
- if (item is MediaGridItem.MediaItem)
+ if (item is MediaGridItem.MediaItem) {
+ // Log entry into the photopicker preview mode
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.ENTER_PICKER_PREVIEW_MODE
+ )
+ )
+ }
navController.navigateToPreviewMedia(item.media)
+ }
}
},
state = state,
)
+ LaunchedEffect(Unit) {
+ // Log loading of photos in the photo grid
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PHOTO_GRID.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.UI_LOADED_PHOTOS
+ )
+ )
+ }
}
}
}
}
/**
+ * A container that animates its size to show the banner if one is defined. It also handles the
+ * banner's onDismiss action by sending the dismissal to the [PhotoGridViewModel].
+ *
+ * @param currentBanner The current banner that [BannerManager] is exposing.
+ */
+@Composable
+private fun AnimatedBannerWrapper(
+ currentBanner: Banner?,
+ viewModel: PhotoGridViewModel = obtainViewModel(),
+) {
+ Box(modifier = Modifier.animateContentSize()) {
+ currentBanner?.let {
+ Banner(
+ it,
+ modifier = Modifier.padding(MEASUREMENT_BANNER_PADDING),
+ onDismiss = {
+ val declaration = it.declaration
+
+ // Coerce the type back to [BannerDefinitions]
+ // so that it can be dismissed.
+ if (declaration is BannerDefinitions) {
+ viewModel.markBannerAsDismissed(declaration)
+ }
+ }
+ )
+ }
+ }
+}
+
+/**
* The navigation button for the main photo grid. Composable for
* [Location.NAVIGATION_BAR_NAV_BUTTON]
*/
@Composable
fun PhotoGridNavButton(modifier: Modifier) {
val navController = LocalNavController.current
+ val scope = rememberCoroutineScope()
+ val events = LocalEvents.current
+ val configuration = LocalPhotopickerConfiguration.current
NavigationBarButton(
- onClick = navController::navigateToPhotoGrid,
+ onClick = {
+ // Log switching tab to the photos tab
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PHOTO_GRID.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.SWITCH_PICKER_TAB
+ )
+ )
+ }
+ navController.navigateToPhotoGrid()
+ },
modifier = modifier,
isCurrentRoute = { route -> route == PHOTO_GRID.route },
) {
diff --git a/photopicker/src/com/android/photopicker/features/photogrid/PhotoGridFeature.kt b/photopicker/src/com/android/photopicker/features/photogrid/PhotoGridFeature.kt
index 215a67763..7e0b7ea9b 100644
--- a/photopicker/src/com/android/photopicker/features/photogrid/PhotoGridFeature.kt
+++ b/photopicker/src/com/android/photopicker/features/photogrid/PhotoGridFeature.kt
@@ -59,7 +59,8 @@ class PhotoGridFeature : PhotopickerUiFeature {
override val eventsConsumed = emptySet<RegisteredEventClass>()
/** Events produced by the Photo grid */
- override val eventsProduced = setOf(Event.ShowSnackbarMessage::class.java)
+ override val eventsProduced =
+ setOf(Event.ShowSnackbarMessage::class.java, Event.LogPhotopickerUIEvent::class.java)
override fun registerLocations(): List<Pair<Location, Int>> {
return listOf(
diff --git a/photopicker/src/com/android/photopicker/features/photogrid/PhotoGridViewModel.kt b/photopicker/src/com/android/photopicker/features/photogrid/PhotoGridViewModel.kt
index 5c316d36f..2d74275c1 100644
--- a/photopicker/src/com/android/photopicker/features/photogrid/PhotoGridViewModel.kt
+++ b/photopicker/src/com/android/photopicker/features/photogrid/PhotoGridViewModel.kt
@@ -21,8 +21,11 @@ import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
+import com.android.photopicker.core.banners.BannerDefinitions
+import com.android.photopicker.core.banners.BannerManager
import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.Telemetry
import com.android.photopicker.core.features.FeatureToken.PHOTO_GRID
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.core.selection.SelectionModifiedResult.FAILURE_SELECTION_LIMIT_EXCEEDED
@@ -50,6 +53,7 @@ constructor(
private val selection: Selection<Media>,
private val dataService: DataService,
private val events: Events,
+ private val bannerManager: BannerManager,
) : ViewModel() {
// Check if a scope override was injected before using the default [viewModelScope]
@@ -98,14 +102,40 @@ constructor(
// when navigating back to the PhotoGrid route.
.cachedIn(scope)
+ /** Export the [Banner] flow from BannerManager to the UI */
+ val banners = bannerManager.flow
+
+ /**
+ * Dismissal handler from the UI to mark a particular banner as dismissed by the user. This call
+ * is handed off to the bannerManager to persist any relevant dismissal state.
+ *
+ * Afterwards, refreshBanners is called to check for any new Banners from [BannerManager].
+ */
+ fun markBannerAsDismissed(banner: BannerDefinitions) {
+ scope.launch {
+ bannerManager.markBannerAsDismissed(banner)
+ bannerManager.refreshBanners()
+ }
+ }
+
/**
* Click handler that is called when items in the grid are clicked. Selection updates are made
* in the viewModelScope to ensure they aren't canceled if the user navigates away from the
* PhotoGrid composable.
*/
- fun handleGridItemSelection(item: Media, selectionLimitExceededMessage: String) {
+ fun handleGridItemSelection(
+ item: Media,
+ selectionLimitExceededMessage: String,
+ ) {
+ // Update the selectable values in the received media object.
+ val updatedMediaItem =
+ Media.withSelectable(
+ item, /* selectionSource */
+ Telemetry.MediaLocation.MAIN_GRID, /* album */
+ null
+ )
scope.launch {
- val result = selection.toggle(item)
+ val result = selection.toggle(updatedMediaItem)
if (result == FAILURE_SELECTION_LIMIT_EXCEEDED) {
scope.launch {
events.dispatch(
diff --git a/photopicker/src/com/android/photopicker/features/preview/Preview.kt b/photopicker/src/com/android/photopicker/features/preview/Preview.kt
index 23954cc4c..8d566d0b0 100644
--- a/photopicker/src/com/android/photopicker/features/preview/Preview.kt
+++ b/photopicker/src/com/android/photopicker/features/preview/Preview.kt
@@ -38,7 +38,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.PhotoLibrary
import androidx.compose.material.icons.outlined.Circle
import androidx.compose.material3.ButtonDefaults
@@ -56,6 +55,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -69,6 +69,12 @@ import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import com.android.photopicker.R
import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.features.Location
import com.android.photopicker.core.glide.RESOLUTION_REQUESTED
import com.android.photopicker.core.glide.Resolution
@@ -82,6 +88,7 @@ import com.android.photopicker.core.theme.LocalFixedAccentColors
import com.android.photopicker.data.model.Media
import com.android.photopicker.extensions.navigateToPreviewSelection
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
/**
* Entry point for the [PhotopickerDestinations.PREVIEW_SELECTION] and
@@ -126,7 +133,8 @@ fun PreviewSelection(
viewModel
.getPreviewMediaIncludingPreGrantedItems(
selectionSnapshot,
- LocalPhotopickerConfiguration.current
+ LocalPhotopickerConfiguration.current,
+ /* isSingleItemPreview */ false
)
.collectAsLazyPagingItems()
}
@@ -148,7 +156,7 @@ fun PreviewSelection(
) {
Row(
modifier =
- Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 4.dp, start = 4.dp),
+ Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 4.dp, start = 8.dp),
) {
// back button
IconButton(onClick = { navController.popBackStack() }) {
@@ -174,9 +182,9 @@ fun PreviewSelection(
Modifier.align(Alignment.Center),
selection,
state,
- snackbarHostState
+ snackbarHostState,
+ /* singleItemPreview */ previewSingleItem
)
-
IconButton(
modifier = Modifier.align(Alignment.TopStart),
onClick = {
@@ -186,7 +194,9 @@ fun PreviewSelection(
) {
if (currentSelection.contains(selection.get(state.currentPage))) {
Icon(
- Icons.Filled.CheckCircle,
+ ImageVector.vectorResource(
+ R.drawable.photopicker_selected_media
+ ),
modifier =
Modifier
// Background is necessary because the icon has negative
@@ -227,7 +237,7 @@ fun PreviewSelection(
Row(
modifier =
Modifier.fillMaxWidth()
- .padding(bottom = 68.dp, start = 4.dp, end = 16.dp, top = 12.dp),
+ .padding(bottom = 48.dp, start = 4.dp, end = 16.dp, top = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
if (previewSingleItem) {
@@ -277,7 +287,12 @@ private fun SelectionButton(
},
colors =
ButtonDefaults.textButtonColors(
- contentColor = LocalFixedAccentColors.current.primaryFixedDim
+ contentColor =
+ // The background color for Preview is always fixed to Black, so when the
+ // custom accent color is defined, switch to a White color for this button
+ // so it doesn't clash with the custom color.
+ if (CustomAccentColorScheme.current.isAccentColorDefined()) Color.White
+ else LocalFixedAccentColors.current.primaryFixedDim
)
) {
if (currentSelection.size > 0) {
@@ -310,7 +325,8 @@ private fun PreviewPager(
modifier: Modifier,
selection: LazyPagingItems<Media>,
state: PagerState,
- snackbarHostState: SnackbarHostState
+ snackbarHostState: SnackbarHostState,
+ singleItemPreview: Boolean,
) {
// Preview session state to keep track if the video player's audio is muted.
var audioIsMuted by remember { mutableStateOf(true) }
@@ -322,9 +338,15 @@ private fun PreviewPager(
val media = selection.get(page)
if (media != null) {
when (media) {
- is Media.Image -> ImageUi(media)
+ is Media.Image -> ImageUi(media, singleItemPreview)
is Media.Video ->
- VideoUi(media, audioIsMuted, { audioIsMuted = it }, snackbarHostState)
+ VideoUi(
+ media,
+ audioIsMuted,
+ { audioIsMuted = it },
+ snackbarHostState,
+ singleItemPreview
+ )
}
}
}
@@ -336,7 +358,32 @@ private fun PreviewPager(
* @param image
*/
@Composable
-private fun ImageUi(image: Media.Image) {
+private fun ImageUi(image: Media.Image, singleItemPreview: Boolean) {
+ if (singleItemPreview) {
+ val events = LocalEvents.current
+ val scope = rememberCoroutineScope()
+ val configuration = LocalPhotopickerConfiguration.current
+
+ scope.launch {
+ val mediaType =
+ if (image.mimeType.contains("gif")) {
+ Telemetry.MediaType.GIF
+ } else {
+ Telemetry.MediaType.PHOTO
+ }
+ // Mark entry into preview mode by long pressing on the media item
+ events.dispatch(
+ Event.LogPhotopickerPreviewInfo(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ Telemetry.PreviewModeEntry.LONG_PRESS,
+ previewItemCount = 1,
+ mediaType,
+ Telemetry.VideoPlayBackInteractions.UNSET_VIDEO_PLAYBACK_INTERACTION
+ )
+ )
+ }
+ }
loadMedia(
media = image,
resolution = Resolution.FULL,
@@ -355,9 +402,20 @@ private fun ImageUi(image: Media.Image) {
@Composable
fun PreviewSelectionButton(modifier: Modifier) {
val navController = LocalNavController.current
+ val events = LocalEvents.current
+ val scope = rememberCoroutineScope()
+ // TODO(b/353659535): Use Selection.size api when available
+ val currentSelection by LocalSelection.current.flow.collectAsStateWithLifecycle()
+ val previewItemCount = currentSelection.size
+ val configuration = LocalPhotopickerConfiguration.current
TextButton(
- onClick = navController::navigateToPreviewSelection,
+ onClick = {
+ scope.launch {
+ logPreviewSelectionButtonClicked(configuration, previewItemCount, events)
+ }
+ navController.navigateToPreviewSelection()
+ },
modifier = modifier,
) {
Text(
@@ -369,3 +427,44 @@ fun PreviewSelectionButton(modifier: Modifier) {
)
}
}
+
+/**
+ * Dispatches all the relevant logging events for the picker's preview mode when the Preview button
+ * is clicked
+ */
+private suspend fun logPreviewSelectionButtonClicked(
+ configuration: PhotopickerConfiguration,
+ previewItemCount: Int,
+ events: Events,
+) {
+ // Log preview item details
+ events.dispatch(
+ Event.LogPhotopickerPreviewInfo(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ Telemetry.PreviewModeEntry.VIEW_SELECTED,
+ previewItemCount,
+ Telemetry.MediaType.UNSET_MEDIA_TYPE,
+ Telemetry.VideoPlayBackInteractions.UNSET_VIDEO_PLAYBACK_INTERACTION
+ )
+ )
+
+ // Log preview related UI events including clicking the 'preview' button
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.ENTER_PICKER_PREVIEW_MODE
+ )
+ )
+
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.PICKER_CLICK_VIEW_SELECTED
+ )
+ )
+}
diff --git a/photopicker/src/com/android/photopicker/features/preview/PreviewFeature.kt b/photopicker/src/com/android/photopicker/features/preview/PreviewFeature.kt
index a74102163..7a9dde03d 100644
--- a/photopicker/src/com/android/photopicker/features/preview/PreviewFeature.kt
+++ b/photopicker/src/com/android/photopicker/features/preview/PreviewFeature.kt
@@ -23,6 +23,8 @@ import androidx.navigation.NamedNavArgument
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDeepLink
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
+import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.RegisteredEventClass
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureRegistration
@@ -46,7 +48,8 @@ class PreviewFeature : PhotopickerUiFeature {
companion object Registration : FeatureRegistration {
override val TAG: String = "PhotopickerPreviewFeature"
- override fun isEnabled(config: PhotopickerConfiguration) = true
+ override fun isEnabled(config: PhotopickerConfiguration) =
+ config.runtimeEnv != PhotopickerRuntimeEnv.EMBEDDED
override fun build(featureManager: FeatureManager) = PreviewFeature()
@@ -59,7 +62,11 @@ class PreviewFeature : PhotopickerUiFeature {
override val eventsConsumed = emptySet<RegisteredEventClass>()
/** Events produced by the Preview page */
- override val eventsProduced = emptySet<RegisteredEventClass>()
+ override val eventsProduced =
+ setOf<RegisteredEventClass>(
+ Event.LogPhotopickerUIEvent::class.java,
+ Event.LogPhotopickerPreviewInfo::class.java
+ )
override fun registerLocations(): List<Pair<Location, Int>> {
return listOf(
diff --git a/photopicker/src/com/android/photopicker/features/preview/PreviewViewModel.kt b/photopicker/src/com/android/photopicker/features/preview/PreviewViewModel.kt
index cdc4d1780..c7ea5a166 100644
--- a/photopicker/src/com/android/photopicker/features/preview/PreviewViewModel.kt
+++ b/photopicker/src/com/android/photopicker/features/preview/PreviewViewModel.kt
@@ -36,7 +36,12 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
+import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.core.selection.SelectionModifiedResult.FAILURE_SELECTION_LIMIT_EXCEEDED
import com.android.photopicker.core.selection.SelectionStrategy
@@ -72,6 +77,8 @@ constructor(
private val selection: Selection<Media>,
private val userMonitor: UserMonitor,
private val dataService: DataService,
+ private val events: Events,
+ private val configManager: ConfigurationManager,
) : ViewModel() {
companion object {
@@ -289,6 +296,19 @@ constructor(
val binder = controllerBundle.getBinder(EXTRA_SURFACE_CONTROLLER)
+ val configuration = configManager.configuration.value
+ // UI event to mark the start of surface controller creation
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.CREATE_SURFACE_CONTROLLER_START
+ )
+ )
+ }
+
// Produce the [RemotePreviewControllerInfo] and save it for future re-use.
val controllerInfo =
RemotePreviewControllerInfo(
@@ -314,6 +334,18 @@ constructor(
try {
controllerInfo.controller.onDestroy()
+ val configuration = configManager.configuration.value
+ // UI event to mark the end of surface controller creation
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.CREATE_SURFACE_CONTROLLER_END
+ )
+ )
+ }
} catch (e: RemoteException) {
Log.d(TAG, "Failed to destroy surface controller.", e)
}
diff --git a/photopicker/src/com/android/photopicker/features/preview/video/VideoUi.kt b/photopicker/src/com/android/photopicker/features/preview/video/VideoUi.kt
index 63f1d1df4..547808dd8 100644
--- a/photopicker/src/com/android/photopicker/features/preview/video/VideoUi.kt
+++ b/photopicker/src/com/android/photopicker/features/preview/video/VideoUi.kt
@@ -60,6 +60,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -70,12 +71,18 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.os.bundleOf
import com.android.photopicker.R
+import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.obtainViewModel
import com.android.photopicker.data.model.Media
import com.android.photopicker.extensions.requireSystemService
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
/** [AudioAttributes] to use with all VideoUi instances. */
private val AUDIO_ATTRIBUTES =
@@ -91,7 +98,7 @@ private val MEASUREMENT_PLAY_PAUSE_ICON_SIZE = 48.dp
/** Padding between the edge of the screen and the Player controls box. */
private val MEASUREMENT_PLAYER_CONTROLS_PADDING_HORIZONTAL = 8.dp
-private val MEASUREMENT_PLAYER_CONTROLS_PADDING_VERTICAL = 16.dp
+private val MEASUREMENT_PLAYER_CONTROLS_PADDING_VERTICAL = 12.dp
/** Delay in milliseconds before the player controls are faded. */
private val TIME_MS_PLAYER_CONTROLS_FADE_DELAY = 3000L
@@ -115,6 +122,7 @@ fun VideoUi(
audioIsMuted: Boolean,
onRequestAudioMuteChange: (Boolean) -> Unit,
snackbarHostState: SnackbarHostState,
+ singleItemPreview: Boolean,
viewModel: PreviewViewModel = obtainViewModel(),
) {
@@ -145,6 +153,25 @@ fun VideoUi(
val aspectRatio by produceAspectRatio(surfaceId, video)
val context = LocalContext.current
+ val scope = rememberCoroutineScope()
+ val events = LocalEvents.current
+ val configuration = LocalPhotopickerConfiguration.current
+
+ // Log that the video audio is muted
+ if (singleItemPreview && audioIsMuted) {
+ LaunchedEffect(video) {
+ events.dispatch(
+ Event.LogPhotopickerPreviewInfo(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ Telemetry.PreviewModeEntry.LONG_PRESS,
+ previewItemCount = 1,
+ Telemetry.MediaType.VIDEO,
+ Telemetry.VideoPlayBackInteractions.MUTE
+ )
+ )
+ }
+ }
/** Run these effects when a new PlaybackInfo is received */
LaunchedEffect(playbackInfo) {
@@ -200,8 +227,41 @@ fun VideoUi(
areControlsVisible = areControlsVisible,
onPlayPause = {
when (playbackInfo.state) {
- PlaybackState.STARTED -> controller.onMediaPause(surfaceId)
- PlaybackState.PAUSED -> controller.onMediaPlay(surfaceId)
+ PlaybackState.STARTED -> {
+ if (singleItemPreview) {
+ // Log video playback interactions
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerPreviewInfo(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ Telemetry.PreviewModeEntry.LONG_PRESS,
+ previewItemCount = 1,
+ Telemetry.MediaType.VIDEO,
+ Telemetry.VideoPlayBackInteractions.PLAY
+ )
+ )
+ }
+ }
+ controller.onMediaPause(surfaceId)
+ }
+ PlaybackState.PAUSED -> {
+ if (singleItemPreview) {
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerPreviewInfo(
+ FeatureToken.PREVIEW.token,
+ configuration.sessionId,
+ Telemetry.PreviewModeEntry.LONG_PRESS,
+ previewItemCount = 1,
+ Telemetry.MediaType.VIDEO,
+ Telemetry.VideoPlayBackInteractions.PAUSE
+ )
+ )
+ }
+ }
+ controller.onMediaPlay(surfaceId)
+ }
else -> {}
}
},
diff --git a/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelector.kt b/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelector.kt
index 3909d9919..fdf685cec 100644
--- a/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelector.kt
+++ b/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelector.kt
@@ -45,7 +45,9 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.photopicker.R
+import com.android.photopicker.core.StateSelector
import com.android.photopicker.core.components.ElevationTokens
+import com.android.photopicker.core.hideWhenState
import com.android.photopicker.core.obtainViewModel
import com.android.photopicker.core.user.UserProfile
@@ -178,7 +180,9 @@ fun ProfileSelector(
}
} else {
// Return a spacer which consumes the modifier so the space is still occupied, but is empty.
- Spacer(modifier)
+ // Hide the spacer when the runtime is embedded, so that the navigation tabs stay in the
+ // center as the overflow menu is disabled in embedded runtime.
+ hideWhenState(StateSelector.Embedded) { Spacer(modifier) }
}
}
diff --git a/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelectorFeature.kt b/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelectorFeature.kt
index 5c4dd0193..6843593d9 100644
--- a/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelectorFeature.kt
+++ b/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelectorFeature.kt
@@ -19,6 +19,7 @@ package com.android.photopicker.features.profileselector
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.RegisteredEventClass
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureRegistration
@@ -49,7 +50,8 @@ class ProfileSelectorFeature : PhotopickerUiFeature {
override val eventsConsumed = setOf<RegisteredEventClass>()
/** Events produced by the ProfileSelector */
- override val eventsProduced = setOf<RegisteredEventClass>()
+ override val eventsProduced =
+ setOf<RegisteredEventClass>(Event.LogPhotopickerUIEvent::class.java)
@Composable
override fun compose(
diff --git a/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelectorViewModel.kt b/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelectorViewModel.kt
index 86afba71a..02d13094f 100644
--- a/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelectorViewModel.kt
+++ b/photopicker/src/com/android/photopicker/features/profileselector/ProfileSelectorViewModel.kt
@@ -21,12 +21,18 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.android.photopicker.core.configuration.ConfigurationManager
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.core.user.SwitchUserProfileResult
import com.android.photopicker.core.user.UserMonitor
import com.android.photopicker.core.user.UserProfile
import com.android.photopicker.core.user.UserProfile.DisabledReason.QUIET_MODE_DO_NOT_SHOW
import com.android.photopicker.data.model.Media
+import com.android.photopicker.extensions.getUserProfilesVisibleToPhotopicker
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -47,6 +53,8 @@ constructor(
private val scopeOverride: CoroutineScope?,
private val selection: Selection<Media>,
private val userMonitor: UserMonitor,
+ private val events: Events,
+ private val configurationManager: ConfigurationManager,
) : ViewModel() {
companion object {
@@ -64,12 +72,7 @@ constructor(
/** All of the profiles that are available to Photopicker */
val allProfiles: StateFlow<List<UserProfile>> =
userMonitor.userStatus
- // For any profiles that contain a [QUIET_MODE_DO_NOT_SHOW] reason, this profile
- // should be skipped as it is currently in quiet mode and the profile specs
- // state it should be hidden from sharing surfaces when it is in quiet mode.
- .map {
- it.allProfiles.filterNot { it.disabledReasons.contains(QUIET_MODE_DO_NOT_SHOW) }
- }
+ .getUserProfilesVisibleToPhotopicker()
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
@@ -103,6 +106,16 @@ constructor(
// If the profile is actually changed, ensure the selection is cleared since
// content cannot be chosen from multiple profiles simultaneously.
selection.clear()
+ val configuration = configurationManager.configuration.value
+ // Log switching user profile in the picker
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.PROFILE_SELECTOR.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.SWITCH_USER_PROFILE
+ )
+ )
}
}
}
diff --git a/photopicker/src/com/android/photopicker/features/selectionbar/SelectionBar.kt b/photopicker/src/com/android/photopicker/features/selectionbar/SelectionBar.kt
index 79212d1f8..877f4296e 100644
--- a/photopicker/src/com/android/photopicker/features/selectionbar/SelectionBar.kt
+++ b/photopicker/src/com/android/photopicker/features/selectionbar/SelectionBar.kt
@@ -49,6 +49,11 @@ import com.android.photopicker.R
import com.android.photopicker.core.animations.emphasizedAccelerate
import com.android.photopicker.core.animations.emphasizedDecelerate
import com.android.photopicker.core.components.ElevationTokens
+import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.features.FeatureToken
import com.android.photopicker.core.features.LocalFeatureManager
import com.android.photopicker.core.features.Location
import com.android.photopicker.core.features.LocationParams
@@ -78,6 +83,8 @@ fun SelectionBar(modifier: Modifier = Modifier, params: LocationParams) {
val selection = LocalSelection.current
val currentSelection by LocalSelection.current.flow.collectAsStateWithLifecycle()
val visible = currentSelection.isNotEmpty()
+ val configuration = LocalPhotopickerConfiguration.current
+ val events = LocalEvents.current
val scope = rememberCoroutineScope()
// The entire selection bar is hidden if the selection is empty, and
@@ -139,6 +146,17 @@ fun SelectionBar(modifier: Modifier = Modifier, params: LocationParams) {
Spacer(Modifier.size(MEASUREMENT_BUTTONS_SPACER_SIZE))
FilledTonalButton(
onClick = {
+ // Log clicking the picker Add media button
+ scope.launch {
+ events.dispatch(
+ Event.LogPhotopickerUIEvent(
+ FeatureToken.SELECTION_BAR.token,
+ configuration.sessionId,
+ configuration.callingPackageUid ?: -1,
+ Telemetry.UiEvent.PICKER_CLICK_ADD_BUTTON
+ )
+ )
+ }
// The selection bar should receive a click handler from its parent
// to handle the primary button click.
val clickAction = params as? LocationParams.WithClickAction
diff --git a/photopicker/src/com/android/photopicker/features/selectionbar/SelectionBarFeature.kt b/photopicker/src/com/android/photopicker/features/selectionbar/SelectionBarFeature.kt
index 8f721a907..9434b8e3a 100644
--- a/photopicker/src/com/android/photopicker/features/selectionbar/SelectionBarFeature.kt
+++ b/photopicker/src/com/android/photopicker/features/selectionbar/SelectionBarFeature.kt
@@ -19,6 +19,8 @@ package com.android.photopicker.features.selectionbar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
+import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.RegisteredEventClass
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureRegistration
@@ -38,7 +40,14 @@ class SelectionBarFeature : PhotopickerUiFeature {
// The selection bar is only shown when in multi-select mode. For single select,
// the activity ends as soon as the first Media is selected, so this feature is
// disabled to prevent it's animation for playing when the selection changes.
- override fun isEnabled(config: PhotopickerConfiguration) = config.selectionLimit > 1
+ override fun isEnabled(config: PhotopickerConfiguration): Boolean {
+ if (config.runtimeEnv == PhotopickerRuntimeEnv.ACTIVITY) {
+ return config.selectionLimit > 1
+ }
+ // This is static enablement of feature. It will be hidden in collapsed
+ // mode for embedded at runtime.
+ return config.runtimeEnv == PhotopickerRuntimeEnv.EMBEDDED
+ }
override fun build(featureManager: FeatureManager) = SelectionBarFeature()
}
@@ -57,7 +66,8 @@ class SelectionBarFeature : PhotopickerUiFeature {
override val eventsConsumed = setOf<RegisteredEventClass>()
/** Events produced by the selection bar */
- override val eventsProduced = setOf<RegisteredEventClass>()
+ override val eventsProduced =
+ setOf<RegisteredEventClass>(Event.LogPhotopickerUIEvent::class.java)
@Composable
override fun compose(location: Location, modifier: Modifier, params: LocationParams) {
diff --git a/photopicker/src/com/android/photopicker/features/snackbar/Snackbar.kt b/photopicker/src/com/android/photopicker/features/snackbar/Snackbar.kt
index 10c60e3bd..6f4bc0749 100644
--- a/photopicker/src/com/android/photopicker/features/snackbar/Snackbar.kt
+++ b/photopicker/src/com/android/photopicker/features/snackbar/Snackbar.kt
@@ -20,14 +20,11 @@ import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.LocalEvents
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@Composable
@@ -47,7 +44,8 @@ fun Snackbar(modifier: Modifier) {
snackbarEvents.collect {
when (it) {
is Event.ShowSnackbarMessage -> {
- // Only enqueue a new snackbar if its message does not match the current snackbar
+ // Only enqueue a new snackbar if its message does not match the current
+ // snackbar
// to ensure that duplicate events are suppressed.
if (snackbarHostState.currentSnackbarData?.visuals?.message != it.message) {
scope.launch { snackbarHostState.showSnackbar(it.message) }
diff --git a/photopicker/src/com/android/photopicker/inject/ActivityModule.kt b/photopicker/src/com/android/photopicker/inject/ActivityModule.kt
index 1af82e0bd..9c2dd8965 100644
--- a/photopicker/src/com/android/photopicker/inject/ActivityModule.kt
+++ b/photopicker/src/com/android/photopicker/inject/ActivityModule.kt
@@ -28,6 +28,7 @@ import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
import com.android.photopicker.core.database.DatabaseManager
import com.android.photopicker.core.database.DatabaseManagerImpl
import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.selection.GrantsAwareSelectionImpl
import com.android.photopicker.core.selection.Selection
@@ -160,6 +161,7 @@ class ActivityModule {
/* scope= */ scope,
/* dispatcher= */ dispatcher,
/* deviceConfigProxy= */ deviceConfigProxy,
+ /* sessionId */ generatePickerSessionId(),
)
return configurationManager
}
@@ -193,6 +195,7 @@ class ActivityModule {
@ActivityRetainedScoped configurationManager: ConfigurationManager,
@ActivityRetainedScoped featureManager: FeatureManager,
@ApplicationContext appContext: Context,
+ events: Events,
): DataService {
if (!::dataService.isInitialized) {
Log.d(
@@ -209,6 +212,7 @@ class ActivityModule {
configurationManager.configuration,
featureManager,
appContext,
+ events,
)
}
return dataService
@@ -300,6 +304,7 @@ class ActivityModule {
fun provideSelection(
@ActivityRetainedScoped @Background scope: CoroutineScope,
configurationManager: ConfigurationManager,
+ dataService: DataService
): Selection<Media> {
if (::selection.isInitialized) {
@@ -312,6 +317,7 @@ class ActivityModule {
GrantsAwareSelectionImpl(
scope = scope,
configuration = configurationManager.configuration,
+ preGrantedItemsCount = dataService.preGrantedMediaCount
)
SelectionStrategy.DEFAULT ->
SelectionImpl(
diff --git a/photopicker/src/com/android/photopicker/inject/EmbeddedServiceModule.kt b/photopicker/src/com/android/photopicker/inject/EmbeddedServiceModule.kt
index bde2b0af1..034dc9cc4 100644
--- a/photopicker/src/com/android/photopicker/inject/EmbeddedServiceModule.kt
+++ b/photopicker/src/com/android/photopicker/inject/EmbeddedServiceModule.kt
@@ -31,6 +31,7 @@ import com.android.photopicker.core.database.DatabaseManagerImpl
import com.android.photopicker.core.embedded.EmbeddedLifecycle
import com.android.photopicker.core.embedded.EmbeddedViewModelFactory
import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.selection.GrantsAwareSelectionImpl
import com.android.photopicker.core.selection.Selection
@@ -108,6 +109,7 @@ class EmbeddedServiceModule {
@Background backgroundDispatcher: CoroutineDispatcher,
featureManager: Lazy<FeatureManager>,
configurationManager: Lazy<ConfigurationManager>,
+ bannerManager: Lazy<BannerManager>,
selection: Lazy<Selection<Media>>,
userMonitor: Lazy<UserMonitor>,
dataService: Lazy<DataService>,
@@ -121,6 +123,7 @@ class EmbeddedServiceModule {
EmbeddedViewModelFactory(
backgroundDispatcher,
configurationManager,
+ bannerManager,
dataService,
events,
featureManager,
@@ -218,6 +221,7 @@ class EmbeddedServiceModule {
/* scope= */ scope,
/* dispatcher= */ dispatcher,
/* deviceConfigProxy= */ deviceConfigProxy,
+ /* sessionId */ generatePickerSessionId(),
)
return configurationManager
}
@@ -235,7 +239,8 @@ class EmbeddedServiceModule {
notificationService: NotificationService,
configurationManager: ConfigurationManager,
featureManager: FeatureManager,
- @ApplicationContext appContext: Context
+ @ApplicationContext appContext: Context,
+ events: Events,
): DataService {
if (!::dataService.isInitialized) {
@@ -252,7 +257,8 @@ class EmbeddedServiceModule {
MediaProviderClient(),
configurationManager.configuration,
featureManager,
- appContext
+ appContext,
+ events,
)
}
return dataService
@@ -373,6 +379,7 @@ class EmbeddedServiceModule {
GrantsAwareSelectionImpl(
scope = scope,
configuration = configurationManager.configuration,
+ preGrantedItemsCount = dataService.preGrantedMediaCount,
)
SelectionStrategy.DEFAULT ->
SelectionImpl(
diff --git a/photopicker/tests/Android.bp b/photopicker/tests/Android.bp
index 53a71b199..14489fe07 100644
--- a/photopicker/tests/Android.bp
+++ b/photopicker/tests/Android.bp
@@ -31,6 +31,7 @@ android_test {
"androidx.test.core",
"androidx.test.rules",
"flag-junit",
+ "glide-mocks",
"hilt_android",
"hilt_android_testing",
"mockito-target",
diff --git a/photopicker/tests/AndroidTest.xml b/photopicker/tests/AndroidTest.xml
index fd9cebea6..6c4eff269 100644
--- a/photopicker/tests/AndroidTest.xml
+++ b/photopicker/tests/AndroidTest.xml
@@ -15,7 +15,7 @@
-->
<configuration description="Config for Android Photopicker test cases">
- <!-- Ensure test APK is enstalled and cleaned up after the run -->
+ <!-- Ensure test APK is installed and cleaned up after the run -->
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="PhotopickerTests.apk"/>
diff --git a/photopicker/tests/src/com/android/photopicker/PhotopickerFeatureBaseTest.kt b/photopicker/tests/src/com/android/photopicker/PhotopickerFeatureBaseTest.kt
index 8356a4112..964ebb8df 100644
--- a/photopicker/tests/src/com/android/photopicker/PhotopickerFeatureBaseTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/PhotopickerFeatureBaseTest.kt
@@ -33,7 +33,6 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.modules.utils.build.SdkLevel
import com.android.photopicker.R
import com.android.photopicker.core.PhotopickerMain
-import com.android.photopicker.core.banners.BannerManager
import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
import com.android.photopicker.core.events.Events
@@ -47,6 +46,8 @@ import com.android.photopicker.core.theme.PhotopickerTheme
import com.android.photopicker.data.model.Media
import com.android.photopicker.tests.utils.mockito.mockSystemService
import com.android.photopicker.tests.utils.mockito.whenever
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
@@ -137,8 +138,8 @@ abstract class PhotopickerFeatureBaseTest {
featureManager: FeatureManager,
selection: Selection<Media>,
events: Events,
- bannerManager: BannerManager,
navController: TestNavHostController = createNavController(),
+ disruptiveDataFlow: Flow<Int> = flow { emit(0) }
) {
val photopickerConfiguration by
configurationManager.configuration.collectAsStateWithLifecycle()
@@ -148,10 +149,10 @@ abstract class PhotopickerFeatureBaseTest {
LocalSelection provides selection,
LocalPhotopickerConfiguration provides photopickerConfiguration,
LocalNavController provides navController,
- LocalEvents provides events,
+ LocalEvents provides events
) {
PhotopickerTheme(config = photopickerConfiguration) {
- PhotopickerMain(bannerManager = bannerManager)
+ PhotopickerMain(disruptiveDataNotification = disruptiveDataFlow)
}
}
}
diff --git a/photopicker/tests/src/com/android/photopicker/core/PhotopickerAppTest.kt b/photopicker/tests/src/com/android/photopicker/core/PhotopickerAppTest.kt
new file mode 100644
index 000000000..37047d48f
--- /dev/null
+++ b/photopicker/tests/src/com/android/photopicker/core/PhotopickerAppTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photopicker.core
+
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.UserManager
+import android.test.mock.MockContentResolver
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import com.android.photopicker.R
+import com.android.photopicker.core.configuration.ConfigurationManager
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.glide.GlideTestRule
+import com.android.photopicker.core.navigation.PhotopickerDestinations
+import com.android.photopicker.core.selection.Selection
+import com.android.photopicker.data.DataService
+import com.android.photopicker.data.TestDataServiceImpl
+import com.android.photopicker.data.model.Media
+import com.android.photopicker.extensions.navigateToAlbumGrid
+import com.android.photopicker.features.PhotopickerFeatureBaseTest
+import com.android.photopicker.inject.PhotopickerTestModule
+import com.android.photopicker.test.utils.MockContentProviderWrapper
+import com.android.photopicker.tests.HiltTestActivity
+import com.android.photopicker.tests.utils.mockito.whenever
+import com.google.common.truth.Truth.assertWithMessage
+import dagger.Lazy
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.android.testing.BindValue
+import dagger.hilt.android.testing.HiltAndroidRule
+import dagger.hilt.android.testing.HiltAndroidTest
+import dagger.hilt.android.testing.UninstallModules
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.runningFold
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+
+/** This test class will run Photopicker's actual MainActivity. */
+@UninstallModules(
+ ApplicationModule::class,
+ ActivityModule::class,
+ EmbeddedServiceModule::class,
+)
+@HiltAndroidTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class PhotopickerAppTest : PhotopickerFeatureBaseTest() {
+ /** Hilt's rule needs to come first to ensure the DI container is setup for the test. */
+ @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this)
+ @get:Rule(order = 1)
+ val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
+
+ val testDispatcher = StandardTestDispatcher()
+
+ /** Overrides for ActivityModule */
+ val testScope: TestScope = TestScope(testDispatcher)
+ @BindValue @Main val mainScope: CoroutineScope = testScope
+ @BindValue @Background var testBackgroundScope: CoroutineScope = testScope.backgroundScope
+
+ /** Setup dependencies for the UninstallModules for the test class. */
+ @Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
+
+ @Inject override lateinit var configurationManager: ConfigurationManager
+ @Inject lateinit var mockContext: Context
+ @Inject lateinit var featureManager: Lazy<FeatureManager>
+ @Inject lateinit var selection: Lazy<Selection<Media>>
+ @Inject lateinit var events: Lazy<Events>
+ @Inject lateinit var dataService: Lazy<DataService>
+ @Mock lateinit var mockUserManager: UserManager
+ @Mock lateinit var mockPackageManager: PackageManager
+ @Mock lateinit var mockContentProvider: ContentProvider
+
+ @BindValue @ApplicationOwned lateinit var contentResolver: ContentResolver
+ private lateinit var provider: MockContentProviderWrapper
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ hiltRule.inject()
+
+ // Stub for MockContentResolver constructor
+ whenever(mockContext.getApplicationInfo()) { getTestableContext().getApplicationInfo() }
+
+ // Stub out the content resolver for Glide
+ val mockContentResolver = MockContentResolver(mockContext)
+ provider = MockContentProviderWrapper(mockContentProvider)
+ mockContentResolver.addProvider(MockContentProviderWrapper.AUTHORITY, provider)
+ contentResolver = mockContentResolver
+
+ // Return a resource png so that glide actually has something to load
+ whenever(mockContentProvider.openTypedAssetFile(any(), any(), any(), any())) {
+ getTestableContext().getResources().openRawResourceFd(R.drawable.android)
+ }
+ setupTestForUserMonitor(mockContext, mockUserManager, contentResolver, mockPackageManager)
+ }
+
+ @Test
+ fun testDataDisruptionResetsTheUi() {
+ testScope.runTest {
+ composeTestRule.setContent {
+ callPhotopickerMain(
+ featureManager = featureManager.get(),
+ selection = selection.get(),
+ events = events.get(),
+ disruptiveDataFlow =
+ dataService.get().disruptiveDataUpdateChannel.receiveAsFlow().runningFold(
+ initial = 0
+ ) { prev, _ ->
+ prev + 1
+ }
+ )
+ }
+
+ val startDestination = navController.currentBackStackEntry?.destination?.route
+ assertWithMessage("Expected the starting destination to not be album grid")
+ .that(startDestination)
+ .isNotEqualTo(PhotopickerDestinations.ALBUM_GRID.route)
+
+ composeTestRule.runOnUiThread { navController.navigateToAlbumGrid() }
+ composeTestRule.waitForIdle()
+
+ val albumRoute = navController.currentBackStackEntry?.destination?.route
+ assertWithMessage("Expected current route to be AlbumGrid")
+ .that(albumRoute)
+ .isEqualTo(PhotopickerDestinations.ALBUM_GRID.route)
+
+ val testDataService =
+ checkNotNull(dataService.get() as? TestDataServiceImpl) {
+ "Expected a TestDataServiceImpl"
+ }
+
+ testDataService.sendDisruptiveDataUpdateNotification()
+
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ val endRoute = navController.currentBackStackEntry?.destination?.route
+ assertWithMessage("Expected to return to start destination")
+ .that(endRoute)
+ .isEqualTo(startDestination)
+ }
+ }
+}
diff --git a/photopicker/tests/src/com/android/photopicker/core/banners/BannerManagerImplTest.kt b/photopicker/tests/src/com/android/photopicker/core/banners/BannerManagerImplTest.kt
index c088ab011..ba1ec95b3 100644
--- a/photopicker/tests/src/com/android/photopicker/core/banners/BannerManagerImplTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/banners/BannerManagerImplTest.kt
@@ -37,6 +37,7 @@ import com.android.photopicker.core.configuration.TestDeviceConfigProxyImpl
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
import com.android.photopicker.core.configuration.testActionPickImagesConfiguration
import com.android.photopicker.core.database.DatabaseManagerTestImpl
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureRegistration
import com.android.photopicker.core.user.UserMonitor
@@ -117,6 +118,8 @@ class BannerManagerImplTest {
)
}
+ val sessionId = generatePickerSessionId()
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -180,6 +183,7 @@ class BannerManagerImplTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId
)
val featureManager =
FeatureManager(
@@ -229,6 +233,7 @@ class BannerManagerImplTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId
)
val featureManager =
FeatureManager(
@@ -282,6 +287,7 @@ class BannerManagerImplTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId
)
val featureManager =
FeatureManager(
@@ -360,6 +366,7 @@ class BannerManagerImplTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId
)
val featureManager =
FeatureManager(
@@ -428,6 +435,7 @@ class BannerManagerImplTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId
)
val featureManager =
FeatureManager(
@@ -484,6 +492,7 @@ class BannerManagerImplTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId
)
val featureManager =
FeatureManager(
@@ -548,6 +557,7 @@ class BannerManagerImplTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId
)
val featureManager =
FeatureManager(
@@ -607,6 +617,7 @@ class BannerManagerImplTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId
)
val featureManager =
FeatureManager(
@@ -680,6 +691,7 @@ class BannerManagerImplTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId
)
val featureManager =
@@ -755,6 +767,7 @@ class BannerManagerImplTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId
)
val featureManager =
FeatureManager(
diff --git a/photopicker/tests/src/com/android/photopicker/core/banners/BannerTest.kt b/photopicker/tests/src/com/android/photopicker/core/banners/BannerTest.kt
index 9ffb297c3..763e5cf35 100644
--- a/photopicker/tests/src/com/android/photopicker/core/banners/BannerTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/banners/BannerTest.kt
@@ -20,6 +20,8 @@ import android.content.Context
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.VerifiedUser
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
@@ -27,16 +29,35 @@ import androidx.compose.ui.test.hasClickAction
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.photopicker.R
+import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.configuration.provideTestConfigurationFlow
+import com.android.photopicker.core.configuration.testPhotopickerConfiguration
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.Event.LogPhotopickerBannerInteraction
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.Telemetry.BannerType
+import com.android.photopicker.core.events.Telemetry.UserBannerInteraction
+import com.android.photopicker.core.features.FeatureManager
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
class BannerTest {
@get:Rule val composeTestRule = createComposeRule()
@@ -90,44 +111,190 @@ class BannerTest {
@Composable override fun getIcon() = Icons.Filled.VerifiedUser
}
+ @Composable
+ private fun showBanner(banner: Banner, config: PhotopickerConfiguration, events: Events) {
+
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides config,
+ LocalEvents provides events,
+ ) {
+ Banner(banner)
+ }
+ }
+
@Test
- fun testBannerDisplaysTitleAndMessage() {
- composeTestRule.setContent { Banner(banner = TEST_BANNER_1) }
+ fun testBannerDisplaysTitleAndMessage() = runTest {
+ val featureManager =
+ FeatureManager(
+ configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
+ scope = this.backgroundScope,
+ )
+
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager = featureManager,
+ )
+
+ val emissions = mutableListOf<Event>()
+ backgroundScope.launch { events.flow.toList(emissions) }
+
+ composeTestRule.setContent {
+ showBanner(banner = TEST_BANNER_1, testPhotopickerConfiguration, events)
+ }
+
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
composeTestRule.onNodeWithText(TEST_BANNER_1_TITLE).assertIsDisplayed()
composeTestRule.onNodeWithText(TEST_BANNER_1_MESSAGE).assertIsDisplayed()
+
+ val event: LogPhotopickerBannerInteraction =
+ checkNotNull(emissions.first() as? LogPhotopickerBannerInteraction) {
+ "Emitted event was not LogPhotopickerBannerInteraction."
+ }
+
+ assertWithMessage("Expected a banner type in event.")
+ .that(event.bannerType)
+ .isEqualTo(BannerType.UNSET_BANNER_TYPE)
+ assertWithMessage("Expected a banner displayed interaction")
+ .that(event.userInteraction)
+ .isEqualTo(UserBannerInteraction.UNSET_BANNER_INTERACTION)
}
@Test
- fun testBannerDisplaysActionButton() {
- composeTestRule.setContent { Banner(banner = TEST_BANNER_1) }
+ fun testBannerDisplaysActionButton() = runTest {
+ val featureManager =
+ FeatureManager(
+ configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
+ scope = this.backgroundScope,
+ )
+
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager = featureManager,
+ )
+
+ val emissions = mutableListOf<Event>()
+ backgroundScope.launch { events.flow.toList(emissions) }
+
+ composeTestRule.setContent {
+ showBanner(banner = TEST_BANNER_1, testPhotopickerConfiguration, events)
+ }
composeTestRule
.onNodeWithText(TEST_BANNER_1_ACTION_LABEL)
.assertIsDisplayed()
.assert(hasClickAction())
+ .performClick()
+
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ val event: LogPhotopickerBannerInteraction =
+ checkNotNull(emissions.last() as? LogPhotopickerBannerInteraction) {
+ "Emitted event was not LogPhotopickerBannerInteraction."
+ }
+
+ assertWithMessage("Expected a banner type in event.")
+ .that(event.bannerType)
+ .isEqualTo(BannerType.UNSET_BANNER_TYPE)
+ assertWithMessage("Expected a banner action button clicked interaction")
+ .that(event.userInteraction)
+ .isEqualTo(UserBannerInteraction.CLICK_BANNER_ACTION_BUTTON)
}
@Test
- fun testBannerDisplaysIcon() {
- composeTestRule.setContent { Banner(banner = TEST_BANNER_1) }
+ fun testBannerDisplaysIcon() = runTest {
+ val featureManager =
+ FeatureManager(
+ configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
+ scope = this.backgroundScope,
+ )
+
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager = featureManager,
+ )
+
+ composeTestRule.setContent {
+ showBanner(banner = TEST_BANNER_1, testPhotopickerConfiguration, events)
+ }
composeTestRule
.onNode(hasContentDescription(TEST_BANNER_1_ICON_DESCRIPTION))
.assertIsDisplayed()
}
@Test
- fun testBannerDisplaysDismissButtonForDismissable() {
+ fun testBannerDisplaysDismissButtonForDismissable() = runTest {
+ val featureManager =
+ FeatureManager(
+ configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
+ scope = this.backgroundScope,
+ )
+
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager = featureManager,
+ )
+
+ val emissions = mutableListOf<Event>()
+ backgroundScope.launch { events.flow.toList(emissions) }
+
val resources = InstrumentationRegistry.getInstrumentation().getContext().getResources()
val dismissString = resources.getString(R.string.photopicker_dismiss_banner_button_label)
- composeTestRule.setContent { Banner(TEST_BANNER_1) }
- composeTestRule.onNodeWithText(dismissString).assertIsDisplayed()
+ composeTestRule.setContent {
+ showBanner(TEST_BANNER_1, testPhotopickerConfiguration, events)
+ }
+
+ composeTestRule
+ .onNodeWithText(dismissString)
+ .assertIsDisplayed()
+ .assert(hasClickAction())
+ .performClick()
+
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ val event: LogPhotopickerBannerInteraction =
+ checkNotNull(emissions.last() as? LogPhotopickerBannerInteraction) {
+ "Emitted event was not LogPhotopickerBannerInteraction."
+ }
+
+ assertWithMessage("Expected a banner type in event.")
+ .that(event.bannerType)
+ .isEqualTo(BannerType.UNSET_BANNER_TYPE)
+ assertWithMessage("Expected a banner dismiss button clicked interaction")
+ .that(event.userInteraction)
+ .isEqualTo(UserBannerInteraction.CLICK_BANNER_DISMISS_BUTTON)
}
@Test
- fun testBannerHidesDismissButton() {
+ fun testBannerHidesDismissButton() = runTest {
+ val featureManager =
+ FeatureManager(
+ configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
+ scope = this.backgroundScope,
+ )
+
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager = featureManager,
+ )
+
val resources = InstrumentationRegistry.getInstrumentation().getContext().getResources()
val dismissString = resources.getString(R.string.photopicker_dismiss_banner_button_label)
- composeTestRule.setContent { Banner(TEST_BANNER_2) }
+ composeTestRule.setContent {
+ showBanner(TEST_BANNER_2, testPhotopickerConfiguration, events)
+ }
composeTestRule.onNodeWithText(dismissString).assertIsNotDisplayed()
}
}
diff --git a/photopicker/tests/src/com/android/photopicker/core/components/mediagrid/MediaGridTest.kt b/photopicker/tests/src/com/android/photopicker/core/components/mediagrid/MediaGridTest.kt
index b672e4d51..d59a58503 100644
--- a/photopicker/tests/src/com/android/photopicker/core/components/mediagrid/MediaGridTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/components/mediagrid/MediaGridTest.kt
@@ -19,6 +19,8 @@ package com.android.photopicker.core.components
import android.content.ContentProvider
import android.content.ContentResolver
import android.net.Uri
+import android.os.Build
+import android.view.SurfaceControlViewHost
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Text
@@ -32,6 +34,7 @@ import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertAll
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.click
import androidx.compose.ui.test.filter
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasTestTag
@@ -42,13 +45,16 @@ import androidx.compose.ui.test.onChildren
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
import androidx.compose.ui.test.swipeUp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
+import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.modules.utils.build.SdkLevel
import com.android.photopicker.R
import com.android.photopicker.core.ActivityModule
import com.android.photopicker.core.ApplicationModule
@@ -63,7 +69,11 @@ import com.android.photopicker.core.configuration.PhotopickerConfiguration
import com.android.photopicker.core.configuration.SINGLE_SELECT_CONFIG
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
import com.android.photopicker.core.configuration.testActionPickImagesConfiguration
+import com.android.photopicker.core.configuration.testEmbeddedPhotopickerConfiguration
import com.android.photopicker.core.configuration.testPhotopickerConfiguration
+import com.android.photopicker.core.embedded.EmbeddedState
+import com.android.photopicker.core.embedded.LocalEmbeddedState
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.selection.SelectionImpl
import com.android.photopicker.core.theme.PhotopickerTheme
import com.android.photopicker.data.model.Group
@@ -78,7 +88,6 @@ import com.android.photopicker.extensions.toMediaGridItemFromMedia
import com.android.photopicker.inject.PhotopickerTestModule
import com.android.photopicker.test.utils.MockContentProviderWrapper
import com.android.photopicker.tests.utils.mockito.whenever
-import com.bumptech.glide.Glide
import com.google.common.truth.Truth.assertWithMessage
import dagger.Module
import dagger.hilt.InstallIn
@@ -100,12 +109,14 @@ import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
-import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
/**
@@ -130,6 +141,7 @@ class MediaGridTest {
/** Hilt's rule needs to come first to ensure the DI container is setup for the test. */
@get:Rule(order = 0) var hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1) val composeTestRule = createComposeRule()
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/**
* MediaGrid uses Glide for loading images, so we have to mock out the dependencies for Glide
@@ -154,10 +166,25 @@ class MediaGridTest {
@Mock lateinit var mockContentProvider: ContentProvider
+ @Mock lateinit var mockSurfaceControlViewHost: SurfaceControlViewHost
+
+ /**
+ * A [EmbeddedState] having a mocked [SurfaceControlViewHost] instance that can be used for
+ * testing in collapsed mode
+ */
+ private lateinit var testEmbeddedStateWithHostInCollapsedState: EmbeddedState
+
+ /**
+ * A [EmbeddedState] having a mocked [SurfaceControlViewHost] instance that can be used for
+ * testing in Expanded state
+ */
+ private lateinit var testEmbeddedStateWithHostInExpandedState: EmbeddedState
+
lateinit var pager: Pager<MediaPageKey, Media>
lateinit var flow: Flow<PagingData<MediaGridItem>>
private val MEDIA_GRID_TEST_TAG = "media_grid"
+ private val BANNER_CONTENT_TEST_TAG = "banner_content"
private val CUSTOM_ITEM_TEST_TAG = "custom_item"
private val CUSTOM_ITEM_SEPARATOR_TAG = "custom_separator"
private val CUSTOM_ITEM_FACTORY_TEXT = "custom item factory"
@@ -227,6 +254,8 @@ class MediaGridTest {
.openRawResourceFd(R.drawable.android)
}
+ initEmbeddedStates()
+
// Normally this would be created in the view model that owns the paged data.
pager =
Pager(PagingConfig(pageSize = 50, maxSize = 500)) { FakeInMemoryMediaPagingSource() }
@@ -236,11 +265,17 @@ class MediaGridTest {
flow = pager.flow.toMediaGridItemFromMedia().insertMonthSeparators()
}
- @After()
- fun teardown() {
- // It is important to tearDown glide after every test to ensure it picks up the updated
- // mocks from Hilt and mocks aren't leaked between tests.
- Glide.tearDown()
+ /** Initialize [EmbeddedState] instances */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private fun initEmbeddedStates() {
+ if (SdkLevel.isAtLeastU()) {
+ @Suppress("DEPRECATION")
+ whenever(mockSurfaceControlViewHost.transferTouchGestureToHost()) { true }
+ testEmbeddedStateWithHostInCollapsedState =
+ EmbeddedState(isExpanded = false, host = mockSurfaceControlViewHost)
+ testEmbeddedStateWithHostInExpandedState =
+ EmbeddedState(isExpanded = true, host = mockSurfaceControlViewHost)
+ }
}
/**
@@ -252,6 +287,7 @@ class MediaGridTest {
selection: SelectionImpl<Media>,
onItemClick: (MediaGridItem) -> Unit,
onItemLongPress: (MediaGridItem) -> Unit = {},
+ bannerContent: (@Composable () -> Unit)? = null,
) {
val items = flow.collectAsLazyPagingItems()
val selected by selection.flow.collectAsStateWithLifecycle()
@@ -261,6 +297,7 @@ class MediaGridTest {
selection = selected,
onItemClick = onItemClick,
onItemLongPress = onItemLongPress,
+ bannerContent = bannerContent,
modifier = Modifier.testTag(MEDIA_GRID_TEST_TAG)
)
}
@@ -307,18 +344,56 @@ class MediaGridTest {
scope = backgroundScope,
configuration = provideTestConfigurationFlow(scope = backgroundScope)
)
-
composeTestRule.setContent {
- grid(
- /* selection= */ selection,
- /* onItemClick= */ {},
- )
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ ) {
+ PhotopickerTheme(isDarkTheme = false, config = testPhotopickerConfiguration) {
+ grid(
+ /* selection= */ selection,
+ /* onItemClick= */ {},
+ )
+ }
+ }
}
val mediaGrid = composeTestRule.onNode(hasTestTag(MEDIA_GRID_TEST_TAG))
mediaGrid.assertIsDisplayed()
}
+ /** Ensures the MediaGrid shows any banner content that is provided. */
+ @Test
+ fun testMediaGridDisplaysBannerContent() = runTest {
+ val selection =
+ SelectionImpl<Media>(
+ scope = backgroundScope,
+ configuration = provideTestConfigurationFlow(scope = backgroundScope)
+ )
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ ) {
+ PhotopickerTheme(isDarkTheme = false, config = testPhotopickerConfiguration) {
+ grid(
+ selection = selection,
+ onItemClick = {},
+ onItemLongPress = {},
+ bannerContent = {
+ Text(
+ text = "bannerContent",
+ modifier = Modifier.testTag(BANNER_CONTENT_TEST_TAG)
+ )
+ }
+ )
+ }
+ }
+ }
+
+ val mediaGrid = composeTestRule.onNode(hasTestTag(BANNER_CONTENT_TEST_TAG))
+ mediaGrid.assertIsDisplayed()
+ }
+
/** Ensures the AlbumGrid loads media with the correct semantic information */
@Test
fun testAlbumGridDisplaysMedia() = runTest {
@@ -339,10 +414,16 @@ class MediaGridTest {
flow = pagerForAlbums.flow.toMediaGridItemFromAlbum()
composeTestRule.setContent {
- grid(
- /* selection= */ selection,
- /* onItemClick= */ {},
- )
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ ) {
+ PhotopickerTheme(isDarkTheme = false, config = testPhotopickerConfiguration) {
+ grid(
+ /* selection= */ selection,
+ /* onItemClick= */ {},
+ )
+ }
+ }
}
val mediaGrid = composeTestRule.onNode(hasTestTag(MEDIA_GRID_TEST_TAG))
@@ -362,10 +443,16 @@ class MediaGridTest {
)
composeTestRule.setContent {
- grid(
- /* selection= */ selection,
- /* onItemClick= */ {},
- )
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ ) {
+ PhotopickerTheme(isDarkTheme = false, config = testPhotopickerConfiguration) {
+ grid(
+ /* selection= */ selection,
+ /* onItemClick= */ {},
+ )
+ }
+ }
}
val mediaGrid = composeTestRule.onNode(hasTestTag(MEDIA_GRID_TEST_TAG))
@@ -567,12 +654,10 @@ class MediaGridTest {
)
composeTestRule.setContent {
- val photopickerConfiguration: PhotopickerConfiguration =
- testPhotopickerConfiguration
CompositionLocalProvider(
- LocalPhotopickerConfiguration provides photopickerConfiguration,
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
) {
- PhotopickerTheme(isDarkTheme = false, config = photopickerConfiguration) {
+ PhotopickerTheme(isDarkTheme = false, config = testPhotopickerConfiguration) {
grid(
/* selection= */ selection,
/* onItemClick= */ {},
@@ -624,14 +709,19 @@ class MediaGridTest {
)
composeTestRule.setContent {
- val items = dataFlow.collectAsLazyPagingItems()
- val selected by selection.flow.collectAsStateWithLifecycle()
-
- mediaGrid(
- items = items,
- selection = selected,
- onItemClick = {},
- )
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ ) {
+ val items = dataFlow.collectAsLazyPagingItems()
+ val selected by selection.flow.collectAsStateWithLifecycle()
+ PhotopickerTheme(isDarkTheme = false, config = testPhotopickerConfiguration) {
+ mediaGrid(
+ items = items,
+ selection = selected,
+ onItemClick = {},
+ )
+ }
+ }
}
composeTestRule.onAllNodes(hasContentDescription(mediaItemString)).assertCountEquals(3)
@@ -651,17 +741,21 @@ class MediaGridTest {
)
composeTestRule.setContent {
- val items = flow.collectAsLazyPagingItems()
- val selected by selection.flow.collectAsStateWithLifecycle()
- mediaGrid(
- items = items,
- selection = selected,
- onItemClick = {},
- onItemLongPress = {},
- contentItemFactory = { item, _, onClick, _ ->
- customContentItemFactory(item, onClick)
- },
- )
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ ) {
+ val items = flow.collectAsLazyPagingItems()
+ val selected by selection.flow.collectAsStateWithLifecycle()
+ mediaGrid(
+ items = items,
+ selection = selected,
+ onItemClick = {},
+ onItemLongPress = {},
+ contentItemFactory = { item, _, onClick, _ ->
+ customContentItemFactory(item, onClick)
+ },
+ )
+ }
}
composeTestRule
@@ -686,14 +780,18 @@ class MediaGridTest {
)
composeTestRule.setContent {
- val items = dataFlow.collectAsLazyPagingItems()
- val selected by selection.flow.collectAsStateWithLifecycle()
- mediaGrid(
- items = items,
- selection = selected,
- onItemClick = {},
- contentSeparatorFactory = { _ -> customContentSeparatorFactory() }
- )
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ ) {
+ val items = dataFlow.collectAsLazyPagingItems()
+ val selected by selection.flow.collectAsStateWithLifecycle()
+ mediaGrid(
+ items = items,
+ selection = selected,
+ onItemClick = {},
+ contentSeparatorFactory = { _ -> customContentSeparatorFactory() }
+ )
+ }
}
composeTestRule
@@ -701,4 +799,226 @@ class MediaGridTest {
.assertAll(hasText(CUSTOM_ITEM_SEPARATOR_TEXT))
}
}
+
+ /** Ensures that touches are transferring for embedded when swipe up in collapsed mode */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testTouchesAreTransferringToHostInEmbedded_CollapsedMode_SwipeUp() = runTest {
+ val selection =
+ SelectionImpl<Media>(
+ scope = backgroundScope,
+ configuration = provideTestConfigurationFlow(scope = backgroundScope)
+ )
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateWithHostInCollapsedState
+ ) {
+ PhotopickerTheme(
+ isDarkTheme = false,
+ config = testEmbeddedPhotopickerConfiguration
+ ) {
+ grid(
+ /* selection= */ selection,
+ /* onItemClick= */ {},
+ )
+ }
+ }
+ }
+
+ val mediaGrid = composeTestRule.onNode(hasTestTag(MEDIA_GRID_TEST_TAG))
+
+ mediaGrid.performTouchInput { swipeUp() }
+ composeTestRule.waitForIdle()
+ mediaGrid.assertIsDisplayed()
+ // Verify whether the method to transfer touch events is invoked during testing
+ @Suppress("DEPRECATION")
+ verify(mockSurfaceControlViewHost, atLeast(1)).transferTouchGestureToHost()
+ }
+
+ /** Ensures that touches are transferring for embedded when swipe down in collapsed mode */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testTouchesAreTransferringToHostInEmbedded_CollapsedMode_SwipeDown() = runTest {
+ val selection =
+ SelectionImpl<Media>(
+ scope = backgroundScope,
+ configuration = provideTestConfigurationFlow(scope = backgroundScope)
+ )
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateWithHostInCollapsedState
+ ) {
+ PhotopickerTheme(
+ isDarkTheme = false,
+ config = testEmbeddedPhotopickerConfiguration
+ ) {
+ grid(
+ /* selection= */ selection,
+ /* onItemClick= */ {},
+ )
+ }
+ }
+ }
+
+ val mediaGrid = composeTestRule.onNode(hasTestTag(MEDIA_GRID_TEST_TAG))
+
+ mediaGrid.performTouchInput { swipeDown() }
+ composeTestRule.waitForIdle()
+ mediaGrid.assertIsDisplayed()
+ // Verify whether the method to transfer touch events is invoked during testing
+ @Suppress("DEPRECATION")
+ verify(mockSurfaceControlViewHost, atLeast(1)).transferTouchGestureToHost()
+ }
+
+ /** Ensures that clicks are not transferring for embedded in collapsed mode */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testTouchesAreNotTransferringToHostInEmbedded_CollapsedMode_Click() = runTest {
+ val selection =
+ SelectionImpl<Media>(
+ scope = backgroundScope,
+ configuration = provideTestConfigurationFlow(scope = backgroundScope)
+ )
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateWithHostInCollapsedState
+ ) {
+ PhotopickerTheme(
+ isDarkTheme = false,
+ config = testEmbeddedPhotopickerConfiguration
+ ) {
+ grid(
+ /* selection= */ selection,
+ /* onItemClick= */ {},
+ )
+ }
+ }
+ }
+
+ val mediaGrid = composeTestRule.onNode(hasTestTag(MEDIA_GRID_TEST_TAG))
+
+ mediaGrid.performTouchInput { click() }
+ composeTestRule.waitForIdle()
+ mediaGrid.assertIsDisplayed()
+ // Verify whether the method to transfer touch events is not invoked during testing
+ @Suppress("DEPRECATION")
+ verify(mockSurfaceControlViewHost, never()).transferTouchGestureToHost()
+ }
+
+ /** Ensures that touches are not transferring for embedded when swipe up in Expanded mode */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testTouchesAreNotTransferringToHostInEmbedded_ExpandedMode_SwipeUP() = runTest {
+ val selection =
+ SelectionImpl<Media>(
+ scope = backgroundScope,
+ configuration = provideTestConfigurationFlow(scope = backgroundScope)
+ )
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateWithHostInExpandedState
+ ) {
+ PhotopickerTheme(
+ isDarkTheme = false,
+ config = testEmbeddedPhotopickerConfiguration
+ ) {
+ grid(
+ /* selection= */ selection,
+ /* onItemClick= */ {},
+ )
+ }
+ }
+ }
+
+ val mediaGrid = composeTestRule.onNode(hasTestTag(MEDIA_GRID_TEST_TAG))
+
+ mediaGrid.performTouchInput { swipeUp() }
+ composeTestRule.waitForIdle()
+ mediaGrid.assertIsDisplayed()
+ // Verify whether the method to transfer touch events is not invoked during testing
+ @Suppress("DEPRECATION")
+ verify(mockSurfaceControlViewHost, never()).transferTouchGestureToHost()
+ }
+
+ /** Ensures that touches are transferring for embedded when swipe down in Expanded mode */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testTouchesAreTransferringToHostInEmbedded_ExpandedMode_SwipeDown() = runTest {
+ val selection =
+ SelectionImpl<Media>(
+ scope = backgroundScope,
+ configuration = provideTestConfigurationFlow(scope = backgroundScope)
+ )
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateWithHostInExpandedState
+ ) {
+ PhotopickerTheme(
+ isDarkTheme = false,
+ config = testEmbeddedPhotopickerConfiguration
+ ) {
+ grid(
+ /* selection= */ selection,
+ /* onItemClick= */ {},
+ )
+ }
+ }
+ }
+
+ val mediaGrid = composeTestRule.onNode(hasTestTag(MEDIA_GRID_TEST_TAG))
+
+ mediaGrid.performTouchInput { swipeDown() }
+ composeTestRule.waitForIdle()
+ mediaGrid.assertIsDisplayed()
+ // Verify whether the method to transfer touch events is invoked during testing
+ @Suppress("DEPRECATION")
+ verify(mockSurfaceControlViewHost, atLeast(1)).transferTouchGestureToHost()
+ }
+
+ /** Ensures that clicks are not transferring for embedded in Expanded mode */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testTouchesAreNotTransferringToHostInEmbedded_ExpandedMode_Click() = runTest {
+ val selection =
+ SelectionImpl<Media>(
+ scope = backgroundScope,
+ configuration = provideTestConfigurationFlow(scope = backgroundScope)
+ )
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateWithHostInExpandedState
+ ) {
+ PhotopickerTheme(
+ isDarkTheme = false,
+ config = testEmbeddedPhotopickerConfiguration
+ ) {
+ grid(
+ /* selection= */ selection,
+ /* onItemClick= */ {},
+ )
+ }
+ }
+ }
+
+ val mediaGrid = composeTestRule.onNode(hasTestTag(MEDIA_GRID_TEST_TAG))
+
+ mediaGrid.performTouchInput { click() }
+ composeTestRule.waitForIdle()
+ mediaGrid.assertIsDisplayed()
+ // Verify whether the method to transfer touch events is not invoked during testing
+ @Suppress("DEPRECATION")
+ verify(mockSurfaceControlViewHost, never()).transferTouchGestureToHost()
+ }
}
diff --git a/photopicker/tests/src/com/android/photopicker/core/configuration/ConfigurationManagerTest.kt b/photopicker/tests/src/com/android/photopicker/core/configuration/ConfigurationManagerTest.kt
index 420163763..defc38d8d 100644
--- a/photopicker/tests/src/com/android/photopicker/core/configuration/ConfigurationManagerTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/configuration/ConfigurationManagerTest.kt
@@ -17,10 +17,15 @@
package com.android.photopicker.core.configuration
import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.provider.EmbeddedPhotopickerFeatureInfo
import android.provider.MediaStore
import androidx.core.os.bundleOf
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.navigation.PhotopickerDestinations
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,6 +50,7 @@ class ConfigurationManagerTest {
// Isolate the test device by providing a test wrapper around device config so that the
// tests can control the flag values that are returned.
val deviceConfigProxy = TestDeviceConfigProxyImpl()
+ val sessionId = generatePickerSessionId()
@Before
fun setup() {
@@ -63,10 +69,11 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration with an action matching the test action.
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
backgroundScope.launch {
val reportedConfiguration = configurationManager.configuration.first()
@@ -81,7 +88,6 @@ class ConfigurationManagerTest {
*/
@Test
fun testConfigurationEmitsFlagChanges() {
-
runTest {
val configurationManager =
ConfigurationManager(
@@ -89,9 +95,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration with an action matching the test action.
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -133,9 +140,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration with an action matching the test action.
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -191,9 +199,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration with an action matching the test action.
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -238,9 +247,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -257,6 +267,7 @@ class ConfigurationManagerTest {
@Test
fun testSetCallerUpdatesConfiguration() {
+
runTest {
val configurationManager =
ConfigurationManager(
@@ -264,9 +275,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -311,9 +323,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -346,9 +359,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -382,9 +396,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -422,9 +437,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -458,6 +474,7 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
assertThrows(IllegalIntentExtraException::class.java) {
configurationManager.setIntent(intent)
@@ -485,9 +502,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -527,9 +545,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -568,9 +587,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -610,9 +630,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -647,9 +668,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -684,9 +706,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -724,9 +747,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -768,9 +792,10 @@ class ConfigurationManagerTest {
scope = this.backgroundScope,
dispatcher = StandardTestDispatcher(this.testScheduler),
deviceConfigProxy,
+ sessionId = sessionId
)
// Expect the default configuration
- val expectedConfiguration = PhotopickerConfiguration(action = "")
+ val expectedConfiguration = PhotopickerConfiguration(action = "", sessionId = sessionId)
val emissions = mutableListOf<PhotopickerConfiguration>()
backgroundScope.launch { configurationManager.configuration.toList(emissions) }
@@ -790,4 +815,167 @@ class ConfigurationManagerTest {
assertThat(emissions.first()).isEqualTo(expectedConfiguration)
}
}
+
+ /**
+ * Ensures that [ConfigurationManager.configuration] will emit an updated
+ * [PhotopickerConfiguration] with the expected [PhotopickerConfiguration.selectionLimit].
+ */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSetEmbeddedPhotopickerFeatureInfoSetsSelectionLimit() {
+ val featureInfo = EmbeddedPhotopickerFeatureInfo.Builder().build()
+
+ runTest {
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ sessionId = sessionId
+ )
+ // Expect the default configuration
+ val expectedConfiguration =
+ PhotopickerConfiguration(
+ runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED,
+ action = "",
+ sessionId = sessionId
+ )
+
+ val emissions = mutableListOf<PhotopickerConfiguration>()
+ backgroundScope.launch { configurationManager.configuration.toList(emissions) }
+
+ advanceTimeBy(100)
+ configurationManager.setEmbeddedPhotopickerFeatureInfo(featureInfo)
+ advanceTimeBy(100)
+
+ assertThat(emissions.size).isEqualTo(2)
+ assertThat(emissions.first()).isEqualTo(expectedConfiguration)
+ assertThat(emissions.last().selectionLimit)
+ .isEqualTo(MediaStore.getPickImagesMaxLimit())
+ }
+ }
+
+ /**
+ * Ensures that [ConfigurationManager.configuration] will emit an updated
+ * [PhotopickerConfiguration] with the expected [PhotopickerConfiguration.mimeTypes].
+ */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSetEmbeddedPhotopickerFeatureInfoSetsMimeTypes() {
+ val featureInfo =
+ EmbeddedPhotopickerFeatureInfo.Builder()
+ .setMimeTypes(arrayListOf("image/png", "video/mp4"))
+ .build()
+
+ runTest {
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ sessionId = sessionId
+ )
+ // Expect the default configuration
+ val expectedConfiguration =
+ PhotopickerConfiguration(
+ runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED,
+ action = "",
+ sessionId = sessionId
+ )
+
+ val emissions = mutableListOf<PhotopickerConfiguration>()
+ backgroundScope.launch { configurationManager.configuration.toList(emissions) }
+
+ advanceTimeBy(100)
+ configurationManager.setEmbeddedPhotopickerFeatureInfo(featureInfo)
+ advanceTimeBy(100)
+
+ assertThat(emissions.size).isEqualTo(2)
+ assertThat(emissions.first()).isEqualTo(expectedConfiguration)
+ assertThat(emissions.last().mimeTypes).isEqualTo(arrayListOf("image/png", "video/mp4"))
+ }
+ }
+
+ /**
+ * Ensures that [ConfigurationManager.configuration] will emit an updated
+ * [PhotopickerConfiguration] with the expected [PhotopickerConfiguration.pickImagesInOrder].
+ */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSetEmbeddedPhotopickerFeatureInfoSetsPickImagesInOrder() {
+ val featureInfo = EmbeddedPhotopickerFeatureInfo.Builder().setOrderedSelection(true).build()
+
+ runTest {
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ sessionId = sessionId
+ )
+ // Expect the default configuration
+ val expectedConfiguration =
+ PhotopickerConfiguration(
+ runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED,
+ action = "",
+ sessionId = sessionId
+ )
+
+ val emissions = mutableListOf<PhotopickerConfiguration>()
+ backgroundScope.launch { configurationManager.configuration.toList(emissions) }
+
+ advanceTimeBy(100)
+ configurationManager.setEmbeddedPhotopickerFeatureInfo(featureInfo)
+ advanceTimeBy(100)
+
+ assertThat(emissions.size).isEqualTo(2)
+ assertThat(emissions.first()).isEqualTo(expectedConfiguration)
+ assertThat(emissions.last().pickImagesInOrder).isTrue()
+ }
+ }
+
+ /**
+ * Ensures that [ConfigurationManager.configuration] will emit an updated
+ * [PhotopickerConfiguration] with the expected [PhotopickerConfiguration.preSelectedUris].
+ */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSetEmbeddedPhotopickerFeatureInfoSetsPreSelectedUris() {
+ val featureInfo =
+ EmbeddedPhotopickerFeatureInfo.Builder()
+ .setPreSelectedUris(arrayListOf(Uri.EMPTY))
+ .build()
+
+ runTest {
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ sessionId = sessionId
+ )
+ // Expect the default configuration
+ val expectedConfiguration =
+ PhotopickerConfiguration(
+ runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED,
+ action = "",
+ sessionId = sessionId
+ )
+
+ val emissions = mutableListOf<PhotopickerConfiguration>()
+ backgroundScope.launch { configurationManager.configuration.toList(emissions) }
+
+ advanceTimeBy(100)
+ configurationManager.setEmbeddedPhotopickerFeatureInfo(featureInfo)
+ advanceTimeBy(100)
+
+ assertThat(emissions.size).isEqualTo(2)
+ assertThat(emissions.first()).isEqualTo(expectedConfiguration)
+ assertThat(emissions.last().preSelectedUris).isEqualTo(arrayListOf(Uri.EMPTY))
+ }
+ }
}
diff --git a/photopicker/tests/src/com/android/photopicker/core/configuration/TestPhotopickerConfiguration.kt b/photopicker/tests/src/com/android/photopicker/core/configuration/TestPhotopickerConfiguration.kt
index f65cadc64..64ffb443b 100644
--- a/photopicker/tests/src/com/android/photopicker/core/configuration/TestPhotopickerConfiguration.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/configuration/TestPhotopickerConfiguration.kt
@@ -18,17 +18,31 @@ package com.android.photopicker.core.configuration
import android.content.Intent
import android.provider.MediaStore
+import com.android.photopicker.core.events.generatePickerSessionId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn
+val testSessionId = generatePickerSessionId()
+
/** A [PhotopickerConfiguration] that allows selection of only a single item. */
-val SINGLE_SELECT_CONFIG = PhotopickerConfiguration(action = "", selectionLimit = 1)
+val SINGLE_SELECT_CONFIG =
+ PhotopickerConfiguration(action = "", selectionLimit = 1, sessionId = testSessionId)
/** A [PhotopickerConfiguration] that allows selection of multiple (50 in this case) items. */
-val MULTI_SELECT_CONFIG = PhotopickerConfiguration(action = "", selectionLimit = 50)
+val MULTI_SELECT_CONFIG =
+ PhotopickerConfiguration(action = "", selectionLimit = 50, sessionId = testSessionId)
+
+/** A test package name used in test photopicker configurations. */
+val TEST_CALLING_PACKAGE = "com.example.test"
+
+/** A test calling uid used in test photopicker configurations. */
+val TEST_CALLING_UID = 1234
+
+/** A test package label used in test photopicker configurations. */
+val TEST_CALLING_PACKAGE_LABEL = "test_app"
/**
* A [PhotopickerConfiguration] that can be used with most tests, that comes with sensible default
@@ -38,6 +52,7 @@ val testPhotopickerConfiguration: PhotopickerConfiguration =
PhotopickerConfiguration(
action = "TEST_ACTION",
intent = Intent("TEST_ACTION"),
+ sessionId = testSessionId
)
/**
@@ -48,6 +63,7 @@ val testActionPickImagesConfiguration: PhotopickerConfiguration =
PhotopickerConfiguration(
action = MediaStore.ACTION_PICK_IMAGES,
intent = Intent(MediaStore.ACTION_PICK_IMAGES),
+ sessionId = testSessionId
)
/**
@@ -58,6 +74,7 @@ val testGetContentConfiguration: PhotopickerConfiguration =
PhotopickerConfiguration(
action = Intent.ACTION_GET_CONTENT,
intent = Intent(Intent.ACTION_GET_CONTENT),
+ sessionId = testSessionId
)
/**
@@ -68,6 +85,10 @@ val testUserSelectImagesForAppConfiguration: PhotopickerConfiguration =
PhotopickerConfiguration(
action = MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP,
intent = Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP),
+ callingPackage = TEST_CALLING_PACKAGE,
+ callingPackageUid = TEST_CALLING_UID,
+ callingPackageLabel = TEST_CALLING_PACKAGE_LABEL,
+ sessionId = testSessionId
)
/**
@@ -75,7 +96,11 @@ val testUserSelectImagesForAppConfiguration: PhotopickerConfiguration =
* [PhotopickerRuntimeEnv.EMBEDDED]
*/
val testEmbeddedPhotopickerConfiguration: PhotopickerConfiguration =
- PhotopickerConfiguration(action = "", runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED)
+ PhotopickerConfiguration(
+ action = "",
+ runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED,
+ sessionId = testSessionId
+ )
/**
* Helper function to generate a [StateFlow] that mimics the flow emitted by the
diff --git a/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedFeaturesTest.kt b/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedFeaturesTest.kt
new file mode 100644
index 000000000..480233e33
--- /dev/null
+++ b/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedFeaturesTest.kt
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photopicker.core.embedded
+
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.Context
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Build
+import android.os.Parcel
+import android.os.UserHandle
+import android.os.UserManager
+import android.test.mock.MockContentResolver
+import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.hasAnyChild
+import androidx.compose.ui.test.hasClickAction
+import androidx.compose.ui.test.hasContentDescription
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onAllNodesWithContentDescription
+import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeLeft
+import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
+import androidx.test.filters.SdkSuppress
+import com.android.photopicker.R
+import com.android.photopicker.core.ActivityModule
+import com.android.photopicker.core.ApplicationModule
+import com.android.photopicker.core.ApplicationOwned
+import com.android.photopicker.core.Background
+import com.android.photopicker.core.EmbeddedServiceModule
+import com.android.photopicker.core.Main
+import com.android.photopicker.core.PhotopickerApp
+import com.android.photopicker.core.ViewModelModule
+import com.android.photopicker.core.banners.BannerManager
+import com.android.photopicker.core.configuration.ConfigurationManager
+import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
+import com.android.photopicker.core.configuration.testEmbeddedPhotopickerConfiguration
+import com.android.photopicker.core.database.DatabaseManager
+import com.android.photopicker.core.events.Event
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.features.FeatureToken
+import com.android.photopicker.core.features.LocalFeatureManager
+import com.android.photopicker.core.glide.GlideTestRule
+import com.android.photopicker.core.navigation.PhotopickerDestinations
+import com.android.photopicker.core.selection.LocalSelection
+import com.android.photopicker.core.selection.Selection
+import com.android.photopicker.core.theme.PhotopickerTheme
+import com.android.photopicker.data.model.Media
+import com.android.photopicker.data.model.MediaSource
+import com.android.photopicker.features.overflowmenu.OverflowMenuFeature
+import com.android.photopicker.features.preview.PreviewFeature
+import com.android.photopicker.features.snackbar.SnackbarFeature
+import com.android.photopicker.inject.EmbeddedTestModule
+import com.android.photopicker.test.utils.MockContentProviderWrapper
+import com.android.photopicker.tests.HiltTestActivity
+import com.android.photopicker.tests.utils.mockito.whenever
+import com.google.common.truth.Truth.assertWithMessage
+import dagger.Lazy
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.android.testing.BindValue
+import dagger.hilt.android.testing.HiltAndroidRule
+import dagger.hilt.android.testing.HiltAndroidTest
+import dagger.hilt.android.testing.UninstallModules
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withContext
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+
+@UninstallModules(
+ ActivityModule::class,
+ ApplicationModule::class,
+ EmbeddedServiceModule::class,
+ ViewModelModule::class,
+)
+@HiltAndroidTest
+@OptIn(ExperimentalCoroutinesApi::class, ExperimentalTestApi::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class EmbeddedFeaturesTest : EmbeddedPhotopickerFeatureBaseTest() {
+ /** Hilt's rule needs to come first to ensure the DI container is setup for the test. */
+ @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this)
+
+ @get:Rule(order = 1)
+ val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
+
+ /** Setup dependencies for the UninstallModules for the test class. */
+ @Module @InstallIn(SingletonComponent::class) class TestModule : EmbeddedTestModule()
+
+ val testDispatcher = StandardTestDispatcher()
+
+ /* Overrides for EmbeddedServiceModule */
+ val testScope: TestScope = TestScope(testDispatcher)
+
+ @BindValue @Main val mainScope: CoroutineScope = testScope
+
+ @BindValue @Background var testBackgroundScope: CoroutineScope = testScope.backgroundScope
+
+ @Inject @Main lateinit var mainDispatcher: CoroutineDispatcher
+
+ /* Overrides for ViewModelModule */
+ @BindValue val viewModelScopeOverride: CoroutineScope? = testScope.backgroundScope
+
+ /**
+ * Preview uses Glide for loading images, so we have to mock out the dependencies for Glide
+ * Replace the injected ContentResolver binding in [ApplicationModule] with this test value.
+ */
+ @BindValue @ApplicationOwned lateinit var contentResolver: ContentResolver
+ private lateinit var provider: MockContentProviderWrapper
+ @Mock lateinit var mockContentProvider: ContentProvider
+
+ @Inject lateinit var events: Events
+ @Inject lateinit var selection: Selection<Media>
+ @Inject lateinit var featureManager: FeatureManager
+ @Inject lateinit var userHandle: UserHandle
+ @Inject lateinit var bannerManager: Lazy<BannerManager>
+ @Inject lateinit var embeddedLifecycle: EmbeddedLifecycle
+ @Inject lateinit var databaseManager: DatabaseManager
+ @Inject override lateinit var configurationManager: ConfigurationManager
+
+ // Needed for UserMonitor
+ @Inject lateinit var mockContext: Context
+ @Mock lateinit var mockUserManager: UserManager
+ @Mock lateinit var mockPackageManager: PackageManager
+
+ private val USER_HANDLE_MANAGED: UserHandle
+ private val USER_ID_MANAGED: Int = 10
+
+ init {
+
+ // Create a UserHandle for a managed profile.
+ val parcel = Parcel.obtain()
+ parcel.writeInt(USER_ID_MANAGED)
+ parcel.setDataPosition(0)
+ USER_HANDLE_MANAGED = UserHandle(parcel)
+ parcel.recycle()
+ }
+
+ private val TEST_TAG_SELECTION_BAR = "selection_bar"
+ private val MEDIA_ITEM =
+ Media.Image(
+ mediaId = "1",
+ pickerId = 1L,
+ authority = "a",
+ mediaSource = MediaSource.LOCAL,
+ mediaUri =
+ Uri.EMPTY.buildUpon()
+ .apply {
+ scheme("content")
+ authority("media")
+ path("picker")
+ path("a")
+ path("1")
+ }
+ .build(),
+ glideLoadableUri =
+ Uri.EMPTY.buildUpon()
+ .apply {
+ scheme("content")
+ authority("a")
+ path("1")
+ }
+ .build(),
+ dateTakenMillisLong = 123456789L,
+ sizeInBytes = 1000L,
+ mimeType = "image/png",
+ standardMimeTypeExtension = 1,
+ )
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ hiltRule.inject()
+
+ // Stub for MockContentResolver constructor
+ whenever(mockContext.getApplicationInfo()) { getTestableContext().getApplicationInfo() }
+
+ // Stub out the content resolver for Glide
+ val mockContentResolver = MockContentResolver(mockContext)
+ provider = MockContentProviderWrapper(mockContentProvider)
+ mockContentResolver.addProvider(MockContentProviderWrapper.AUTHORITY, provider)
+ contentResolver = mockContentResolver
+
+ // Return a resource png so that glide actually has something to load
+ whenever(mockContentProvider.openTypedAssetFile(any(), any(), any(), any())) {
+ getTestableContext().getResources().openRawResourceFd(R.drawable.android)
+ }
+ setupTestForUserMonitor(mockContext, mockUserManager, contentResolver, mockPackageManager)
+ }
+
+ @Test
+ fun testNavigationBarIsNotDisplayedInEmbeddedWhenCollapsed() =
+ testScope.runTest {
+ val resources = getTestableContext().getResources()
+ val photosGridNavButtonLabel =
+ resources.getString(R.string.photopicker_photos_nav_button_label)
+ val albumsGridNavButtonLabel =
+ resources.getString(R.string.photopicker_albums_nav_button_label)
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateCollapsed,
+ ) {
+ callEmbeddedPhotopickerMain(
+ embeddedLifecycle = embeddedLifecycle,
+ featureManager = featureManager,
+ selection = selection,
+ events = events,
+ )
+ }
+ }
+
+ // Wait for the PhotoGridViewModel to load data and for the UI to update.
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ composeTestRule
+ .onNode(
+ hasAnyChild(hasText(photosGridNavButtonLabel)) and
+ hasAnyChild(hasText(albumsGridNavButtonLabel))
+ )
+ .assertIsNotDisplayed()
+ }
+
+ @Test
+ fun testNavigationBarIsDisplayedInEmbeddedWhenExpanded() =
+ testScope.runTest {
+ val resources = getTestableContext().getResources()
+ val photosGridNavButtonLabel =
+ resources.getString(R.string.photopicker_photos_nav_button_label)
+ val albumsGridNavButtonLabel =
+ resources.getString(R.string.photopicker_albums_nav_button_label)
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateExpanded,
+ ) {
+ callEmbeddedPhotopickerMain(
+ embeddedLifecycle = embeddedLifecycle,
+ featureManager = featureManager,
+ selection = selection,
+ events = events,
+ )
+ }
+ }
+
+ // Wait for the PhotoGridViewModel to load data and for the UI to update.
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ // Photos Grid Nav Button and Albums Grid Nav Button
+ composeTestRule
+ .onNode(hasText(photosGridNavButtonLabel))
+ .assertIsDisplayed()
+ .assert(hasClickAction())
+
+ composeTestRule
+ .onNode(hasText(albumsGridNavButtonLabel))
+ .assertIsDisplayed()
+ .assert(hasClickAction())
+ }
+
+ @Test
+ fun testSwipeLeftToNavigateDisabledInEmbeddedWhenCollapsed() =
+ testScope.runTest {
+ val resources = getTestableContext().getResources()
+ val mediaItemString = resources.getString(R.string.photopicker_media_item)
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateCollapsed,
+ ) {
+ callEmbeddedPhotopickerMain(
+ embeddedLifecycle = embeddedLifecycle,
+ featureManager = featureManager,
+ selection = selection,
+ events = events,
+ )
+ }
+ }
+
+ // Wait for the PhotoGridViewModel to load data and for the UI to update.
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ composeTestRule
+ .onAllNodesWithContentDescription(mediaItemString)
+ .onFirst()
+ .performTouchInput { swipeLeft() }
+ composeTestRule.waitForIdle()
+ val route = navController.currentBackStackEntry?.destination?.route
+ assertWithMessage("Expected swipe to be disabled")
+ .that(route)
+ .isEqualTo(PhotopickerDestinations.PHOTO_GRID.route)
+ }
+
+ @Test
+ fun testSwipeLeftToAlbumWorksInEmbeddedWhenExpanded() =
+ testScope.runTest {
+ val resources = getTestableContext().getResources()
+ val mediaItemString = resources.getString(R.string.photopicker_media_item)
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateExpanded,
+ ) {
+ callEmbeddedPhotopickerMain(
+ embeddedLifecycle = embeddedLifecycle,
+ featureManager = featureManager,
+ selection = selection,
+ events = events,
+ )
+ }
+ }
+
+ // Wait for the PhotoGridViewModel to load data and for the UI to update.
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ composeTestRule
+ .onAllNodesWithContentDescription(mediaItemString)
+ .onFirst()
+ .performTouchInput { swipeLeft() }
+ composeTestRule.waitForIdle()
+ val route = navController.currentBackStackEntry?.destination?.route
+ assertWithMessage("Expected swipe to navigate to AlbumGrid")
+ .that(route)
+ .isEqualTo(PhotopickerDestinations.ALBUM_GRID.route)
+ }
+
+ @Test
+ fun testProfileSelectorIsNotDisplayedInEmbeddedWhenCollapsed() =
+ testScope.runTest {
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateCollapsed,
+ ) {
+ callEmbeddedPhotopickerMain(
+ embeddedLifecycle = embeddedLifecycle,
+ featureManager = featureManager,
+ selection = selection,
+ events = events,
+ )
+ }
+ }
+
+ // Wait for the PhotoGridViewModel to load data and for the UI to update.
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ composeTestRule
+ .onNode(
+ hasContentDescription(
+ getTestableContext()
+ .getResources()
+ .getString(R.string.photopicker_profile_switch_button_description)
+ )
+ )
+ .assertIsNotDisplayed()
+ }
+
+ @Test
+ fun testProfileSelectorIsDisplayedInEmbeddedWhenExpanded() =
+ testScope.runTest {
+
+ // Initial setup state: Two profiles (Personal/Work), both enabled
+ whenever(mockUserManager.userProfiles) { listOf(userHandle, USER_HANDLE_MANAGED) }
+ whenever(mockUserManager.isManagedProfile(USER_ID_MANAGED)) { true }
+ whenever(mockUserManager.isQuietModeEnabled(USER_HANDLE_MANAGED)) { false }
+ whenever(mockUserManager.getProfileParent(USER_HANDLE_MANAGED)) { userHandle }
+
+ withContext(Dispatchers.Main) {
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateExpanded,
+ ) {
+ callEmbeddedPhotopickerMain(
+ embeddedLifecycle = embeddedLifecycle,
+ featureManager = featureManager,
+ selection = selection,
+ events = events,
+ )
+ }
+ }
+ }
+
+ // Wait for the PhotoGridViewModel to load data and for the UI to update.
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ composeTestRule
+ .onNode(
+ hasContentDescription(
+ getTestableContext()
+ .getResources()
+ .getString(R.string.photopicker_profile_switch_button_description)
+ )
+ )
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testSnackbarIsAlwaysEnabledInEmbedded() {
+
+ assertWithMessage("SnackbarFeature is not always enabled for action pick image")
+ .that(SnackbarFeature.Registration.isEnabled(testEmbeddedPhotopickerConfiguration))
+ .isEqualTo(true)
+ }
+
+ @Test
+ fun testSnackbarDisplaysOnEvent() =
+ testScope.runTest {
+ composeTestRule.setContent {
+ CompositionLocalProvider(
+ LocalPhotopickerConfiguration provides testEmbeddedPhotopickerConfiguration,
+ LocalEmbeddedState provides testEmbeddedStateCollapsed,
+ LocalFeatureManager provides featureManager,
+ LocalSelection provides selection,
+ LocalEvents provides events,
+ LocalEmbeddedLifecycle provides embeddedLifecycle,
+ LocalViewModelStoreOwner provides embeddedLifecycle,
+ LocalOnBackPressedDispatcherOwner provides embeddedLifecycle,
+ ) {
+ PhotopickerTheme(
+ isDarkTheme = false,
+ config = testEmbeddedPhotopickerConfiguration
+ ) {
+ PhotopickerApp(disruptiveDataNotification = flow { emit(0) })
+ }
+ }
+ }
+
+ // Advance the UI clock manually to control for the fade animations on the snackbar.
+ composeTestRule.mainClock.autoAdvance = false
+
+ val TEST_MESSAGE = "This is a test message"
+ events.dispatch(Event.ShowSnackbarMessage(FeatureToken.CORE.token, TEST_MESSAGE))
+ advanceTimeBy(500)
+
+ // Advance ui clock to allow fade in
+ composeTestRule.mainClock.advanceTimeBy(2000L)
+ composeTestRule.onNode(hasText(TEST_MESSAGE)).assertIsDisplayed()
+
+ // Advance ui clock to allow fade out
+ composeTestRule.mainClock.advanceTimeBy(10_000L)
+ composeTestRule.onNode(hasText(TEST_MESSAGE)).assertIsNotDisplayed()
+ }
+
+ @Test
+ fun testOverflowMenuDisabledInEmbedded() {
+
+ assertWithMessage("Expected OverflowMenuFeature to be disabled in embedded runtime")
+ .that(OverflowMenuFeature.Registration.isEnabled(testEmbeddedPhotopickerConfiguration))
+ .isEqualTo(false)
+ }
+
+ @Test
+ fun testPreviewDisabledInEmbedded() {
+
+ assertWithMessage("Expected PreviewFeature to be disabled in embedded runtime")
+ .that(PreviewFeature.Registration.isEnabled(testEmbeddedPhotopickerConfiguration))
+ .isEqualTo(false)
+ }
+}
diff --git a/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedPhotopickerFeatureBaseTest.kt b/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedPhotopickerFeatureBaseTest.kt
index b472aae71..1bbaa409d 100644
--- a/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedPhotopickerFeatureBaseTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedPhotopickerFeatureBaseTest.kt
@@ -21,7 +21,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import com.android.photopicker.core.PhotopickerMain
-import com.android.photopicker.core.banners.BannerManager
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.selection.Selection
@@ -47,14 +46,13 @@ abstract class EmbeddedPhotopickerFeatureBaseTest : PhotopickerFeatureBaseTest()
featureManager: FeatureManager,
selection: Selection<Media>,
events: Events,
- bannerManager: BannerManager,
) {
CompositionLocalProvider(
LocalEmbeddedLifecycle provides embeddedLifecycle,
LocalViewModelStoreOwner provides embeddedLifecycle,
LocalOnBackPressedDispatcherOwner provides embeddedLifecycle,
) {
- callPhotopickerMain(featureManager, selection, events, bannerManager)
+ callPhotopickerMain(featureManager, selection, events)
}
}
}
diff --git a/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedServiceTest.kt b/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedServiceTest.kt
index 33741f162..f011dd73f 100644
--- a/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedServiceTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedServiceTest.kt
@@ -48,6 +48,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mockito.MockitoAnnotations
@@ -111,6 +112,7 @@ class EmbeddedServiceTest {
@DisableFlags(Flags.FLAG_ENABLE_EMBEDDED_PHOTOPICKER)
@Test
+ @Ignore("b/357048672")
fun testEmbeddedServiceOnBindIsNullWhenEmbeddedDisabled() {
assertThat(embeddedService.onBind(Intent())).isNull()
}
diff --git a/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedStateManagerTest.kt b/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedStateManagerTest.kt
index e662befc3..d5a2d15f3 100644
--- a/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedStateManagerTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/embedded/EmbeddedStateManagerTest.kt
@@ -18,9 +18,13 @@ package com.android.photopicker.core.embedded
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -28,21 +32,80 @@ import org.junit.runner.RunWith
/** Unit tests for the [EmbeddedStateManager] */
@SmallTest
@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
class EmbeddedStateManagerTest {
@Test
- fun testEmitsEmbeddedState() {
- runTest {
- val embeddedStateManager = EmbeddedStateManager()
-
- val expectedEmbeddedState = EmbeddedState()
-
- backgroundScope.launch {
- val reportedEmbeddedState = embeddedStateManager.state.first()
- assertWithMessage("Reported embedded state is not correct")
- .that(reportedEmbeddedState)
- .isEqualTo(expectedEmbeddedState)
- }
+ fun testEmitsEmbeddedState() = runTest {
+ val embeddedStateManager = EmbeddedStateManager()
+
+ val expectedEmbeddedState = EmbeddedState()
+
+ backgroundScope.launch {
+ val reportedEmbeddedState = embeddedStateManager.state.first()
+ assertWithMessage("Reported embedded state is not correct")
+ .that(reportedEmbeddedState)
+ .isEqualTo(expectedEmbeddedState)
}
}
+
+ @Test
+ fun testEmitsExpandedStateChanged() = runTest {
+ val embeddedStateManager = EmbeddedStateManager()
+
+ val expectedEmbeddedState = EmbeddedState(isExpanded = false)
+
+ val emissions = mutableListOf<EmbeddedState>()
+ backgroundScope.launch { embeddedStateManager.state.toList(emissions) }
+
+ advanceTimeBy(100)
+
+ embeddedStateManager.setIsExpanded(isExpanded = true)
+
+ advanceTimeBy(100)
+
+ assertThat(emissions.size).isEqualTo(2)
+ assertThat(emissions.first()).isEqualTo(expectedEmbeddedState)
+ assertThat(emissions.last()).isEqualTo(expectedEmbeddedState.copy(isExpanded = true))
+ }
+
+ @Test
+ fun testEmitsDarkThemeStateChanged() = runTest {
+ val embeddedStateManager = EmbeddedStateManager()
+
+ val expectedEmbeddedState = EmbeddedState(isDarkTheme = false)
+
+ val emissions = mutableListOf<EmbeddedState>()
+ backgroundScope.launch { embeddedStateManager.state.toList(emissions) }
+
+ advanceTimeBy(100)
+
+ embeddedStateManager.setIsDarkTheme(isDarkTheme = true)
+
+ advanceTimeBy(100)
+
+ assertThat(emissions.size).isEqualTo(2)
+ assertThat(emissions.first()).isEqualTo(expectedEmbeddedState)
+ assertThat(emissions.last()).isEqualTo(expectedEmbeddedState.copy(isDarkTheme = true))
+ }
+
+ @Test
+ fun testTriggerRecomposeFlipsRecomposeToggle() = runTest {
+ val embeddedStateManager = EmbeddedStateManager()
+
+ val expectedEmbeddedState = EmbeddedState(recomposeToggle = false)
+
+ val emissions = mutableListOf<EmbeddedState>()
+ backgroundScope.launch { embeddedStateManager.state.toList(emissions) }
+
+ advanceTimeBy(100)
+
+ embeddedStateManager.triggerRecompose()
+
+ advanceTimeBy(100)
+
+ assertThat(emissions.size).isEqualTo(2)
+ assertThat(emissions.first()).isEqualTo(expectedEmbeddedState)
+ assertThat(emissions.last()).isEqualTo(expectedEmbeddedState.copy(recomposeToggle = true))
+ }
}
diff --git a/photopicker/tests/src/com/android/photopicker/core/embedded/SessionTest.kt b/photopicker/tests/src/com/android/photopicker/core/embedded/SessionTest.kt
index 760da82b7..9827a0689 100644
--- a/photopicker/tests/src/com/android/photopicker/core/embedded/SessionTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/embedded/SessionTest.kt
@@ -18,6 +18,7 @@ package com.android.photopicker.core.embedded
import android.content.ContentProvider
import android.content.ContentResolver
import android.content.Context
+import android.content.ContextWrapper
import android.content.pm.PackageManager
import android.hardware.display.DisplayManager
import android.net.Uri
@@ -26,12 +27,12 @@ import android.os.Build
import android.os.Process
import android.os.UserManager
import android.provider.EmbeddedPhotopickerFeatureInfo
-import android.provider.EmbeddedPhotopickerSessionResponse
import android.provider.IEmbeddedPhotopickerClient
import android.test.mock.MockContentResolver
import android.view.SurfaceView
import android.view.WindowManager
import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsNodeInteractionCollection
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasClickAction
@@ -53,14 +54,24 @@ import com.android.photopicker.core.EmbeddedServiceModule
import com.android.photopicker.core.Main
import com.android.photopicker.core.ViewModelModule
import com.android.photopicker.core.configuration.ConfigurationManager
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.selection.Selection
+import com.android.photopicker.data.DataService
+import com.android.photopicker.data.TestDataServiceImpl
+import com.android.photopicker.data.model.CollectionInfo
import com.android.photopicker.data.model.Media
+import com.android.photopicker.data.model.MediaSource
+import com.android.photopicker.data.model.Provider
import com.android.photopicker.extensions.requireSystemService
import com.android.photopicker.inject.EmbeddedTestModule
import com.android.photopicker.test.utils.MockContentProviderWrapper
import com.android.photopicker.tests.HiltTestActivity
+import com.android.photopicker.tests.utils.StubProvider
+import com.android.photopicker.tests.utils.mockito.capture
import com.android.photopicker.tests.utils.mockito.whenever
-import com.bumptech.glide.Glide
+import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import dagger.Module
import dagger.hilt.EntryPoints
@@ -80,13 +91,20 @@ import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.After
+import org.junit.Assert.fail
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@UninstallModules(
@@ -103,6 +121,7 @@ class SessionTest : EmbeddedPhotopickerFeatureBaseTest() {
@get:Rule(order = 0) var hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/** Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : EmbeddedTestModule()
@@ -126,28 +145,30 @@ class SessionTest : EmbeddedPhotopickerFeatureBaseTest() {
@BindValue @ApplicationOwned lateinit var contentResolver: ContentResolver
private lateinit var provider: MockContentProviderWrapper
@Mock lateinit var mockContentProvider: ContentProvider
+
// Needed for UserMonitor
@Mock lateinit var mockUserManager: UserManager
@Mock lateinit var mockPackageManager: PackageManager
@Inject lateinit var mockContext: Context
@Inject lateinit var embeddedServiceComponentBuilder: EmbeddedServiceComponentBuilder
@Inject lateinit var selection: Selection<Media>
+ @Inject lateinit var featureManager: FeatureManager
+ @Inject lateinit var events: Events
@Inject override lateinit var configurationManager: ConfigurationManager
+ @Inject lateinit var dataService: DataService
+ @Inject lateinit var embeddedLifecycle: EmbeddedLifecycle
- val featureInfo = EmbeddedPhotopickerFeatureInfo.Builder().build()
+ @Captor lateinit var uriCaptor: ArgumentCaptor<Uri>
- val client =
- // TODO(b/354929684): Replace AIDL implementation with wrapper class.
- object : IEmbeddedPhotopickerClient.Stub() {
+ @Captor lateinit var uriCaptor2: ArgumentCaptor<Uri>
- override fun onSessionOpened(response: EmbeddedPhotopickerSessionResponse) {}
+ @Captor lateinit var uriCaptor3: ArgumentCaptor<Uri>
- override fun onSessionError(errorMsg: String) {}
+ @Mock lateinit var mockClient: IEmbeddedPhotopickerClient.Stub
- override fun onItemSelected(uri: Uri) {}
+ private lateinit var mockTextContextWrapper: FakeTestContextWrapper
- override fun onItemDeselected(uri: Uri) {}
- }
+ private val featureInfo = EmbeddedPhotopickerFeatureInfo.Builder().build()
// Session has a surfacePackage which outlives the test if not closed, so it always needs to be
// closed at the end of each test to prevent any existing UI activity from leaking into the next
@@ -174,7 +195,9 @@ class SessionTest : EmbeddedPhotopickerFeatureBaseTest() {
displayId = displayId,
hostToken = Binder(),
featureInfo = featureInfo,
- clientCallback = client,
+ clientCallback = mockClient,
+ grantUriPermission = { _, uri -> mockTextContextWrapper.grantUriPermission(uri) },
+ revokeUriPermission = { _, uri -> mockTextContextWrapper.revokeUriPermission(uri) },
)
session = newSession
return newSession
@@ -185,6 +208,8 @@ class SessionTest : EmbeddedPhotopickerFeatureBaseTest() {
MockitoAnnotations.initMocks(this)
hiltRule.inject()
+ mockTextContextWrapper = spy(FakeTestContextWrapper())
+
whenever(mockContext.getApplicationInfo()) { getTestableContext().getApplicationInfo() }
val mockContentResolver = MockContentResolver(mockContext)
@@ -205,7 +230,6 @@ class SessionTest : EmbeddedPhotopickerFeatureBaseTest() {
// mocks from Hilt and mocks aren't leaked between tests.
session?.close()
session = null
- Glide.tearDown()
}
/**
@@ -379,4 +403,361 @@ class SessionTest : EmbeddedPhotopickerFeatureBaseTest() {
.that(configuration.callingPackageLabel)
.isNotNull()
}
+
+ @Test
+ fun testSessionSetsEmbeddedPhotopickerFeatureInfoInConfiguration() =
+ testScope.runTest {
+ val component = embeddedServiceComponentBuilder.build()
+ val entryPoint = EntryPoints.get(component, Session.EmbeddedEntryPoint::class.java)
+
+ // Create a session with the component and let it initialize.
+ getSessionUnderTest(component)
+ advanceTimeBy(100)
+
+ val configuration = entryPoint.configurationManager().get().configuration.value
+ assertWithMessage(
+ "Expected configuration to contain the featureInfo max selection limit"
+ )
+ .that(configuration.selectionLimit)
+ .isEqualTo(featureInfo.maxSelectionLimit)
+ assertWithMessage("Expected configuration to contain the featureInfo mime types")
+ .that(configuration.mimeTypes)
+ .isEqualTo(featureInfo.mimeTypes)
+ assertWithMessage(
+ "Expected configuration to contain the featureInfo ordered selection flag"
+ )
+ .that(configuration.pickImagesInOrder)
+ .isEqualTo(featureInfo.isOrderedSelection)
+ assertWithMessage("Expected configuration to contain the featureInfo pre-selected URIs")
+ .that(configuration.preSelectedUris)
+ .isEqualTo(featureInfo.preSelectedUris)
+ }
+
+ @Test
+ fun testSelectionUpdateGrantsAndRevokesPermissionSuccess() =
+ testScope.runTest {
+ val component = embeddedServiceComponentBuilder.build()
+ val session = getSessionUnderTest(component)
+
+ val itemCount = 20
+ setUpTestDataWithStubProvider(itemCount)
+
+ advanceTimeBy(100)
+
+ // Now the view is in the test's compose tree, so do a simple check to make sure
+ // the view actually initialized and the test can locate the photo grid / modify the
+ // selection.
+ composeTestRule.setContent {
+ // Wrap the surfacePackage inside of an [AndroidView] to make the view accessible to
+ // the test.
+ AndroidView(
+ factory = {
+ SurfaceView(getTestableContext()).apply {
+ setChildSurfacePackage(session.surfacePackage)
+ }
+ }
+ )
+ }
+
+ composeTestRule.waitForIdle()
+
+ clearInvocations(mockTextContextWrapper, mockClient)
+
+ val resources = getTestableContext().getResources()
+
+ // This is the accessibility label for a Photo in the grid.
+ val mediaItemString = resources.getString(R.string.photopicker_media_item)
+
+ // Get all image nodes
+ val allImageNodes = composeTestRule.onAllNodesWithContentDescription(mediaItemString)
+
+ // Make list of indices to select
+ var indicesToSelect = setOf(2, 0, 4) // Select images at indices 2, 0, and 4
+ var expectedUris: List<Uri> = constructUrisForIndices(indicesToSelect)
+
+ // Filter image nodes based on the indices to select and performClick
+ performClickForIndices(allImageNodes, indicesToSelect)
+
+ // Wait for PhotoGridViewModel to modify Selection
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ // Ensure the click handler correctly ran by checking the selection snapshot.
+ assertWithMessage("Expected selection to contain an item, but it did not.")
+ .that(selection.snapshot().size)
+ .isEqualTo(3)
+
+ // Verify that grantUriPermission is invoked for all newly selected media.
+ verify(mockTextContextWrapper, times(3)).grantUriPermission(capture(uriCaptor))
+ var capturedUris = uriCaptor.allValues
+
+ assertThat(capturedUris.toList()).containsExactlyElementsIn(expectedUris)
+
+ // Verify that client callback is invoked for all uris that were successfully
+ // granted permission
+ for (uri in expectedUris) {
+ verify(mockClient, times(1)).onItemSelected(uri)
+ }
+
+ clearInvocations(mockTextContextWrapper, mockClient)
+
+ // Make list of indices to deselect.
+ val indicesToDeselect = setOf(2, 0) // Deselect images at indices 2, 0
+ // Get difference of two list which is the final selected uris and get expectedUri list.
+ expectedUris = constructUrisForIndices(indicesToDeselect)
+
+ // Filter image nodes based on the indices to select and performClick
+ performClickForIndices(allImageNodes, indicesToDeselect)
+
+ // Wait for PhotoGridViewModel to modify Selection
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ assertWithMessage("Expected selection to contain an item, but it did not.")
+ .that(selection.snapshot().size)
+ .isEqualTo(1)
+
+ // Verify that revokeUriPermission is invoked for all newly deselected media.
+ verify(mockTextContextWrapper, times(2)).revokeUriPermission(capture(uriCaptor2))
+ capturedUris = uriCaptor2.allValues
+
+ assertThat(capturedUris.toList()).containsExactlyElementsIn(expectedUris)
+
+ // Verify that client callback is invoked for all uris that were successfully
+ // revoked permission
+ for (uri in expectedUris) {
+ verify(mockClient, times(1)).onItemDeselected(uri)
+ }
+
+ clearInvocations(mockTextContextWrapper, mockClient)
+
+ // Make list of indices to select again
+ indicesToSelect = setOf(7, 8) // Select images at indices 7,8
+ expectedUris = constructUrisForIndices(indicesToSelect)
+
+ // Filter image nodes based on the indices to select and performClick
+ performClickForIndices(allImageNodes, indicesToSelect)
+
+ // Wait for PhotoGridViewModel to modify Selection
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ assertWithMessage("Expected selection to contain an item, but it did not.")
+ .that(selection.snapshot().size)
+ .isEqualTo(3)
+
+ // Verify that grantUriPermission is invoked for all newly selected media.
+ verify(mockTextContextWrapper, times(2)).grantUriPermission(capture(uriCaptor3))
+ capturedUris = uriCaptor3.allValues
+
+ assertThat(capturedUris).containsExactlyElementsIn(expectedUris)
+
+ // Verify that client callback is invoked for all uris that were successfully
+ // granted permission
+ for (uri in expectedUris) {
+ verify(mockClient, times(1)).onItemSelected(uri)
+ }
+ }
+
+ @Test
+ fun testSelectionGrantOrRevokePermissionFailed() =
+ testScope.runTest {
+ setUpTestDataWithStubProvider(20)
+
+ // Mark image at node 0 as media item we aren't able to grant permission.
+ val grantFailureUri = constructUrisForIndices(setOf(0))[0]
+ whenever(mockTextContextWrapper.grantUriPermission(grantFailureUri)) {
+ EmbeddedService.GrantResult.FAILURE
+ }
+
+ val component = embeddedServiceComponentBuilder.build()
+ val session = getSessionUnderTest(component)
+ advanceTimeBy(100)
+
+ // Now the view is in the test's compose tree, so do a simple check to make sure
+ // the view actually initialized and the test can locate the photo grid / modify the
+ // selection.
+ composeTestRule.setContent {
+ // Wrap the surfacePackage inside of an [AndroidView] to make the view accessible to
+ // the test.
+ AndroidView(
+ factory = {
+ SurfaceView(getTestableContext()).apply {
+ setChildSurfacePackage(session.surfacePackage)
+ }
+ }
+ )
+ }
+
+ composeTestRule.waitForIdle()
+
+ val resources = getTestableContext().getResources()
+ // This is the accessibility label for a Photo in the grid.
+ val mediaItemString = resources.getString(R.string.photopicker_media_item)
+
+ // Get all image nodes
+ val allImageNodes = composeTestRule.onAllNodesWithContentDescription(mediaItemString)
+
+ // Make list of indices to select
+ var indicesToSelect = setOf(2, 0, 4) // Select images at indices 2, 0, and 4
+ var expectedUris: List<Uri> = constructUrisForIndices(indicesToSelect)
+
+ // Filter image nodes based on the indices to select and performClick
+ performClickForIndices(allImageNodes, indicesToSelect)
+
+ // Wait for PhotoGridViewModel to modify Selection
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ // Ensure the click handler correctly ran by checking the selection snapshot.
+ assertWithMessage("Expected selection to contain an item, but it did not.")
+ .that(selection.snapshot().size)
+ .isEqualTo(3)
+
+ // Verify that client callback is invoked for all uris that were successfully
+ // granted permission and never for the uri that we failed granting permission
+ verify(mockTextContextWrapper, times(3)).grantUriPermission(capture(uriCaptor))
+ var capturedUris = uriCaptor.allValues
+
+ assertThat(capturedUris.toList()).containsExactlyElementsIn(expectedUris)
+
+ for (uri in expectedUris) {
+ if (uri == grantFailureUri) continue
+ verify(mockClient, times(1)).onItemSelected(uri)
+ }
+ verify(mockClient, never()).onItemSelected(grantFailureUri)
+
+ clearInvocations(mockTextContextWrapper, mockClient)
+
+ // Mark image at node 2 as media item we aren't able to revoke permission.
+ val revokeFailureUri = constructUrisForIndices(setOf(2))[0]
+ whenever(mockTextContextWrapper.revokeUriPermission(revokeFailureUri)) {
+ EmbeddedService.GrantResult.FAILURE
+ }
+
+ // Make list of indices to select
+ var indicesToDeselect = setOf(2) // Deselect image at indices 2
+ expectedUris = constructUrisForIndices(indicesToDeselect)
+
+ // Filter image nodes based on the indices to select and performClick
+ performClickForIndices(allImageNodes, indicesToDeselect)
+
+ // Wait for PhotoGridViewModel to modify Selection
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ // Ensure the click handler correctly ran by checking the selection snapshot.
+ assertWithMessage("Expected selection to contain an item, but it did not.")
+ .that(selection.snapshot().size)
+ .isEqualTo(2) // images at indices 0, 4 are still selected
+
+ // Verify client callback is never invoked if we failed to revoke permission to uri
+ verify(mockTextContextWrapper, times(1)).revokeUriPermission(capture(uriCaptor2))
+ capturedUris = uriCaptor2.allValues
+
+ assertThat(capturedUris.toList()).containsExactlyElementsIn(expectedUris)
+
+ verify(mockClient, never()).onItemDeselected(revokeFailureUri)
+ }
+
+ /** Gets the correct nodes of media item for given indices and performs click. */
+ private fun performClickForIndices(
+ allImageNodes: SemanticsNodeInteractionCollection,
+ indicesToSelect: Set<Int>
+ ) {
+ var imageNodesToSelect =
+ indicesToSelect.mapNotNull { index ->
+ try {
+ allImageNodes.get(index)
+ } catch (e: AssertionError) {
+ // Fail the test if no node found at given position
+ fail("Unexpected AssertionError: Index out of bounds") // Fail the test
+ null
+ }
+ }
+
+ for (node in imageNodesToSelect) {
+ node.assert(hasClickAction()).assertIsDisplayed().performClick()
+ }
+ }
+
+ /** Using [StubProvider] as a backing provider, set custom number of media */
+ private fun setUpTestDataWithStubProvider(mediaCount: Int): List<Media> {
+ val stubProvider =
+ Provider(
+ authority = StubProvider.AUTHORITY,
+ mediaSource = MediaSource.LOCAL,
+ uid = 1,
+ displayName = "Stub Provider"
+ )
+
+ val testDataService = dataService as? TestDataServiceImpl
+ checkNotNull(testDataService) { "Expected a TestDataServiceImpl" }
+ testDataService.setAvailableProviders(listOf(stubProvider))
+ testDataService.collectionInfo.put(
+ stubProvider,
+ CollectionInfo(
+ authority = stubProvider.authority,
+ collectionId = null,
+ accountName = null,
+ )
+ )
+
+ val testImages = StubProvider.getTestMediaFromStubProvider(mediaCount)
+ testDataService.mediaList = testImages
+ return testImages
+ }
+
+ /**
+ * Fake [ContextWrapper] class to mock [ContextWrapper#grantUriPermission] and
+ * [ContextWrapper#revokeUriPermission] in tests.
+ *
+ * These methods by default return Success. Tests can manipulate the behaviour for specific uri
+ * in their tests as we are spying this class instead of mocking.
+ */
+ open class FakeTestContextWrapper {
+ open fun grantUriPermission(uri: Uri): EmbeddedService.GrantResult {
+ return EmbeddedService.GrantResult.SUCCESS
+ }
+
+ open fun revokeUriPermission(uri: Uri): EmbeddedService.GrantResult {
+ return EmbeddedService.GrantResult.SUCCESS
+ }
+ }
+
+ /**
+ * Constructs URI for given indices for test.
+ *
+ * Follows format "content://stubprovider/$id"
+ */
+ private fun constructUrisForIndices(uriIndices: Set<Int>): List<Uri> {
+ val newUris = uriIndices.map { index -> Uri.parse("content://stubprovider/${index + 1}") }
+ return newUris
+ }
+
+ @Test
+ fun testSessionNotifyResizedChangesViewSize() =
+ testScope.runTest {
+ val component = embeddedServiceComponentBuilder.build()
+
+ val session = getSessionUnderTest(component)
+ advanceTimeBy(100)
+
+ val initialWidth = session.getView().width
+ val initialHeight = session.getView().height
+
+ val newWidth = 2 * initialWidth
+ val newHeight = 2 * initialHeight
+
+ async { session.notifyResized(newWidth, newHeight) }.await()
+ advanceTimeBy(100)
+
+ assertWithMessage("Expected view's width to be resized")
+ .that(session.getView().width)
+ .isEqualTo(newWidth)
+
+ assertWithMessage("Expected view's height to be resized")
+ .that(session.getView().height)
+ .isEqualTo(newHeight)
+ }
}
diff --git a/photopicker/tests/src/com/android/photopicker/core/events/EventsTest.kt b/photopicker/tests/src/com/android/photopicker/core/events/EventsTest.kt
index 81f7c079d..22a56d820 100644
--- a/photopicker/tests/src/com/android/photopicker/core/events/EventsTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/events/EventsTest.kt
@@ -57,6 +57,7 @@ class EventsTest {
val token = "MockedFeatureToken"
}
private val testRegistrations = setOf(mockRegistration)
+ private val sessionId = generatePickerSessionId()
private data class TestEvent(override val dispatcherToken: String) : Event
@@ -213,7 +214,11 @@ class EventsTest {
scope = backgroundScope,
provideTestConfigurationFlow(
scope = backgroundScope,
- PhotopickerConfiguration(action = "TEST", deviceIsDebuggable = true)
+ PhotopickerConfiguration(
+ action = "TEST",
+ deviceIsDebuggable = true,
+ sessionId = sessionId
+ )
),
buildFeatureManagerWithFeatures(testRegistrations, backgroundScope)
)
@@ -241,7 +246,11 @@ class EventsTest {
scope = backgroundScope,
provideTestConfigurationFlow(
scope = backgroundScope,
- PhotopickerConfiguration(action = "TEST", deviceIsDebuggable = false)
+ PhotopickerConfiguration(
+ action = "TEST",
+ deviceIsDebuggable = false,
+ sessionId = sessionId
+ )
),
buildFeatureManagerWithFeatures(testRegistrations, backgroundScope)
)
diff --git a/photopicker/tests/src/com/android/photopicker/core/features/FeatureManagerTest.kt b/photopicker/tests/src/com/android/photopicker/core/features/FeatureManagerTest.kt
index 09ec82e3a..5f2dbff08 100644
--- a/photopicker/tests/src/com/android/photopicker/core/features/FeatureManagerTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/features/FeatureManagerTest.kt
@@ -36,6 +36,7 @@ import com.android.photopicker.core.configuration.provideTestConfigurationFlow
import com.android.photopicker.core.configuration.testPhotopickerConfiguration
import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.RegisteredEventClass
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.features.alwaysdisabledfeature.AlwaysDisabledFeature
import com.android.photopicker.features.highpriorityuifeature.HighPriorityUiFeature
import com.android.photopicker.features.simpleuifeature.SimpleUiFeature
@@ -77,6 +78,8 @@ class FeatureManagerTest {
AlwaysDisabledFeature.Registration,
)
+ val sessionId = generatePickerSessionId()
+
@Composable
private fun featureManagerTestUiComposeTop(
featureManager: FeatureManager,
@@ -263,6 +266,7 @@ class FeatureManagerTest {
PhotopickerConfiguration(
action = "TEST",
deviceIsDebuggable = true,
+ sessionId = sessionId
)
)
@@ -300,6 +304,7 @@ class FeatureManagerTest {
PhotopickerConfiguration(
action = "TEST",
deviceIsDebuggable = false,
+ sessionId = sessionId
)
)
diff --git a/photopicker/tests/src/com/android/photopicker/core/glide/GlideTestRule.kt b/photopicker/tests/src/com/android/photopicker/core/glide/GlideTestRule.kt
new file mode 100644
index 000000000..1f5995fe1
--- /dev/null
+++ b/photopicker/tests/src/com/android/photopicker/core/glide/GlideTestRule.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photopicker.core.glide
+
+import androidx.test.core.app.ApplicationProvider
+import com.bumptech.glide.Glide
+import com.bumptech.glide.GlideBuilder
+import com.bumptech.glide.load.engine.executor.GlideExecutor
+import com.bumptech.glide.load.engine.executor.MockGlideExecutor
+import org.junit.rules.ExternalResource
+
+/**
+ * A JUnit rule that configures Glide for use in tests.
+ *
+ * ```
+ * @get:Rule val glideRule = GlideTestRule()
+ * ```
+ */
+class GlideTestRule : ExternalResource() {
+
+ override fun before() {
+
+ // For tests, force Glide onto the main thread, rather than it's private executor pool.
+ val glideExecutor: GlideExecutor = MockGlideExecutor.newMainThreadExecutor()
+ Glide.init(
+ ApplicationProvider.getApplicationContext(),
+ GlideBuilder()
+ .setDiskCacheExecutor(glideExecutor)
+ .setAnimationExecutor(glideExecutor)
+ .setSourceExecutor(glideExecutor),
+ )
+ }
+
+ override fun after() {
+ Glide.tearDown()
+ }
+}
diff --git a/photopicker/tests/src/com/android/photopicker/core/glide/LoadMediaTest.kt b/photopicker/tests/src/com/android/photopicker/core/glide/LoadMediaTest.kt
index db0e7996e..d9b83f6ea 100644
--- a/photopicker/tests/src/com/android/photopicker/core/glide/LoadMediaTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/glide/LoadMediaTest.kt
@@ -41,7 +41,6 @@ import com.android.photopicker.test.utils.GlideLoadableIdlingResource
import com.android.photopicker.test.utils.MockContentProviderWrapper
import com.android.photopicker.tests.utils.mockito.capture
import com.android.photopicker.tests.utils.mockito.whenever
-import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
@@ -98,8 +97,9 @@ import org.mockito.MockitoAnnotations
class LoadMediaTest {
/** Hilt's rule needs to come first to ensure the DI container is setup for the test. */
- @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this)
+ @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1) val composeTestRule = createComposeRule()
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
private val glideIdlingResource: GlideLoadableIdlingResource = GlideLoadableIdlingResource()
private lateinit var provider: MockContentProviderWrapper
@@ -168,10 +168,6 @@ class LoadMediaTest {
fun teardown() {
composeTestRule.unregisterIdlingResource(glideIdlingResource)
glideIdlingResource.reset()
-
- // It is important to tearDown glide after every test to ensure it picks up the updated
- // mocks from Hilt and mocks aren't leaked between tests.
- Glide.tearDown()
}
/** Ensures that a [GlideLoadable] can be loaded via the [loadMedia] composable using Glide. */
diff --git a/photopicker/tests/src/com/android/photopicker/core/navigation/PhotopickerNavGraphTest.kt b/photopicker/tests/src/com/android/photopicker/core/navigation/PhotopickerNavGraphTest.kt
index a244ba344..91258ac40 100644
--- a/photopicker/tests/src/com/android/photopicker/core/navigation/PhotopickerNavGraphTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/navigation/PhotopickerNavGraphTest.kt
@@ -32,6 +32,7 @@ import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
import com.android.photopicker.core.configuration.PhotopickerConfiguration
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
import com.android.photopicker.core.events.RegisteredEventClass
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureRegistration
import com.android.photopicker.core.features.LocalFeatureManager
@@ -56,6 +57,7 @@ class PhotopickerNavGraphTest {
lateinit var navController: TestNavHostController
lateinit var featureManager: FeatureManager
+ private val sessionId = generatePickerSessionId()
val testRegistrations =
setOf(
@@ -86,7 +88,8 @@ class PhotopickerNavGraphTest {
@Composable
private fun testNavGraph(
featureManager: FeatureManager,
- configuration: PhotopickerConfiguration = PhotopickerConfiguration(action = "")
+ configuration: PhotopickerConfiguration =
+ PhotopickerConfiguration(action = "", sessionId = sessionId)
) {
navController = TestNavHostController(LocalContext.current)
navController.navigatorProvider.addNavigator(ComposeNavigator())
@@ -150,7 +153,8 @@ class PhotopickerNavGraphTest {
val config =
PhotopickerConfiguration(
action = "",
- startDestination = PhotopickerDestinations.ALBUM_GRID
+ startDestination = PhotopickerDestinations.ALBUM_GRID,
+ sessionId = sessionId
)
composeTestRule.setContent { testNavGraph(featureManager, config) }
@@ -165,7 +169,8 @@ class PhotopickerNavGraphTest {
val config =
PhotopickerConfiguration(
action = "",
- startDestination = PhotopickerDestinations.PHOTO_GRID
+ startDestination = PhotopickerDestinations.PHOTO_GRID,
+ sessionId = sessionId
)
composeTestRule.setContent { testNavGraph(featureManager, config) }
diff --git a/photopicker/tests/src/com/android/photopicker/core/selection/GrantsAwareSelectionTest.kt b/photopicker/tests/src/com/android/photopicker/core/selection/GrantsAwareSelectionTest.kt
index 1302f6022..d6c68e3fc 100644
--- a/photopicker/tests/src/com/android/photopicker/core/selection/GrantsAwareSelectionTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/core/selection/GrantsAwareSelectionTest.kt
@@ -1,18 +1,18 @@
/*
-* Copyright 2024 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.photopicker.core.selection
@@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest
import com.android.photopicker.core.configuration.MULTI_SELECT_CONFIG
import com.android.photopicker.core.configuration.SINGLE_SELECT_CONFIG
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
+import com.android.photopicker.data.TestDataServiceImpl
import com.android.photopicker.data.model.Grantable
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,10 +58,11 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = SINGLE_SELECT_CONFIG
- )
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = SINGLE_SELECT_CONFIG,
+ ),
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
val snapshot = selection.snapshot()
@@ -77,11 +79,12 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = MULTI_SELECT_CONFIG
- ),
- initialSelection = INITIAL_SELECTION
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ initialSelection = INITIAL_SELECTION,
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
val snapshot = selection.snapshot()
@@ -99,17 +102,38 @@ class GrantsAwareSelectionTest {
}
@Test
- fun testSelectionReturnsSuccess() = runTest {
+ fun testPreGrantsCountIsReflectedInSize() = runTest {
+ var countOfGrants = 120
+ val dataService = TestDataServiceImpl()
+ dataService.setInitPreGrantsCount(countOfGrants)
val selection: Selection<SelectionData> =
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = MULTI_SELECT_CONFIG
- ),
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ preGrantedItemsCount = dataService.preGrantedMediaCount
)
+ assertWithMessage("Unexpected size of selection")
+ .that(selection.snapshot().size)
+ .isEqualTo(countOfGrants)
+ }
+
+ @Test
+ fun testSelectionReturnsSuccess() = runTest {
+ val selection: Selection<SelectionData> =
+ GrantsAwareSelectionImpl(
+ scope = backgroundScope,
+ configuration =
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
+ )
assertWithMessage("Selection addition was expected to be successful: item 1")
.that(selection.add(SelectionData(1)))
@@ -128,14 +152,14 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = SINGLE_SELECT_CONFIG
- ),
- initialSelection = setOf(SelectionData(1))
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = SINGLE_SELECT_CONFIG
+ ),
+ initialSelection = setOf(SelectionData(1)),
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
-
assertWithMessage("Snapshot was expected to contain the initial selection")
.that(selection.add(SelectionData(2)))
.isEqualTo(SelectionModifiedResult.FAILURE_SELECTION_LIMIT_EXCEEDED)
@@ -148,10 +172,11 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = MULTI_SELECT_CONFIG
- )
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
val emissions = mutableListOf<Set<SelectionData>>()
backgroundScope.launch { selection.flow.toList(emissions) }
@@ -181,10 +206,11 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = MULTI_SELECT_CONFIG
- )
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
val emissions = mutableListOf<Set<SelectionData>>()
backgroundScope.launch { selection.flow.toList(emissions) }
@@ -221,11 +247,12 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = MULTI_SELECT_CONFIG
- ),
- initialSelection = INITIAL_SELECTION
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ initialSelection = INITIAL_SELECTION,
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
val emissions = mutableListOf<Set<SelectionData>>()
backgroundScope.launch { selection.flow.toList(emissions) }
@@ -260,11 +287,12 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = MULTI_SELECT_CONFIG
- ),
- initialSelection = setOf(testItem, anotherTestItem)
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ initialSelection = setOf(testItem, anotherTestItem),
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
val emissions = mutableListOf<Set<SelectionData>>()
backgroundScope.launch { selection.flow.toList(emissions) }
@@ -311,11 +339,12 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = MULTI_SELECT_CONFIG
- ),
- initialSelection = values
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ initialSelection = values,
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
val emissions = mutableListOf<Set<SelectionData>>()
backgroundScope.launch { selection.flow.toList(emissions) }
@@ -350,11 +379,12 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = MULTI_SELECT_CONFIG
- ),
- initialSelection = INITIAL_SELECTION
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ initialSelection = INITIAL_SELECTION,
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
val emissions = mutableListOf<Set<SelectionData>>()
backgroundScope.launch { selection.flow.toList(emissions) }
@@ -391,11 +421,12 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = MULTI_SELECT_CONFIG
- ),
- initialSelection = INITIAL_SELECTION
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ initialSelection = INITIAL_SELECTION,
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
val emissions = mutableListOf<Set<SelectionData>>()
backgroundScope.launch { selection.flow.toList(emissions) }
@@ -442,11 +473,12 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = MULTI_SELECT_CONFIG
- ),
- initialSelection = values
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ initialSelection = values,
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
assertWithMessage("Received unexpected position for item.")
@@ -461,11 +493,12 @@ class GrantsAwareSelectionTest {
GrantsAwareSelectionImpl(
scope = backgroundScope,
configuration =
- provideTestConfigurationFlow(
- scope = backgroundScope,
- defaultConfiguration = MULTI_SELECT_CONFIG
- ),
- initialSelection = INITIAL_SELECTION
+ provideTestConfigurationFlow(
+ scope = backgroundScope,
+ defaultConfiguration = MULTI_SELECT_CONFIG
+ ),
+ initialSelection = INITIAL_SELECTION,
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount,
)
val missingElement = SelectionData(id = 999)
@@ -477,69 +510,70 @@ class GrantsAwareSelectionTest {
/** Ensures a single preGranted item can be removed and added again. */
@Test
- fun testSelectionCanRemoveSinglePreGrantedItem() =
- runTest {
- // mock a test item to return isPreGranted as true.
- val testItem = SelectionData(id = 999, isPreGrantedParam = true)
-
- val selection =
- GrantsAwareSelectionImpl<SelectionData>(
- scope = backgroundScope,
- configuration =
+ fun testSelectionCanRemoveSinglePreGrantedItem() = runTest {
+ // mock a test item to return isPreGranted as true.
+ val testItem = SelectionData(id = 999, isPreGrantedParam = true)
+
+ val dataService = TestDataServiceImpl()
+ dataService.setInitPreGrantsCount(1)
+ val selection =
+ GrantsAwareSelectionImpl<SelectionData>(
+ scope = backgroundScope,
+ configuration =
provideTestConfigurationFlow(
scope = backgroundScope,
defaultConfiguration = MULTI_SELECT_CONFIG,
),
- preGrantedItemsCount = 1, // corresponding to testItem
- )
-
- val emissions = mutableListOf<Set<SelectionData>>()
- backgroundScope.launch { selection.flow.toList(emissions) }
-
- val initialSnapshot = selection.snapshot()
- // There is only one preGranted item
- assertWithMessage("Initial Snapshot has an unexpected size")
- .that(initialSnapshot)
- .hasSize(1)
-
- // remove preGranted item
- selection.remove(testItem)
-
- val snapshot = selection.snapshot()
- advanceTimeBy(100)
- val flow = emissions.last()
-
- assertWithMessage("Deselection should contain test item").that(
- selection.getDeselection()
- ).contains(testItem)
-
- assertWithMessage("Snapshot contains the removed item.")
- .that(snapshot).doesNotContain(testItem)
-
- assertWithMessage("Emitted flow value contains the removed item.")
- .that(flow)
- .doesNotContain(testItem)
- assertWithMessage("Emitted flow has an unexpected size").that(flow).hasSize(0)
-
- // Now add the preGranted item again and verify that it was removed from deselection.
- selection.add(testItem)
-
- val snapshot2 = selection.snapshot()
- advanceTimeBy(100)
- val flow2 = emissions.last()
- assertWithMessage("Deselection should not contain test item").that(
- selection
- .getDeselection(),
+ preGrantedItemsCount = dataService.preGrantedMediaCount
)
- .doesNotContain(testItem)
- assertWithMessage("Snapshot contains the added item.")
- .that(snapshot2).contains(testItem)
- assertWithMessage("Snapshot has an unexpected size").that(snapshot2).hasSize(1)
+ val emissions = mutableListOf<Set<SelectionData>>()
+ backgroundScope.launch { selection.flow.toList(emissions) }
- assertWithMessage("Emitted flow value contains the removed item.")
- .that(flow2)
- .contains(testItem)
- assertWithMessage("Emitted flow has an unexpected size").that(flow2).hasSize(1)
- }
-} \ No newline at end of file
+ val initialSnapshot = selection.snapshot()
+ // There is only one preGranted item
+ assertWithMessage("Initial Snapshot has an unexpected size")
+ .that(initialSnapshot)
+ .hasSize(1)
+
+ // remove preGranted item
+ selection.remove(testItem)
+
+ val snapshot = selection.snapshot()
+ advanceTimeBy(100)
+ val flow = emissions.last()
+
+ assertWithMessage("Deselection should contain test item")
+ .that(selection.getDeselection())
+ .contains(testItem)
+
+ assertWithMessage("Snapshot contains the removed item.")
+ .that(snapshot)
+ .doesNotContain(testItem)
+
+ assertWithMessage("Emitted flow value contains the removed item.")
+ .that(flow)
+ .doesNotContain(testItem)
+ assertWithMessage("Emitted flow has an unexpected size").that(flow).hasSize(0)
+
+ // Now add the preGranted item again and verify that it was removed from deselection.
+ selection.add(testItem)
+
+ val snapshot2 = selection.snapshot()
+ advanceTimeBy(100)
+ val flow2 = emissions.last()
+ assertWithMessage("Deselection should not contain test item")
+ .that(
+ selection.getDeselection(),
+ )
+ .doesNotContain(testItem)
+
+ assertWithMessage("Snapshot contains the added item.").that(snapshot2).contains(testItem)
+ assertWithMessage("Snapshot has an unexpected size").that(snapshot2).hasSize(1)
+
+ assertWithMessage("Emitted flow value contains the removed item.")
+ .that(flow2)
+ .contains(testItem)
+ assertWithMessage("Emitted flow has an unexpected size").that(flow2).hasSize(1)
+ }
+}
diff --git a/photopicker/tests/src/com/android/photopicker/data/DataServiceImplTest.kt b/photopicker/tests/src/com/android/photopicker/data/DataServiceImplTest.kt
index 3e1da0f75..ccee584e4 100644
--- a/photopicker/tests/src/com/android/photopicker/data/DataServiceImplTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/data/DataServiceImplTest.kt
@@ -33,7 +33,9 @@ import com.android.photopicker.core.configuration.PhotopickerConfiguration
import com.android.photopicker.core.configuration.PhotopickerFlags
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
import com.android.photopicker.core.configuration.testPhotopickerConfiguration
+import com.android.photopicker.core.events.Events
import com.android.photopicker.core.events.RegisteredEventClass
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.user.UserProfile
import com.android.photopicker.core.user.UserStatus
@@ -96,6 +98,8 @@ class DataServiceImplTest {
)
}
+ private val sessionId = generatePickerSessionId()
+
private lateinit var testFeatureManager: FeatureManager
private lateinit var testContentProvider: TestMediaProvider
private lateinit var testContentResolver: ContentResolver
@@ -104,6 +108,7 @@ class DataServiceImplTest {
private lateinit var userStatus: UserStatus
private lateinit var mockContext: Context
private lateinit var mockPackageManager: PackageManager
+ private lateinit var events: Events
@Before
fun setup() {
@@ -133,6 +138,12 @@ class DataServiceImplTest {
@Test
fun testInitialAllowedProvider() = runTest {
val userStatusFlow: StateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val dataService: DataService =
DataServiceImpl(
@@ -143,7 +154,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
val emissions = mutableListOf<List<Provider>>()
@@ -159,6 +171,12 @@ class DataServiceImplTest {
@Test
fun testUpdateAvailableProviders() = runTest {
val userStatusFlow: StateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val dataService: DataService =
DataServiceImpl(
@@ -169,7 +187,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
val emissions = mutableListOf<List<Provider>>()
@@ -232,6 +251,20 @@ class DataServiceImplTest {
)
val userStatusFlow: StateFlow<UserStatus> = MutableStateFlow(userStatus)
val scope = TestScope()
+ val featureManager =
+ FeatureManager(
+ provideTestConfigurationFlow(scope = scope.backgroundScope),
+ scope,
+ setOf(), // Don't register CloudMediaFeature
+ setOf<RegisteredEventClass>(),
+ setOf<RegisteredEventClass>(),
+ )
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ featureManager
+ )
val dataService: DataService =
DataServiceImpl(
userStatus = userStatusFlow,
@@ -240,15 +273,9 @@ class DataServiceImplTest {
mediaProviderClient = mediaProviderClient,
dispatcher = StandardTestDispatcher(this.testScheduler),
config = MutableStateFlow(testPhotopickerConfiguration),
- featureManager =
- FeatureManager(
- provideTestConfigurationFlow(scope = scope.backgroundScope),
- scope,
- setOf(), // Don't register CloudMediaFeature
- setOf<RegisteredEventClass>(),
- setOf<RegisteredEventClass>(),
- ),
- appContext = mockContext
+ appContext = mockContext,
+ featureManager = featureManager,
+ events = events
)
val emissions = mutableListOf<List<Provider>>()
@@ -267,6 +294,12 @@ class DataServiceImplTest {
@Test
fun testAvailableProvidersWhenUserChanges() = runTest {
val userStatusFlow: MutableStateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val dataService: DataService =
DataServiceImpl(
@@ -277,7 +310,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
val emissions = mutableListOf<List<Provider>>()
@@ -349,6 +383,12 @@ class DataServiceImplTest {
fun testContentObserverRegistrationWhenUserChanges() = runTest {
val userStatusFlow: MutableStateFlow<UserStatus> = MutableStateFlow(userStatus)
val mockNotificationService = mock(NotificationService::class.java)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val dataService: DataService =
DataServiceImpl(
@@ -359,7 +399,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
val emissions = mutableListOf<List<Provider>>()
@@ -460,6 +501,12 @@ class DataServiceImplTest {
@Test
fun testMediaPagingSourceInvalidation() = runTest {
val userStatusFlow: MutableStateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val dataService: DataService =
DataServiceImpl(
@@ -470,7 +517,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
val emissions = mutableListOf<List<Provider>>()
@@ -522,6 +570,12 @@ class DataServiceImplTest {
@Test
fun testAlbumPagingSourceInvalidation() = runTest {
val userStatusFlow: MutableStateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val dataService: DataService =
DataServiceImpl(
@@ -532,7 +586,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
// Check initial available provider emissions
@@ -584,6 +639,12 @@ class DataServiceImplTest {
@Test
fun testAlbumMediaPagingSourceCacheUpdates() = runTest {
testContentProvider.lastRefreshMediaRequest = null
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val userStatusFlow: MutableStateFlow<UserStatus> = MutableStateFlow(userStatus)
val dataService: DataService =
@@ -595,7 +656,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
advanceTimeBy(100)
@@ -656,6 +718,12 @@ class DataServiceImplTest {
@Test
fun testAlbumMediaPagingSourceInvalidation() = runTest {
val userStatusFlow: MutableStateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val dataService: DataService =
DataServiceImpl(
@@ -666,7 +734,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
// Check initial available provider emissions
@@ -744,6 +813,12 @@ class DataServiceImplTest {
@Test
fun testOnUpdateMediaNotification() = runTest {
val userStatusFlow: StateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val dataService: DataService =
DataServiceImpl(
@@ -754,7 +829,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
advanceTimeBy(100)
@@ -787,6 +863,12 @@ class DataServiceImplTest {
@Test
fun testOnUpdateAlbumMediaNotification() = runTest {
val userStatusFlow: StateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val dataService: DataService =
DataServiceImpl(
@@ -797,7 +879,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
advanceTimeBy(100)
@@ -853,6 +936,12 @@ class DataServiceImplTest {
@Test
fun testDisruptiveDataUpdate() = runTest {
val userStatusFlow: StateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
testContentProvider.providers =
mutableListOf(
@@ -873,7 +962,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
val availableProviderEmissions = mutableListOf<List<Provider>>()
@@ -942,6 +1032,12 @@ class DataServiceImplTest {
@Test
fun testCollectionInfoUpdate() = runTest {
val userStatusFlow: StateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val dataService: DataService =
DataServiceImpl(
@@ -952,7 +1048,8 @@ class DataServiceImplTest {
dispatcher = StandardTestDispatcher(this.testScheduler),
config = provideTestConfigurationFlow(this.backgroundScope),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
val availableProviderEmissions = mutableListOf<List<Provider>>()
@@ -986,6 +1083,12 @@ class DataServiceImplTest {
@Test
fun testGetAllAllowedProviders() = runTest {
val userStatusFlow: StateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val cloudProvider1 =
Provider(
"cloud_primary",
@@ -1029,11 +1132,13 @@ class DataServiceImplTest {
cloudProvider2.authority
),
CLOUD_ENFORCE_PROVIDER_ALLOWLIST = true
- )
+ ),
+ sessionId = sessionId
)
),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
val actualAllAllowedProviders = dataService.getAllAllowedProviders()
@@ -1045,6 +1150,12 @@ class DataServiceImplTest {
@Test
fun testGetAllAllowedProvidersWhenAllowlistIsEnforced() = runTest {
val userStatusFlow: StateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val cloudProvider1 =
Provider(
"cloud_primary",
@@ -1084,11 +1195,13 @@ class DataServiceImplTest {
PhotopickerFlags(
CLOUD_ALLOWED_PROVIDERS = arrayOf(cloudProvider1.authority),
CLOUD_ENFORCE_PROVIDER_ALLOWLIST = true
- )
+ ),
+ sessionId = sessionId
)
),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
val actualAllAllowedProviders = dataService.getAllAllowedProviders()
@@ -1099,6 +1212,12 @@ class DataServiceImplTest {
@Test
fun testGetAllAllowedProvidersWhenDeviceHasLimitedProviders() = runTest {
val userStatusFlow: StateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val cloudProvider1 =
Provider(
"cloud_primary",
@@ -1141,11 +1260,13 @@ class DataServiceImplTest {
cloudProvider2.authority
),
CLOUD_ENFORCE_PROVIDER_ALLOWLIST = true
- )
+ ),
+ sessionId = sessionId
)
),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
val actualAllAllowedProviders = dataService.getAllAllowedProviders()
@@ -1156,6 +1277,12 @@ class DataServiceImplTest {
@Test
fun testGetAllAllowedProvidersWhenAllowlistIsNotEnforced() = runTest {
val userStatusFlow: StateFlow<UserStatus> = MutableStateFlow(userStatus)
+ events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope),
+ testFeatureManager
+ )
val cloudProvider1 =
Provider(
"cloud_primary",
@@ -1195,11 +1322,13 @@ class DataServiceImplTest {
PhotopickerFlags(
CLOUD_ALLOWED_PROVIDERS = arrayOf(),
CLOUD_ENFORCE_PROVIDER_ALLOWLIST = false
- )
+ ),
+ sessionId = sessionId
)
),
featureManager = testFeatureManager,
- appContext = mockContext
+ appContext = mockContext,
+ events = events
)
val actualAllAllowedProviders = dataService.getAllAllowedProviders()
diff --git a/photopicker/tests/src/com/android/photopicker/data/TestDataServiceImpl.kt b/photopicker/tests/src/com/android/photopicker/data/TestDataServiceImpl.kt
index 836171a94..dbb7ca51b 100644
--- a/photopicker/tests/src/com/android/photopicker/data/TestDataServiceImpl.kt
+++ b/photopicker/tests/src/com/android/photopicker/data/TestDataServiceImpl.kt
@@ -59,10 +59,18 @@ class TestDataServiceImpl() : DataService {
val collectionInfo: HashMap<Provider, CollectionInfo> = HashMap()
+ private var _preGrantsCount = MutableStateFlow(/* default value */ 0)
+
fun setAvailableProviders(newProviders: List<Provider>) {
_availableProviders.update { newProviders }
}
+ override val preGrantedMediaCount: StateFlow<Int> = _preGrantsCount
+
+ fun setInitPreGrantsCount(count: Int) {
+ _preGrantsCount.update { count }
+ }
+
override fun albumMediaPagingSource(album: Album): PagingSource<MediaPageKey, Media> {
return albumMediaList?.let { FakeInMemoryMediaPagingSource(it) }
?: FakeInMemoryMediaPagingSource(albumMediaSetSize)
@@ -97,10 +105,18 @@ class TestDataServiceImpl() : DataService {
override val disruptiveDataUpdateChannel = Channel<Unit>(CONFLATED)
+ suspend fun sendDisruptiveDataUpdateNotification() {
+ disruptiveDataUpdateChannel.send(Unit)
+ }
+
override suspend fun getCollectionInfo(provider: Provider): CollectionInfo =
collectionInfo.getOrElse(provider, { CollectionInfo(provider.authority) })
override suspend fun ensureProviders() {}
override fun getAllAllowedProviders(): List<Provider> = allowedProviders
+
+ override fun refreshPreGrantedItemsCount() {
+ // no_op
+ }
}
diff --git a/photopicker/tests/src/com/android/photopicker/data/TestMediaProvider.kt b/photopicker/tests/src/com/android/photopicker/data/TestMediaProvider.kt
index b3628e557..40a095988 100644
--- a/photopicker/tests/src/com/android/photopicker/data/TestMediaProvider.kt
+++ b/photopicker/tests/src/com/android/photopicker/data/TestMediaProvider.kt
@@ -111,6 +111,7 @@ class TestMediaProvider(
var albumMedia: Map<String, List<Media>> = DEFAULT_ALBUM_MEDIA
) : MockContentProvider() {
var lastRefreshMediaRequest: Bundle? = null
+ var TEST_GRANTS_COUNT = 2
override fun query(
uri: Uri,
@@ -123,6 +124,7 @@ class TestMediaProvider(
"collection_info" -> getCollectionInfo()
"media" -> getMedia()
"album" -> getAlbums()
+ "media_grants_count" -> fetchMediaGrantsCount()
else -> {
val pathSegments: MutableList<String> = uri.getPathSegments()
if (pathSegments.size == 4 && pathSegments[2].equals("album")) {
@@ -209,6 +211,7 @@ class TestMediaProvider(
MediaProviderClient.MediaResponse.MIME_TYPE.key,
MediaProviderClient.MediaResponse.STANDARD_MIME_TYPE_EXT.key,
MediaProviderClient.MediaResponse.DURATION.key,
+ MediaProviderClient.MediaResponse.IS_PRE_GRANTED.key,
)
)
mediaItems.forEach { mediaItem ->
@@ -224,7 +227,8 @@ class TestMediaProvider(
mediaItem.sizeInBytes.toString(),
mediaItem.mimeType,
mediaItem.standardMimeTypeExtension.toString(),
- if (mediaItem is Media.Video) mediaItem.duration else "0"
+ if (mediaItem is Media.Video) mediaItem.duration else "0",
+ if (mediaItem.isPreGranted) 1 else 0,
)
)
}
@@ -260,6 +264,12 @@ class TestMediaProvider(
return cursor
}
+ private fun fetchMediaGrantsCount(): Cursor {
+ val cursor = MatrixCursor(arrayOf("grants_count"))
+ cursor.addRow(arrayOf(TEST_GRANTS_COUNT))
+ return cursor
+ }
+
private fun getAlbumMedia(albumId: String): Cursor? {
return getMedia(albumMedia.getOrDefault(albumId, emptyList()))
}
diff --git a/photopicker/tests/src/com/android/photopicker/data/paging/AlbumMediaPagingSourceTest.kt b/photopicker/tests/src/com/android/photopicker/data/paging/AlbumMediaPagingSourceTest.kt
index 1cb67d586..553f23234 100644
--- a/photopicker/tests/src/com/android/photopicker/data/paging/AlbumMediaPagingSourceTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/data/paging/AlbumMediaPagingSourceTest.kt
@@ -17,11 +17,17 @@
package com.android.photopicker.features.data.paging
import android.content.ContentResolver
+import android.content.Intent
import android.provider.MediaStore
import androidx.paging.PagingSource.LoadParams
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.configuration.provideTestConfigurationFlow
+import com.android.photopicker.core.configuration.testSessionId
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.features.FeatureRegistration
import com.android.photopicker.data.MediaProviderClient
import com.android.photopicker.data.TestMediaProvider
import com.android.photopicker.data.model.MediaPageKey
@@ -49,6 +55,12 @@ class AlbumMediaPagingSourceTest {
private val contentResolver: ContentResolver = ContentResolver.wrap(testContentProvider)
private val availableProviders: List<Provider> =
listOf(Provider("auth", MediaSource.LOCAL, 0, ""))
+ private val testPhotopickerConfiguration: PhotopickerConfiguration =
+ PhotopickerConfiguration(
+ action = MediaStore.ACTION_PICK_IMAGES,
+ intent = Intent(MediaStore.ACTION_PICK_IMAGES),
+ sessionId = testSessionId
+ )
@Mock private lateinit var mockMediaProviderClient: MediaProviderClient
@@ -61,7 +73,19 @@ class AlbumMediaPagingSourceTest {
fun testLoad() = runTest {
val albumId = "test-album-id"
val albumAuthority = availableProviders[0].authority
- val config = PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES)
+ val featureManager =
+ FeatureManager(
+ provideTestConfigurationFlow(this.backgroundScope, testPhotopickerConfiguration),
+ this.backgroundScope,
+ emptySet<FeatureRegistration>(),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope, testPhotopickerConfiguration),
+ featureManager
+ )
+
val albumMediaPagingSource =
AlbumMediaPagingSource(
albumId = albumId,
@@ -70,7 +94,8 @@ class AlbumMediaPagingSourceTest {
availableProviders = availableProviders,
mediaProviderClient = mockMediaProviderClient,
dispatcher = StandardTestDispatcher(this.testScheduler),
- config = config
+ testPhotopickerConfiguration,
+ events
)
val pageKey = MediaPageKey()
@@ -93,7 +118,7 @@ class AlbumMediaPagingSourceTest {
pageSize,
contentResolver,
availableProviders,
- config
+ testPhotopickerConfiguration
)
}
}
diff --git a/photopicker/tests/src/com/android/photopicker/data/paging/AlbumPagingSourceTest.kt b/photopicker/tests/src/com/android/photopicker/data/paging/AlbumPagingSourceTest.kt
index 19b95836d..361725c00 100644
--- a/photopicker/tests/src/com/android/photopicker/data/paging/AlbumPagingSourceTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/data/paging/AlbumPagingSourceTest.kt
@@ -17,11 +17,17 @@
package com.android.photopicker.features.data.paging
import android.content.ContentResolver
+import android.content.Intent
import android.provider.MediaStore
import androidx.paging.PagingSource.LoadParams
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.configuration.provideTestConfigurationFlow
+import com.android.photopicker.core.configuration.testSessionId
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.features.FeatureRegistration
import com.android.photopicker.data.MediaProviderClient
import com.android.photopicker.data.TestMediaProvider
import com.android.photopicker.data.model.MediaPageKey
@@ -47,6 +53,12 @@ class AlbumPagingSourceTest {
private val testContentProvider: TestMediaProvider = TestMediaProvider()
private val contentResolver: ContentResolver = ContentResolver.wrap(testContentProvider)
private val availableProviders: List<Provider> = emptyList()
+ private val testPhotopickerConfiguration: PhotopickerConfiguration =
+ PhotopickerConfiguration(
+ action = MediaStore.ACTION_PICK_IMAGES,
+ intent = Intent(MediaStore.ACTION_PICK_IMAGES),
+ sessionId = testSessionId
+ )
@Mock private lateinit var mockMediaProviderClient: MediaProviderClient
@@ -57,14 +69,27 @@ class AlbumPagingSourceTest {
@Test
fun testLoad() = runTest {
- val config = PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES)
+ val featureManager =
+ FeatureManager(
+ provideTestConfigurationFlow(this.backgroundScope, testPhotopickerConfiguration),
+ this.backgroundScope,
+ emptySet<FeatureRegistration>(),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope, testPhotopickerConfiguration),
+ featureManager
+ )
+
val albumPagingSource =
AlbumPagingSource(
contentResolver = contentResolver,
availableProviders = availableProviders,
mediaProviderClient = mockMediaProviderClient,
dispatcher = StandardTestDispatcher(this.testScheduler),
- config = config
+ testPhotopickerConfiguration,
+ events
)
val pageKey = MediaPageKey()
@@ -80,6 +105,12 @@ class AlbumPagingSourceTest {
advanceTimeBy(100)
verify(mockMediaProviderClient, times(1))
- .fetchAlbums(pageKey, pageSize, contentResolver, emptyList(), config)
+ .fetchAlbums(
+ pageKey,
+ pageSize,
+ contentResolver,
+ emptyList(),
+ testPhotopickerConfiguration
+ )
}
}
diff --git a/photopicker/tests/src/com/android/photopicker/data/paging/MediaPagingSourceTest.kt b/photopicker/tests/src/com/android/photopicker/data/paging/MediaPagingSourceTest.kt
index 50c63f4bb..eb277d75c 100644
--- a/photopicker/tests/src/com/android/photopicker/data/paging/MediaPagingSourceTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/data/paging/MediaPagingSourceTest.kt
@@ -17,11 +17,17 @@
package com.android.photopicker.features.data.paging
import android.content.ContentResolver
+import android.content.Intent
import android.provider.MediaStore
import androidx.paging.PagingSource.LoadParams
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.configuration.provideTestConfigurationFlow
+import com.android.photopicker.core.configuration.testSessionId
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.features.FeatureRegistration
import com.android.photopicker.data.MediaProviderClient
import com.android.photopicker.data.TestMediaProvider
import com.android.photopicker.data.model.MediaPageKey
@@ -47,6 +53,12 @@ class MediaPagingSourceTest {
private val testContentProvider: TestMediaProvider = TestMediaProvider()
private val contentResolver: ContentResolver = ContentResolver.wrap(testContentProvider)
private val availableProviders: List<Provider> = emptyList()
+ private val testPhotopickerConfiguration: PhotopickerConfiguration =
+ PhotopickerConfiguration(
+ action = MediaStore.ACTION_PICK_IMAGES,
+ intent = Intent(MediaStore.ACTION_PICK_IMAGES),
+ sessionId = testSessionId
+ )
@Mock private lateinit var mockMediaProviderClient: MediaProviderClient
@@ -57,14 +69,27 @@ class MediaPagingSourceTest {
@Test
fun testLoad() = runTest {
- val config = PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES)
+ val featureManager =
+ FeatureManager(
+ provideTestConfigurationFlow(this.backgroundScope, testPhotopickerConfiguration),
+ this.backgroundScope,
+ emptySet<FeatureRegistration>(),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(this.backgroundScope, testPhotopickerConfiguration),
+ featureManager
+ )
+
val mediaPagingSource =
MediaPagingSource(
contentResolver = contentResolver,
availableProviders = availableProviders,
mediaProviderClient = mockMediaProviderClient,
dispatcher = StandardTestDispatcher(this.testScheduler),
- config = config
+ testPhotopickerConfiguration,
+ events
)
val pageKey: MediaPageKey = MediaPageKey()
@@ -80,6 +105,12 @@ class MediaPagingSourceTest {
advanceTimeBy(100)
verify(mockMediaProviderClient, times(1))
- .fetchMedia(pageKey, pageSize, contentResolver, emptyList(), config)
+ .fetchMedia(
+ pageKey,
+ pageSize,
+ contentResolver,
+ emptyList(),
+ testPhotopickerConfiguration
+ )
}
}
diff --git a/photopicker/tests/src/com/android/photopicker/data/paging/MediaProviderClientTest.kt b/photopicker/tests/src/com/android/photopicker/data/paging/MediaProviderClientTest.kt
index ae29a22a3..90bdccda1 100644
--- a/photopicker/tests/src/com/android/photopicker/data/paging/MediaProviderClientTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/data/paging/MediaProviderClientTest.kt
@@ -17,11 +17,14 @@
package com.android.photopicker.features.data.paging
import android.content.ContentResolver
+import android.content.Intent
import android.provider.MediaStore
import androidx.paging.PagingSource.LoadResult
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.configuration.testUserSelectImagesForAppConfiguration
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.data.MediaProviderClient
import com.android.photopicker.data.TestMediaProvider
import com.android.photopicker.data.model.Group
@@ -39,6 +42,7 @@ import org.junit.runner.RunWith
class MediaProviderClientTest {
private val testContentProvider: TestMediaProvider = TestMediaProvider()
private val testContentResolver: ContentResolver = ContentResolver.wrap(testContentProvider)
+ private val sessionId = generatePickerSessionId()
@Test
fun testFetchAvailableProviders() = runTest {
@@ -63,7 +67,11 @@ class MediaProviderClientTest {
pageSize = 5,
contentResolver = testContentResolver,
availableProviders = listOf(Provider("provider", MediaSource.LOCAL, 0, "")),
- config = PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES)
+ config =
+ PhotopickerConfiguration(
+ action = MediaStore.ACTION_PICK_IMAGES,
+ sessionId = sessionId
+ )
)
assertThat(mediaLoadResult is LoadResult.Page).isTrue()
@@ -103,7 +111,11 @@ class MediaProviderClientTest {
)
val mimeTypes = arrayListOf("image/gif", "video/*")
val config =
- PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES, mimeTypes = mimeTypes)
+ PhotopickerConfiguration(
+ action = MediaStore.ACTION_PICK_IMAGES,
+ mimeTypes = mimeTypes,
+ sessionId = sessionId
+ )
mediaProviderClient.refreshMedia(
providers = providers,
@@ -121,6 +133,60 @@ class MediaProviderClientTest {
}
@Test
+ fun testRefreshMediaForUserSelectAction() = runTest {
+ testContentProvider.lastRefreshMediaRequest = null
+ val mediaProviderClient = MediaProviderClient()
+ val providers: List<Provider> =
+ mutableListOf(
+ Provider(
+ authority = "local_authority",
+ mediaSource = MediaSource.LOCAL,
+ uid = 0,
+ displayName = "abc"
+ ),
+ Provider(
+ authority = "hypothetical_local_authority",
+ mediaSource = MediaSource.LOCAL,
+ uid = 2,
+ displayName = "xyz"
+ ),
+ )
+ mediaProviderClient.refreshMedia(
+ providers = providers,
+ resolver = testContentResolver,
+ config = testUserSelectImagesForAppConfiguration
+ )
+
+ assertThat(testContentProvider.lastRefreshMediaRequest).isNotNull()
+ // TODO(b/340246010): Currently, we trigger sync for all available providers. This is
+ // because UI is responsible for triggering syncs which is sometimes required to enable
+ // providers. This should be changed to triggering syncs for specific providers once the
+ // backend takes responsibility for the sync triggers.
+ assertThat(testContentProvider.lastRefreshMediaRequest?.getBoolean("is_local_only", true))
+ .isFalse()
+ assertThat(testContentProvider.lastRefreshMediaRequest?.getStringArrayList("mime_types"))
+ .isEqualTo(testUserSelectImagesForAppConfiguration.mimeTypes)
+ assertThat(testContentProvider.lastRefreshMediaRequest?.getString("intent_action"))
+ .isEqualTo(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP)
+ assertThat(testContentProvider.lastRefreshMediaRequest?.getInt(Intent.EXTRA_UID))
+ .isEqualTo(testUserSelectImagesForAppConfiguration.callingPackageUid)
+ }
+
+ @Test
+ fun testFetchMediaGrantsCount() = runTest {
+ testContentProvider.lastRefreshMediaRequest = null
+ val mediaProviderClient = MediaProviderClient()
+
+ val countOfGrants =
+ mediaProviderClient.fetchMediaGrantsCount(
+ contentResolver = testContentResolver,
+ callingPackageUid = testUserSelectImagesForAppConfiguration.callingPackageUid ?: -1,
+ )
+
+ assertThat(countOfGrants).isEqualTo(testContentProvider.TEST_GRANTS_COUNT)
+ }
+
+ @Test
fun testRefreshLocalOnlyMedia() = runTest {
testContentProvider.lastRefreshMediaRequest = null
val mediaProviderClient = MediaProviderClient()
@@ -141,7 +207,11 @@ class MediaProviderClientTest {
)
val mimeTypes = arrayListOf("image/gif", "video/*")
val config =
- PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES, mimeTypes = mimeTypes)
+ PhotopickerConfiguration(
+ action = MediaStore.ACTION_PICK_IMAGES,
+ mimeTypes = mimeTypes,
+ sessionId = sessionId
+ )
mediaProviderClient.refreshMedia(
providers = providers,
@@ -186,7 +256,11 @@ class MediaProviderClientTest {
)
val mimeTypes = arrayListOf("image/gif", "video/*")
val config =
- PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES, mimeTypes = mimeTypes)
+ PhotopickerConfiguration(
+ action = MediaStore.ACTION_PICK_IMAGES,
+ mimeTypes = mimeTypes,
+ sessionId = sessionId
+ )
mediaProviderClient.refreshAlbumMedia(
albumId = albumId,
@@ -219,7 +293,11 @@ class MediaProviderClientTest {
pageSize = 5,
contentResolver = testContentResolver,
availableProviders = listOf(Provider("provider", MediaSource.LOCAL, 0, "")),
- config = PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES),
+ config =
+ PhotopickerConfiguration(
+ action = MediaStore.ACTION_PICK_IMAGES,
+ sessionId = sessionId
+ ),
)
assertThat(albumLoadResult is LoadResult.Page).isTrue()
@@ -246,7 +324,11 @@ class MediaProviderClientTest {
pageSize = 5,
contentResolver = testContentResolver,
availableProviders = listOf(Provider(albumAuthority, MediaSource.LOCAL, 0, "")),
- config = PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES),
+ config =
+ PhotopickerConfiguration(
+ action = MediaStore.ACTION_PICK_IMAGES,
+ sessionId = sessionId
+ ),
)
assertThat(mediaLoadResult is LoadResult.Page).isTrue()
diff --git a/photopicker/tests/src/com/android/photopicker/features/albumgrid/AlbumGridFeatureTest.kt b/photopicker/tests/src/com/android/photopicker/features/albumgrid/AlbumGridFeatureTest.kt
index 6a2999d8c..d75873ef0 100644
--- a/photopicker/tests/src/com/android/photopicker/features/albumgrid/AlbumGridFeatureTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/albumgrid/AlbumGridFeatureTest.kt
@@ -22,6 +22,7 @@ import android.content.Context
import android.content.pm.PackageManager
import android.net.Uri
import android.os.UserManager
+import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA
import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_FAVORITES
import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS
import android.test.mock.MockContentResolver
@@ -44,7 +45,6 @@ import com.android.photopicker.core.ConcurrencyModule
import com.android.photopicker.core.EmbeddedServiceModule
import com.android.photopicker.core.Main
import com.android.photopicker.core.ViewModelModule
-import com.android.photopicker.core.banners.BannerManager
import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.configuration.testActionPickImagesConfiguration
import com.android.photopicker.core.configuration.testGetContentConfiguration
@@ -52,6 +52,7 @@ import com.android.photopicker.core.configuration.testPhotopickerConfiguration
import com.android.photopicker.core.configuration.testUserSelectImagesForAppConfiguration
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.navigation.PhotopickerDestinations
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.data.DataService
@@ -67,7 +68,6 @@ import com.android.photopicker.test.utils.MockContentProviderWrapper
import com.android.photopicker.tests.HiltTestActivity
import com.android.photopicker.tests.utils.mockito.whenever
import com.google.common.truth.Truth.assertWithMessage
-import dagger.Lazy
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.testing.BindValue
@@ -105,6 +105,7 @@ class AlbumGridFeatureTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
@@ -139,7 +140,6 @@ class AlbumGridFeatureTest : PhotopickerFeatureBaseTest() {
@Inject lateinit var selection: Selection<Media>
@Inject lateinit var featureManager: FeatureManager
@Inject lateinit var events: Events
- @Inject lateinit var bannerManager: Lazy<BannerManager>
@Inject override lateinit var configurationManager: ConfigurationManager
@Inject lateinit var dataService: DataService
@@ -193,7 +193,6 @@ class AlbumGridFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -228,7 +227,6 @@ class AlbumGridFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -257,6 +255,7 @@ class AlbumGridFeatureTest : PhotopickerFeatureBaseTest() {
// Allow the PreviewViewModel to collect flows
advanceTimeBy(100)
+ composeTestRule.waitForIdle()
assertWithMessage("Expected route to be albummediagrid")
.that(navController.currentBackStackEntry?.destination?.route)
@@ -271,7 +270,6 @@ class AlbumGridFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -317,7 +315,6 @@ class AlbumGridFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -397,7 +394,6 @@ class AlbumGridFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -479,7 +475,6 @@ class AlbumGridFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -518,4 +513,80 @@ class AlbumGridFeatureTest : PhotopickerFeatureBaseTest() {
.assertIsDisplayed()
}
}
+
+ @Test
+ fun testEmptyStateContentForCamera() {
+
+ val testDataService = dataService as? TestDataServiceImpl
+ checkNotNull(testDataService) { "Expected a TestDataServiceImpl" }
+
+ // Force the data service to return no data for all test sources during this test.
+ testDataService.albumMediaSetSize = 0
+ testDataService.albumsList =
+ listOf(
+ Group.Album(
+ id = ALBUM_ID_CAMERA,
+ pickerId = 1234L,
+ authority = "a",
+ displayName = "Camera",
+ coverUri =
+ Uri.EMPTY.buildUpon()
+ .apply {
+ scheme("content")
+ authority("a")
+ path("1234")
+ }
+ .build(),
+ dateTakenMillisLong = 12345678L,
+ coverMediaSource = MediaSource.LOCAL,
+ )
+ )
+
+ val resources = getTestableContext().getResources()
+
+ testScope.runTest {
+ composeTestRule.setContent {
+ // Set an explicit size to prevent errors in glide being unable to measure
+ callPhotopickerMain(
+ featureManager = featureManager,
+ selection = selection,
+ events = events,
+ )
+ }
+
+ advanceTimeBy(100)
+
+ // Navigate on the UI thread (similar to a click handler)
+ composeTestRule.runOnUiThread({ navController.navigateToAlbumGrid() })
+
+ assertWithMessage("Expected route to be albumgrid")
+ .that(navController.currentBackStackEntry?.destination?.route)
+ .isEqualTo(PhotopickerDestinations.ALBUM_GRID.route)
+
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ advanceTimeBy(100)
+
+ val testAlbumDisplayName = "Camera"
+ composeTestRule.onNode(hasText(testAlbumDisplayName)).performClick()
+
+ composeTestRule.waitForIdle()
+
+ // Allow the PreviewViewModel to collect flows
+ advanceTimeBy(100)
+
+ // Wait for the PhotoGridViewModel to load data and for the UI to update.
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ composeTestRule
+ .onNode(hasText(resources.getString(R.string.photopicker_photos_empty_state_title)))
+ .assertIsDisplayed()
+
+ composeTestRule
+ .onNode(hasText(resources.getString(R.string.photopicker_camera_empty_state_body)))
+ .assertIsDisplayed()
+ }
+ }
}
diff --git a/photopicker/tests/src/com/android/photopicker/features/albumgrid/AlbumGridViewModelTest.kt b/photopicker/tests/src/com/android/photopicker/features/albumgrid/AlbumGridViewModelTest.kt
index a2ca38334..c6130fd22 100644
--- a/photopicker/tests/src/com/android/photopicker/features/albumgrid/AlbumGridViewModelTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/albumgrid/AlbumGridViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.photopicker.features.albumgrid
import android.net.Uri
+import android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.photopicker.core.configuration.PhotopickerConfiguration
@@ -24,10 +25,13 @@ import com.android.photopicker.core.configuration.provideTestConfigurationFlow
import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.events.RegisteredEventClass
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureToken.ALBUM_GRID
import com.android.photopicker.core.selection.SelectionImpl
import com.android.photopicker.data.TestDataServiceImpl
+import com.android.photopicker.data.model.Group
import com.android.photopicker.data.model.Media
import com.android.photopicker.data.model.MediaSource
import com.google.common.truth.Truth.assertWithMessage
@@ -74,6 +78,27 @@ class AlbumGridViewModelTest {
standardMimeTypeExtension = 1,
)
+ val album =
+ Group.Album(
+ id = ALBUM_ID_VIDEOS,
+ pickerId = 1234L,
+ authority = "a",
+ displayName = "Videos",
+ coverUri =
+ Uri.EMPTY.buildUpon()
+ .apply {
+ scheme("content")
+ authority("a")
+ path("1234")
+ }
+ .build(),
+ dateTakenMillisLong = 12345678L,
+ coverMediaSource = MediaSource.LOCAL,
+ )
+
+ val updatedMediaItem =
+ mediaItem.copy(mediaItemAlbum = album, selectionSource = Telemetry.MediaLocation.ALBUM)
+
@Test
fun testAlbumGridItemClickedUpdatesSelection() {
@@ -112,23 +137,24 @@ class AlbumGridViewModelTest {
.isEqualTo(0)
// Toggle the item into the selection
- viewModel.handleAlbumMediaGridItemSelection(mediaItem, "")
+ viewModel.handleAlbumMediaGridItemSelection(mediaItem, "", album)
// Wait for selection update.
advanceTimeBy(100)
+ // The selected media item gets updated with the Selectable interface values
assertWithMessage("Selection did not contain expected item")
.that(selection.snapshot())
- .contains(mediaItem)
+ .contains(updatedMediaItem)
// Toggle the item out of the selection
- viewModel.handleAlbumMediaGridItemSelection(mediaItem, "")
+ viewModel.handleAlbumMediaGridItemSelection(mediaItem, "", album)
advanceTimeBy(100)
assertWithMessage("Selection contains unexpected item")
.that(selection.snapshot())
- .doesNotContain(mediaItem)
+ .doesNotContain(updatedMediaItem)
}
}
@@ -146,7 +172,8 @@ class AlbumGridViewModelTest {
PhotopickerConfiguration(
action = "TEST_ACTION",
intent = null,
- selectionLimit = 0
+ selectionLimit = 0,
+ sessionId = generatePickerSessionId()
)
)
)
@@ -183,7 +210,7 @@ class AlbumGridViewModelTest {
// Toggle the item into the selection
val errorMessage = "test"
- viewModel.handleAlbumMediaGridItemSelection(mediaItem, errorMessage)
+ viewModel.handleAlbumMediaGridItemSelection(mediaItem, errorMessage, album)
// Wait for selection update.
advanceTimeBy(100)
diff --git a/photopicker/tests/src/com/android/photopicker/features/browse/BrowseFeatureTest.kt b/photopicker/tests/src/com/android/photopicker/features/browse/BrowseFeatureTest.kt
index b1024e9ed..6ae0eef80 100644
--- a/photopicker/tests/src/com/android/photopicker/features/browse/BrowseFeatureTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/browse/BrowseFeatureTest.kt
@@ -36,7 +36,6 @@ import com.android.photopicker.core.Background
import com.android.photopicker.core.ConcurrencyModule
import com.android.photopicker.core.EmbeddedServiceModule
import com.android.photopicker.core.Main
-import com.android.photopicker.core.banners.BannerManager
import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.configuration.testActionPickImagesConfiguration
import com.android.photopicker.core.configuration.testEmbeddedPhotopickerConfiguration
@@ -46,6 +45,7 @@ import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureToken
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.data.model.Media
import com.android.photopicker.features.PhotopickerFeatureBaseTest
@@ -91,6 +91,7 @@ class BrowseFeatureTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
@@ -110,7 +111,6 @@ class BrowseFeatureTest : PhotopickerFeatureBaseTest() {
@Inject lateinit var selection: Lazy<Selection<Media>>
@Inject lateinit var featureManager: Lazy<FeatureManager>
- @Inject lateinit var bannerManager: Lazy<BannerManager>
@Inject lateinit var events: Lazy<Events>
@Inject override lateinit var configurationManager: ConfigurationManager
@@ -156,7 +156,6 @@ class BrowseFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager.get(),
selection = selection.get(),
events = events.get(),
- bannerManager = bannerManager.get(),
)
}
@@ -196,7 +195,6 @@ class BrowseFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager.get(),
selection = selection.get(),
events = events.get(),
- bannerManager = bannerManager.get(),
)
}
diff --git a/photopicker/tests/src/com/android/photopicker/features/cloudmedia/CloudMediaFeatureTest.kt b/photopicker/tests/src/com/android/photopicker/features/cloudmedia/CloudMediaFeatureTest.kt
index 517e4c8a1..f554290e3 100644
--- a/photopicker/tests/src/com/android/photopicker/features/cloudmedia/CloudMediaFeatureTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/cloudmedia/CloudMediaFeatureTest.kt
@@ -52,6 +52,7 @@ import com.android.photopicker.core.configuration.testUserSelectImagesForAppConf
import com.android.photopicker.core.database.DatabaseManager
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.data.DataService
import com.android.photopicker.data.TestDataServiceImpl
@@ -103,6 +104,7 @@ class CloudMediaFeatureTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
@@ -186,7 +188,6 @@ class CloudMediaFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager.get(),
selection = selection.get(),
events = events.get(),
- bannerManager = bannerManager.get(),
)
}
@@ -236,7 +237,6 @@ class CloudMediaFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager.get(),
selection = selection.get(),
events = events.get(),
- bannerManager = bannerManager.get(),
)
}
@@ -328,7 +328,6 @@ class CloudMediaFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager.get(),
selection = selection.get(),
events = events.get(),
- bannerManager = bannerManager.get(),
)
}
composeTestRule.waitForIdle()
@@ -397,7 +396,6 @@ class CloudMediaFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager.get(),
selection = selection.get(),
events = events.get(),
- bannerManager = bannerManager.get(),
)
}
composeTestRule.waitForIdle()
@@ -453,7 +451,6 @@ class CloudMediaFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager.get(),
selection = selection.get(),
events = events.get(),
- bannerManager = bannerManager.get(),
)
}
composeTestRule.waitForIdle()
@@ -522,7 +519,6 @@ class CloudMediaFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager.get(),
selection = selection.get(),
events = events.get(),
- bannerManager = bannerManager.get(),
)
}
composeTestRule.waitForIdle()
@@ -567,7 +563,6 @@ class CloudMediaFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager.get(),
selection = selection.get(),
events = events.get(),
- bannerManager = bannerManager.get(),
)
}
composeTestRule.waitForIdle()
@@ -625,7 +620,6 @@ class CloudMediaFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager.get(),
selection = selection.get(),
events = events.get(),
- bannerManager = bannerManager.get(),
)
}
composeTestRule.waitForIdle()
diff --git a/photopicker/tests/src/com/android/photopicker/features/cloudmedia/MediaPreloaderTest.kt b/photopicker/tests/src/com/android/photopicker/features/cloudmedia/MediaPreloaderTest.kt
index ff15fc144..ac0318492 100644
--- a/photopicker/tests/src/com/android/photopicker/features/cloudmedia/MediaPreloaderTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/cloudmedia/MediaPreloaderTest.kt
@@ -52,9 +52,11 @@ import com.android.photopicker.core.configuration.testGetContentConfiguration
import com.android.photopicker.core.configuration.testPhotopickerConfiguration
import com.android.photopicker.core.configuration.testUserSelectImagesForAppConfiguration
import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.LocalEvents
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.Location
import com.android.photopicker.core.features.LocationParams
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.data.model.Media
import com.android.photopicker.data.model.MediaSource
@@ -107,6 +109,7 @@ class MediaPreloaderTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
@@ -238,7 +241,8 @@ class MediaPreloaderTest : PhotopickerFeatureBaseTest() {
composeTestRule.setContent {
CompositionLocalProvider(
- LocalPhotopickerConfiguration provides testPhotopickerConfiguration
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ LocalEvents provides events.get()
) {
featureManager
.get()
@@ -280,7 +284,8 @@ class MediaPreloaderTest : PhotopickerFeatureBaseTest() {
var preloadDeferred = CompletableDeferred<Boolean>()
composeTestRule.setContent {
CompositionLocalProvider(
- LocalPhotopickerConfiguration provides testPhotopickerConfiguration
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ LocalEvents provides events.get()
) {
featureManager
.get()
@@ -326,7 +331,8 @@ class MediaPreloaderTest : PhotopickerFeatureBaseTest() {
var preloadDeferred = CompletableDeferred<Boolean>()
composeTestRule.setContent {
CompositionLocalProvider(
- LocalPhotopickerConfiguration provides testPhotopickerConfiguration
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ LocalEvents provides events.get()
) {
featureManager
.get()
@@ -374,7 +380,8 @@ class MediaPreloaderTest : PhotopickerFeatureBaseTest() {
var preloadDeferred = CompletableDeferred<Boolean>()
composeTestRule.setContent {
CompositionLocalProvider(
- LocalPhotopickerConfiguration provides testPhotopickerConfiguration
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ LocalEvents provides events.get()
) {
featureManager
.get()
@@ -421,7 +428,8 @@ class MediaPreloaderTest : PhotopickerFeatureBaseTest() {
composeTestRule.setContent {
CompositionLocalProvider(
- LocalPhotopickerConfiguration provides testPhotopickerConfiguration
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
+ LocalEvents provides events.get()
) {
featureManager
.get()
diff --git a/photopicker/tests/src/com/android/photopicker/features/navigationbar/NavigationBarFeatureTest.kt b/photopicker/tests/src/com/android/photopicker/features/navigationbar/NavigationBarFeatureTest.kt
index 74b0411b0..d869b4cef 100644
--- a/photopicker/tests/src/com/android/photopicker/features/navigationbar/NavigationBarFeatureTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/navigationbar/NavigationBarFeatureTest.kt
@@ -37,7 +37,6 @@ import com.android.photopicker.core.ConcurrencyModule
import com.android.photopicker.core.EmbeddedServiceModule
import com.android.photopicker.core.Main
import com.android.photopicker.core.ViewModelModule
-import com.android.photopicker.core.banners.BannerManager
import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
import com.android.photopicker.core.configuration.testActionPickImagesConfiguration
@@ -46,6 +45,7 @@ import com.android.photopicker.core.configuration.testPhotopickerConfiguration
import com.android.photopicker.core.configuration.testUserSelectImagesForAppConfiguration
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.data.model.Media
import com.android.photopicker.features.PhotopickerFeatureBaseTest
@@ -54,7 +54,6 @@ import com.android.photopicker.test.utils.MockContentProviderWrapper
import com.android.photopicker.tests.HiltTestActivity
import com.android.photopicker.tests.utils.mockito.whenever
import com.google.common.truth.Truth.assertWithMessage
-import dagger.Lazy
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.testing.BindValue
@@ -90,6 +89,7 @@ class NavigationBarFeatureTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
@@ -124,7 +124,6 @@ class NavigationBarFeatureTest : PhotopickerFeatureBaseTest() {
@Inject lateinit var selection: Selection<Media>
@Inject lateinit var featureManager: FeatureManager
@Inject lateinit var events: Events
- @Inject lateinit var bannerManager: Lazy<BannerManager>
@Inject override lateinit var configurationManager: ConfigurationManager
@Before
@@ -198,7 +197,6 @@ class NavigationBarFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
diff --git a/photopicker/tests/src/com/android/photopicker/features/overflowmenu/OverflowMenuFeatureTest.kt b/photopicker/tests/src/com/android/photopicker/features/overflowmenu/OverflowMenuFeatureTest.kt
index db5db7092..f34b328dc 100644
--- a/photopicker/tests/src/com/android/photopicker/features/overflowmenu/OverflowMenuFeatureTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/overflowmenu/OverflowMenuFeatureTest.kt
@@ -40,9 +40,11 @@ import com.android.photopicker.core.ConcurrencyModule
import com.android.photopicker.core.EmbeddedServiceModule
import com.android.photopicker.core.Main
import com.android.photopicker.core.configuration.ConfigurationManager
+import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
import com.android.photopicker.core.configuration.testActionPickImagesConfiguration
import com.android.photopicker.core.configuration.testGetContentConfiguration
+import com.android.photopicker.core.configuration.testPhotopickerConfiguration
import com.android.photopicker.core.configuration.testUserSelectImagesForAppConfiguration
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.events.LocalEvents
@@ -51,6 +53,7 @@ import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureToken.OVERFLOW_MENU
import com.android.photopicker.core.features.LocalFeatureManager
import com.android.photopicker.core.features.Location
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.features.PhotopickerFeatureBaseTest
import com.android.photopicker.features.simpleuifeature.SimpleUiFeature
import com.android.photopicker.inject.PhotopickerTestModule
@@ -91,6 +94,7 @@ class OverflowMenuFeatureTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
@@ -165,6 +169,7 @@ class OverflowMenuFeatureTest : PhotopickerFeatureBaseTest() {
CompositionLocalProvider(
LocalFeatureManager provides featureManager,
LocalEvents provides events,
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration,
) {
featureManager.composeLocation(Location.OVERFLOW_MENU)
}
@@ -246,6 +251,7 @@ class OverflowMenuFeatureTest : PhotopickerFeatureBaseTest() {
CompositionLocalProvider(
LocalFeatureManager provides featureManager,
LocalEvents provides events,
+ LocalPhotopickerConfiguration provides testPhotopickerConfiguration
) {
featureManager.composeLocation(Location.OVERFLOW_MENU)
}
diff --git a/photopicker/tests/src/com/android/photopicker/features/photogrid/PhotoGridFeatureTest.kt b/photopicker/tests/src/com/android/photopicker/features/photogrid/PhotoGridFeatureTest.kt
index 492f110e6..f3b1573a8 100644
--- a/photopicker/tests/src/com/android/photopicker/features/photogrid/PhotoGridFeatureTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/photogrid/PhotoGridFeatureTest.kt
@@ -45,11 +45,15 @@ import com.android.photopicker.core.EmbeddedServiceModule
import com.android.photopicker.core.Main
import com.android.photopicker.core.ViewModelModule
import com.android.photopicker.core.banners.BannerManager
+import com.android.photopicker.core.banners.BannerStateDao
import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.configuration.PhotopickerConfiguration
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
+import com.android.photopicker.core.database.DatabaseManager
import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.navigation.PhotopickerDestinations
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.data.DataService
@@ -83,6 +87,8 @@ import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
import org.mockito.MockitoAnnotations
@UninstallModules(
@@ -100,6 +106,7 @@ class PhotoGridFeatureTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
@@ -137,6 +144,9 @@ class PhotoGridFeatureTest : PhotopickerFeatureBaseTest() {
@Inject lateinit var events: Events
@Inject lateinit var bannerManager: Lazy<BannerManager>
@Inject lateinit var dataService: DataService
+ @Inject lateinit var databaseManager: DatabaseManager
+
+ val sessionId = generatePickerSessionId()
@Before
fun setup() {
@@ -162,17 +172,19 @@ class PhotoGridFeatureTest : PhotopickerFeatureBaseTest() {
@Test
fun testPhotoGridIsAlwaysEnabled() {
- val configOne = PhotopickerConfiguration(action = "TEST_ACTION")
+ val configOne = PhotopickerConfiguration(action = "TEST_ACTION", sessionId = sessionId)
assertWithMessage("PhotoGridFeature is not always enabled for TEST_ACTION")
.that(PhotoGridFeature.Registration.isEnabled(configOne))
.isEqualTo(true)
- val configTwo = PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES)
+ val configTwo =
+ PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES, sessionId = sessionId)
assertWithMessage("PhotoGridFeature is not always enabled")
.that(PhotoGridFeature.Registration.isEnabled(configTwo))
.isEqualTo(true)
- val configThree = PhotopickerConfiguration(action = Intent.ACTION_GET_CONTENT)
+ val configThree =
+ PhotopickerConfiguration(action = Intent.ACTION_GET_CONTENT, sessionId = sessionId)
assertWithMessage("PhotoGridFeature is not always enabled")
.that(PhotoGridFeature.Registration.isEnabled(configThree))
.isEqualTo(true)
@@ -195,7 +207,6 @@ class PhotoGridFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -219,7 +230,6 @@ class PhotoGridFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -257,7 +267,6 @@ class PhotoGridFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -284,7 +293,6 @@ class PhotoGridFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -321,7 +329,6 @@ class PhotoGridFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -338,4 +345,39 @@ class PhotoGridFeatureTest : PhotopickerFeatureBaseTest() {
.assertIsDisplayed()
}
}
+
+ @Test
+ fun testShowsBannersInGrid() {
+
+ testScope.runTest {
+ val bannerStateDao = databaseManager.acquireDao(BannerStateDao::class.java)
+ whenever(bannerStateDao.getBannerState(anyString(), anyInt())) { null }
+
+ configurationManager.setCaller(
+ callingPackage = "com.android.test.package",
+ callingPackageUid = 12345,
+ callingPackageLabel = "Test Package",
+ )
+ advanceTimeBy(100)
+
+ bannerManager.get().refreshBanners()
+ composeTestRule.setContent {
+ callPhotopickerMain(
+ featureManager = featureManager,
+ selection = selection,
+ events = events,
+ )
+ }
+
+ val resources = getTestableContext().getResources()
+ val expectedPrivacyMessage =
+ resources.getString(R.string.photopicker_privacy_explainer, "Test Package")
+
+ // Wait for the PhotoGridViewModel to load data and for the UI to update.
+ advanceTimeBy(100)
+ composeTestRule.waitForIdle()
+
+ composeTestRule.onNode(hasText(expectedPrivacyMessage)).assertIsDisplayed()
+ }
+ }
}
diff --git a/photopicker/tests/src/com/android/photopicker/features/photogrid/PhotoGridViewModelTest.kt b/photopicker/tests/src/com/android/photopicker/features/photogrid/PhotoGridViewModelTest.kt
index a69ef3a4c..04be68e75 100644
--- a/photopicker/tests/src/com/android/photopicker/features/photogrid/PhotoGridViewModelTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/photogrid/PhotoGridViewModelTest.kt
@@ -16,34 +16,85 @@
package com.android.photopicker.features.photogrid
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.UserProperties
import android.net.Uri
+import android.os.Parcel
+import android.os.UserHandle
+import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.modules.utils.build.SdkLevel
+import com.android.photopicker.R
+import com.android.photopicker.core.banners.BannerDefinitions
+import com.android.photopicker.core.banners.BannerManagerImpl
+import com.android.photopicker.core.banners.BannerState
+import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
+import com.android.photopicker.core.configuration.TestDeviceConfigProxyImpl
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
+import com.android.photopicker.core.configuration.testActionPickImagesConfiguration
+import com.android.photopicker.core.database.DatabaseManagerTestImpl
import com.android.photopicker.core.events.Event
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.events.RegisteredEventClass
+import com.android.photopicker.core.events.Telemetry
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureToken.PHOTO_GRID
import com.android.photopicker.core.selection.SelectionImpl
+import com.android.photopicker.core.user.UserMonitor
import com.android.photopicker.data.TestDataServiceImpl
import com.android.photopicker.data.model.Media
import com.android.photopicker.data.model.MediaSource
+import com.android.photopicker.tests.utils.mockito.mockSystemService
+import com.android.photopicker.tests.utils.mockito.whenever
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class PhotoGridViewModelTest {
+ private val USER_ID_PRIMARY: Int = 0
+ private val USER_HANDLE_PRIMARY: UserHandle
+ private val PLATFORM_PROVIDED_PROFILE_LABEL = "Platform Label"
+ private val deviceConfigProxy = TestDeviceConfigProxyImpl()
+ @Mock lateinit var mockContext: Context
+ @Mock lateinit var mockUserManager: UserManager
+ @Mock lateinit var mockPackageManager: PackageManager
+ @Mock lateinit var mockContentResolver: ContentResolver
+
+ init {
+ val parcel1 = Parcel.obtain()
+ parcel1.writeInt(USER_ID_PRIMARY)
+ parcel1.setDataPosition(0)
+ USER_HANDLE_PRIMARY = UserHandle(parcel1)
+ parcel1.recycle()
+ }
+
val mediaItem =
Media.Image(
mediaId = "id",
@@ -73,6 +124,46 @@ class PhotoGridViewModelTest {
mimeType = "image/png",
standardMimeTypeExtension = 1,
)
+ val updatedMediaItem =
+ mediaItem.copy(mediaItemAlbum = null, selectionSource = Telemetry.MediaLocation.MAIN_GRID)
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ deviceConfigProxy.reset()
+ val resources = InstrumentationRegistry.getInstrumentation().getContext().getResources()
+
+ mockSystemService(mockContext, UserManager::class.java) { mockUserManager }
+ whenever(mockContext.packageManager) { mockPackageManager }
+ whenever(mockContext.contentResolver) { mockContentResolver }
+ whenever(mockContext.createPackageContextAsUser(any(), anyInt(), any())) { mockContext }
+ whenever(mockContext.createContextAsUser(any(UserHandle::class.java), anyInt())) {
+ mockContext
+ }
+
+ // Initial setup state: Two profiles (Personal/Work), both enabled
+ whenever(mockUserManager.userProfiles) { listOf(USER_HANDLE_PRIMARY) }
+
+ // Default responses for relevant UserManager apis
+ whenever(mockUserManager.isQuietModeEnabled(USER_HANDLE_PRIMARY)) { false }
+ whenever(mockUserManager.isManagedProfile(USER_ID_PRIMARY)) { false }
+
+ val mockResolveInfo = mock(ResolveInfo::class.java)
+ whenever(mockResolveInfo.isCrossProfileIntentForwarderActivity()) { true }
+ whenever(mockPackageManager.queryIntentActivities(any(Intent::class.java), anyInt())) {
+ listOf(mockResolveInfo)
+ }
+
+ if (SdkLevel.isAtLeastV()) {
+ whenever(mockUserManager.getUserBadge()) {
+ resources.getDrawable(R.drawable.android, /* theme= */ null)
+ }
+ whenever(mockUserManager.getProfileLabel()) { PLATFORM_PROVIDED_PROFILE_LABEL }
+ whenever(mockUserManager.getUserProperties(USER_HANDLE_PRIMARY)) {
+ UserProperties.Builder().build()
+ }
+ }
+ }
@Test
fun testPhotoGridItemClickedUpdatesSelection() {
@@ -97,12 +188,46 @@ class PhotoGridViewModelTest {
featureManager = featureManager,
)
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId(),
+ )
+
+ val userMonitor =
+ UserMonitor(
+ mockContext,
+ provideTestConfigurationFlow(
+ scope = this.backgroundScope,
+ defaultConfiguration = testActionPickImagesConfiguration,
+ ),
+ this.backgroundScope,
+ StandardTestDispatcher(this.testScheduler),
+ USER_HANDLE_PRIMARY
+ )
+
+ val bannerManager =
+ BannerManagerImpl(
+ scope = this.backgroundScope,
+ backgroundDispatcher = StandardTestDispatcher(this.testScheduler),
+ configurationManager = configurationManager,
+ databaseManager = DatabaseManagerTestImpl(),
+ featureManager = featureManager,
+ dataService = TestDataServiceImpl(),
+ userMonitor = userMonitor,
+ processOwnerHandle = USER_HANDLE_PRIMARY
+ )
+
val viewModel =
PhotoGridViewModel(
this.backgroundScope,
selection,
TestDataServiceImpl(),
events,
+ bannerManager,
)
assertWithMessage("Unexpected selection start size")
@@ -115,9 +240,10 @@ class PhotoGridViewModelTest {
// Wait for selection update.
advanceTimeBy(100)
+ // The selected media item gets updated with the Selectable interface values
assertWithMessage("Selection did not contain expected item")
.that(selection.snapshot())
- .contains(mediaItem)
+ .contains(updatedMediaItem)
// Toggle the item out of the selection
viewModel.handleGridItemSelection(mediaItem, "")
@@ -126,7 +252,7 @@ class PhotoGridViewModelTest {
assertWithMessage("Selection contains unexpected item")
.that(selection.snapshot())
- .doesNotContain(mediaItem)
+ .doesNotContain(updatedMediaItem)
}
}
@@ -144,7 +270,8 @@ class PhotoGridViewModelTest {
PhotopickerConfiguration(
action = "TEST_ACTION",
intent = null,
- selectionLimit = 0
+ selectionLimit = 0,
+ sessionId = generatePickerSessionId()
)
)
)
@@ -167,12 +294,46 @@ class PhotoGridViewModelTest {
val eventsDispatched = mutableListOf<Event>()
backgroundScope.launch { events.flow.toList(eventsDispatched) }
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId(),
+ )
+
+ val userMonitor =
+ UserMonitor(
+ mockContext,
+ provideTestConfigurationFlow(
+ scope = this.backgroundScope,
+ defaultConfiguration = testActionPickImagesConfiguration,
+ ),
+ this.backgroundScope,
+ StandardTestDispatcher(this.testScheduler),
+ USER_HANDLE_PRIMARY
+ )
+
+ val bannerManager =
+ BannerManagerImpl(
+ scope = this.backgroundScope,
+ backgroundDispatcher = StandardTestDispatcher(this.testScheduler),
+ configurationManager = configurationManager,
+ databaseManager = DatabaseManagerTestImpl(),
+ featureManager = featureManager,
+ dataService = TestDataServiceImpl(),
+ userMonitor = userMonitor,
+ processOwnerHandle = USER_HANDLE_PRIMARY
+ )
+
val viewModel =
PhotoGridViewModel(
this.backgroundScope,
selection,
TestDataServiceImpl(),
events,
+ bannerManager,
)
assertWithMessage("Unexpected selection start size")
@@ -182,7 +343,6 @@ class PhotoGridViewModelTest {
// Toggle the item into the selection
val errorMessage = "test"
viewModel.handleGridItemSelection(mediaItem, errorMessage)
-
// Wait for selection update.
advanceTimeBy(100)
@@ -191,4 +351,84 @@ class PhotoGridViewModelTest {
.contains(Event.ShowSnackbarMessage(PHOTO_GRID.token, errorMessage))
}
}
+
+ @Test
+ fun testPhotoGridBannerDismissedHandler() {
+
+ runTest {
+ val selection =
+ SelectionImpl<Media>(
+ scope = this.backgroundScope,
+ configuration = provideTestConfigurationFlow(scope = this.backgroundScope)
+ )
+
+ val featureManager =
+ FeatureManager(
+ configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
+ scope = this.backgroundScope,
+ )
+
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager = featureManager,
+ )
+
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId(),
+ )
+
+ val userMonitor =
+ UserMonitor(
+ mockContext,
+ provideTestConfigurationFlow(
+ scope = this.backgroundScope,
+ defaultConfiguration = testActionPickImagesConfiguration,
+ ),
+ this.backgroundScope,
+ StandardTestDispatcher(this.testScheduler),
+ USER_HANDLE_PRIMARY
+ )
+
+ val databaseManager = DatabaseManagerTestImpl()
+
+ val bannerManager =
+ BannerManagerImpl(
+ scope = this.backgroundScope,
+ backgroundDispatcher = StandardTestDispatcher(this.testScheduler),
+ configurationManager = configurationManager,
+ databaseManager = databaseManager,
+ featureManager = featureManager,
+ dataService = TestDataServiceImpl(),
+ userMonitor = userMonitor,
+ processOwnerHandle = USER_HANDLE_PRIMARY
+ )
+
+ val viewModel =
+ PhotoGridViewModel(
+ this.backgroundScope,
+ selection,
+ TestDataServiceImpl(),
+ events,
+ bannerManager,
+ )
+
+ viewModel.markBannerAsDismissed(BannerDefinitions.CLOUD_CHOOSE_ACCOUNT)
+ advanceTimeBy(100)
+ verify(databaseManager.bannerState)
+ .setBannerState(
+ BannerState(
+ bannerId = BannerDefinitions.CLOUD_CHOOSE_ACCOUNT.id,
+ uid = 0,
+ dismissed = true,
+ )
+ )
+ }
+ }
}
diff --git a/photopicker/tests/src/com/android/photopicker/features/preview/PreviewFeatureTest.kt b/photopicker/tests/src/com/android/photopicker/features/preview/PreviewFeatureTest.kt
index be5b4bd59..f97ba8740 100644
--- a/photopicker/tests/src/com/android/photopicker/features/preview/PreviewFeatureTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/preview/PreviewFeatureTest.kt
@@ -62,10 +62,10 @@ import com.android.photopicker.core.ConcurrencyModule
import com.android.photopicker.core.EmbeddedServiceModule
import com.android.photopicker.core.Main
import com.android.photopicker.core.ViewModelModule
-import com.android.photopicker.core.banners.BannerManager
import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.navigation.PhotopickerDestinations
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.data.model.Media
@@ -80,7 +80,6 @@ import com.android.photopicker.tests.utils.mockito.capture
import com.android.photopicker.tests.utils.mockito.nonNullableEq
import com.android.photopicker.tests.utils.mockito.whenever
import com.google.common.truth.Truth.assertWithMessage
-import dagger.Lazy
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.testing.BindValue
@@ -129,6 +128,7 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
@@ -168,7 +168,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
@Inject lateinit var selection: Selection<Media>
@Inject lateinit var featureManager: FeatureManager
@Inject lateinit var events: Events
- @Inject lateinit var bannerManager: Lazy<BannerManager>
@Inject override lateinit var configurationManager: ConfigurationManager
val TEST_MEDIA_IMAGE =
@@ -330,7 +329,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
}
@@ -371,7 +369,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
}
@@ -409,7 +406,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -447,7 +443,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
}
@@ -457,6 +452,7 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
// Navigate on the UI thread (similar to a click handler)
composeTestRule.runOnUiThread({ navController.navigateToPreviewSelection() })
+ composeTestRule.waitForIdle()
assertWithMessage("Expected route to be preview/selection")
.that(navController.currentBackStackEntry?.destination?.route)
@@ -477,7 +473,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
}
@@ -549,7 +544,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
}
@@ -598,7 +592,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -646,7 +639,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -692,7 +684,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -744,7 +735,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -825,7 +815,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -900,7 +889,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -977,7 +965,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
@@ -1052,7 +1039,6 @@ class PreviewFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
diff --git a/photopicker/tests/src/com/android/photopicker/features/preview/PreviewViewModelTest.kt b/photopicker/tests/src/com/android/photopicker/features/preview/PreviewViewModelTest.kt
index f70e8e26c..9c3aaedbd 100644
--- a/photopicker/tests/src/com/android/photopicker/features/preview/PreviewViewModelTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/preview/PreviewViewModelTest.kt
@@ -55,8 +55,15 @@ import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.modules.utils.build.SdkLevel
import com.android.photopicker.R
+import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.configuration.MULTI_SELECT_CONFIG
+import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
+import com.android.photopicker.core.configuration.TestDeviceConfigProxyImpl
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.generatePickerSessionId
+import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.features.FeatureRegistration
import com.android.photopicker.core.selection.GrantsAwareSelectionImpl
import com.android.photopicker.core.selection.SelectionImpl
import com.android.photopicker.core.user.UserMonitor
@@ -110,6 +117,7 @@ class PreviewViewModelTest {
private val USER_HANDLE_PRIMARY: UserHandle
private val USER_ID_PRIMARY: Int = 0
+ private val deviceConfigProxy = TestDeviceConfigProxyImpl()
init {
val parcel1 = Parcel.obtain()
@@ -210,6 +218,7 @@ class PreviewViewModelTest {
@Before
fun setup() {
+ deviceConfigProxy.reset()
MockitoAnnotations.initMocks(this)
mockSystemService(mockContext, UserManager::class.java) { mockUserManager }
@@ -260,6 +269,26 @@ class PreviewViewModelTest {
fun testToggleInSelectionUpdatesSelection() {
runTest {
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId()
+ )
+ val featureManager =
+ FeatureManager(
+ configurationManager.configuration,
+ this.backgroundScope,
+ emptySet<FeatureRegistration>(),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager
+ )
val selection =
SelectionImpl<Media>(
scope = this.backgroundScope,
@@ -277,7 +306,9 @@ class PreviewViewModelTest {
StandardTestDispatcher(this.testScheduler),
USER_HANDLE_PRIMARY
),
- dataService = TestDataServiceImpl()
+ dataService = TestDataServiceImpl(),
+ events,
+ configurationManager
)
assertWithMessage("Unexpected selection start size")
@@ -309,6 +340,26 @@ class PreviewViewModelTest {
fun testToggleInSelectionCollectionUpdatesSelection() {
runTest {
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId()
+ )
+ val featureManager =
+ FeatureManager(
+ configurationManager.configuration,
+ this.backgroundScope,
+ emptySet<FeatureRegistration>(),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager
+ )
val selection =
SelectionImpl<Media>(
scope = this.backgroundScope,
@@ -330,7 +381,9 @@ class PreviewViewModelTest {
StandardTestDispatcher(this.testScheduler),
USER_HANDLE_PRIMARY
),
- dataService = TestDataServiceImpl()
+ dataService = TestDataServiceImpl(),
+ events,
+ configurationManager
)
assertWithMessage("Unexpected selection start size")
@@ -369,6 +422,26 @@ class PreviewViewModelTest {
configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
initialSelection = setOf(TEST_MEDIA_IMAGE),
)
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId()
+ )
+ val featureManager =
+ FeatureManager(
+ configurationManager.configuration,
+ this.backgroundScope,
+ emptySet<FeatureRegistration>(),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager
+ )
val viewModel =
PreviewViewModel(
@@ -381,7 +454,9 @@ class PreviewViewModelTest {
StandardTestDispatcher(this.testScheduler),
USER_HANDLE_PRIMARY
),
- dataService = TestDataServiceImpl()
+ dataService = TestDataServiceImpl(),
+ events,
+ configurationManager
)
var snapshot = viewModel.selectionSnapshot.first()
@@ -412,6 +487,27 @@ class PreviewViewModelTest {
GrantsAwareSelectionImpl<Media>(
scope = this.backgroundScope,
configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount
+ )
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId()
+ )
+ val featureManager =
+ FeatureManager(
+ configurationManager.configuration,
+ this.backgroundScope,
+ emptySet<FeatureRegistration>(),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager
)
val viewModel =
@@ -425,7 +521,9 @@ class PreviewViewModelTest {
StandardTestDispatcher(this.testScheduler),
USER_HANDLE_PRIMARY
),
- dataService = TestDataServiceImpl()
+ dataService = TestDataServiceImpl(),
+ events,
+ configurationManager
)
// remove a pre-granted item and it should be added to the deselection snapshot.
@@ -459,6 +557,26 @@ class PreviewViewModelTest {
configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
initialSelection = setOf(TEST_MEDIA_IMAGE),
)
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId()
+ )
+ val featureManager =
+ FeatureManager(
+ configurationManager.configuration,
+ this.backgroundScope,
+ emptySet<FeatureRegistration>(),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager
+ )
val viewModel =
PreviewViewModel(
this.backgroundScope,
@@ -470,7 +588,9 @@ class PreviewViewModelTest {
StandardTestDispatcher(this.testScheduler),
USER_HANDLE_PRIMARY
),
- dataService = TestDataServiceImpl()
+ dataService = TestDataServiceImpl(),
+ events,
+ configurationManager
)
val controller =
@@ -514,6 +634,26 @@ class PreviewViewModelTest {
configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
initialSelection = setOf(TEST_MEDIA_IMAGE),
)
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId()
+ )
+ val featureManager =
+ FeatureManager(
+ configurationManager.configuration,
+ this.backgroundScope,
+ emptySet<FeatureRegistration>(),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager
+ )
val viewModel =
PreviewViewModel(
this.backgroundScope,
@@ -525,7 +665,9 @@ class PreviewViewModelTest {
StandardTestDispatcher(this.testScheduler),
USER_HANDLE_PRIMARY
),
- dataService = TestDataServiceImpl()
+ dataService = TestDataServiceImpl(),
+ events,
+ configurationManager
)
val controller =
@@ -613,6 +755,26 @@ class PreviewViewModelTest {
configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
initialSelection = setOf(TEST_MEDIA_IMAGE),
)
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId()
+ )
+ val featureManager =
+ FeatureManager(
+ configurationManager.configuration,
+ this.backgroundScope,
+ emptySet<FeatureRegistration>(),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager
+ )
val viewModel =
PreviewViewModel(
this.backgroundScope,
@@ -624,7 +786,9 @@ class PreviewViewModelTest {
StandardTestDispatcher(this.testScheduler),
USER_HANDLE_PRIMARY
),
- dataService = TestDataServiceImpl()
+ dataService = TestDataServiceImpl(),
+ events,
+ configurationManager
)
viewModel.getControllerForAuthority(MockContentProviderWrapper.AUTHORITY)
@@ -645,6 +809,26 @@ class PreviewViewModelTest {
configuration = provideTestConfigurationFlow(scope = this.backgroundScope),
initialSelection = setOf(TEST_MEDIA_IMAGE),
)
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId()
+ )
+ val featureManager =
+ FeatureManager(
+ configurationManager.configuration,
+ this.backgroundScope,
+ registeredFeatures = setOf(PreviewFeature.Registration),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager
+ )
val viewModel =
PreviewViewModel(
this.backgroundScope,
@@ -656,7 +840,9 @@ class PreviewViewModelTest {
StandardTestDispatcher(this.testScheduler),
USER_HANDLE_PRIMARY
),
- dataService = TestDataServiceImpl()
+ dataService = TestDataServiceImpl(),
+ events,
+ configurationManager
)
viewModel.getControllerForAuthority(MockContentProviderWrapper.AUTHORITY)
diff --git a/photopicker/tests/src/com/android/photopicker/features/privacyexplainer/PrivacyExplainerFeatureTest.kt b/photopicker/tests/src/com/android/photopicker/features/privacyexplainer/PrivacyExplainerFeatureTest.kt
index f84f5ec38..7d67f0430 100644
--- a/photopicker/tests/src/com/android/photopicker/features/privacyexplainer/PrivacyExplainerFeatureTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/privacyexplainer/PrivacyExplainerFeatureTest.kt
@@ -44,6 +44,7 @@ import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.database.DatabaseManager
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.data.model.Media
import com.android.photopicker.features.PhotopickerFeatureBaseTest
@@ -92,6 +93,7 @@ class PrivacyExplainerFeatureTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
@@ -177,7 +179,6 @@ class PrivacyExplainerFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
composeTestRule.waitForIdle()
@@ -220,7 +221,6 @@ class PrivacyExplainerFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
composeTestRule.waitForIdle()
diff --git a/photopicker/tests/src/com/android/photopicker/features/profileselector/ProfileSelectorFeatureTest.kt b/photopicker/tests/src/com/android/photopicker/features/profileselector/ProfileSelectorFeatureTest.kt
index 9be1300bb..a45b11759 100644
--- a/photopicker/tests/src/com/android/photopicker/features/profileselector/ProfileSelectorFeatureTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/profileselector/ProfileSelectorFeatureTest.kt
@@ -44,10 +44,10 @@ import com.android.photopicker.core.ConcurrencyModule
import com.android.photopicker.core.EmbeddedServiceModule
import com.android.photopicker.core.Main
import com.android.photopicker.core.ViewModelModule
-import com.android.photopicker.core.banners.BannerManager
import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.selection.Selection
import com.android.photopicker.data.model.Media
import com.android.photopicker.features.PhotopickerFeatureBaseTest
@@ -55,7 +55,6 @@ import com.android.photopicker.inject.PhotopickerTestModule
import com.android.photopicker.tests.HiltTestActivity
import com.android.photopicker.tests.utils.mockito.mockSystemService
import com.android.photopicker.tests.utils.mockito.whenever
-import dagger.Lazy
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.testing.BindValue
@@ -94,6 +93,7 @@ class ProfileSelectorFeatureTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
@@ -116,7 +116,6 @@ class ProfileSelectorFeatureTest : PhotopickerFeatureBaseTest() {
@Inject lateinit var selection: Selection<Media>
@Inject lateinit var featureManager: FeatureManager
@Inject lateinit var userHandle: UserHandle
- @Inject lateinit var bannerManager: Lazy<BannerManager>
@Inject override lateinit var configurationManager: ConfigurationManager
@BindValue @ApplicationOwned val contentResolver: ContentResolver = MockContentResolver()
@@ -160,7 +159,6 @@ class ProfileSelectorFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
composeTestRule
@@ -182,7 +180,6 @@ class ProfileSelectorFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
composeTestRule
@@ -270,7 +267,6 @@ class ProfileSelectorFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
composeTestRule
@@ -349,7 +345,6 @@ class ProfileSelectorFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
composeTestRule
@@ -392,7 +387,6 @@ class ProfileSelectorFeatureTest : PhotopickerFeatureBaseTest() {
featureManager = featureManager,
selection = selection,
events = events,
- bannerManager = bannerManager.get(),
)
}
composeTestRule
diff --git a/photopicker/tests/src/com/android/photopicker/features/profileselector/ProfileSelectorViewModelTest.kt b/photopicker/tests/src/com/android/photopicker/features/profileselector/ProfileSelectorViewModelTest.kt
index 50e0cac6c..31aba1d8c 100644
--- a/photopicker/tests/src/com/android/photopicker/features/profileselector/ProfileSelectorViewModelTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/profileselector/ProfileSelectorViewModelTest.kt
@@ -31,8 +31,15 @@ import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.modules.utils.build.SdkLevel
import com.android.photopicker.R
+import com.android.photopicker.core.configuration.ConfigurationManager
+import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
+import com.android.photopicker.core.configuration.TestDeviceConfigProxyImpl
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
import com.android.photopicker.core.configuration.testActionPickImagesConfiguration
+import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.generatePickerSessionId
+import com.android.photopicker.core.features.FeatureManager
+import com.android.photopicker.core.features.FeatureRegistration
import com.android.photopicker.core.selection.SelectionImpl
import com.android.photopicker.core.user.UserMonitor
import com.android.photopicker.core.user.UserProfile
@@ -65,6 +72,7 @@ class ProfileSelectorViewModelTest {
@Mock lateinit var mockPackageManager: PackageManager
private val mockContentResolver: MockContentResolver = MockContentResolver()
+ private val deviceConfigProxy = TestDeviceConfigProxyImpl()
private val USER_HANDLE_PRIMARY: UserHandle
private val USER_ID_PRIMARY: Int = 0
@@ -118,6 +126,7 @@ class ProfileSelectorViewModelTest {
@Before
fun setup() {
+ deviceConfigProxy.reset()
MockitoAnnotations.initMocks(this)
mockSystemService(mockContext, UserManager::class.java) { mockUserManager }
@@ -169,6 +178,26 @@ class ProfileSelectorViewModelTest {
scope = this.backgroundScope,
configuration = provideTestConfigurationFlow(scope = this.backgroundScope)
)
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId()
+ )
+ val featureManager =
+ FeatureManager(
+ configurationManager.configuration,
+ this.backgroundScope,
+ emptySet<FeatureRegistration>(),
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager
+ )
val viewModel =
ProfileSelectorViewModel(
@@ -184,6 +213,8 @@ class ProfileSelectorViewModelTest {
StandardTestDispatcher(this.testScheduler),
USER_HANDLE_PRIMARY
),
+ events,
+ configurationManager
)
assertWithMessage("Expected available number of profiles to be 2.")
@@ -217,6 +248,25 @@ class ProfileSelectorViewModelTest {
scope = this.backgroundScope,
configuration = provideTestConfigurationFlow(scope = this.backgroundScope)
)
+ val configurationManager =
+ ConfigurationManager(
+ runtimeEnv = PhotopickerRuntimeEnv.ACTIVITY,
+ scope = this.backgroundScope,
+ dispatcher = StandardTestDispatcher(this.testScheduler),
+ deviceConfigProxy,
+ generatePickerSessionId()
+ )
+ val featureManager =
+ FeatureManager(
+ configurationManager.configuration,
+ this.backgroundScope,
+ )
+ val events =
+ Events(
+ scope = this.backgroundScope,
+ provideTestConfigurationFlow(scope = this.backgroundScope),
+ featureManager
+ )
val viewModel =
ProfileSelectorViewModel(
@@ -232,6 +282,8 @@ class ProfileSelectorViewModelTest {
StandardTestDispatcher(this.testScheduler),
USER_HANDLE_PRIMARY
),
+ events,
+ configurationManager
)
selection.add(TEST_MEDIA_IMAGE)
diff --git a/photopicker/tests/src/com/android/photopicker/features/selectionbar/SelectionBarFeatureTest.kt b/photopicker/tests/src/com/android/photopicker/features/selectionbar/SelectionBarFeatureTest.kt
index 012efb2c5..394e49bf3 100644
--- a/photopicker/tests/src/com/android/photopicker/features/selectionbar/SelectionBarFeatureTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/selectionbar/SelectionBarFeatureTest.kt
@@ -47,13 +47,16 @@ import com.android.photopicker.core.Main
import com.android.photopicker.core.configuration.ConfigurationManager
import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
import com.android.photopicker.core.configuration.PhotopickerConfiguration
+import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
import com.android.photopicker.core.configuration.provideTestConfigurationFlow
import com.android.photopicker.core.configuration.testPhotopickerConfiguration
import com.android.photopicker.core.events.Events
import com.android.photopicker.core.events.LocalEvents
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.LocalFeatureManager
import com.android.photopicker.core.features.LocationParams
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.navigation.LocalNavController
import com.android.photopicker.core.selection.LocalSelection
import com.android.photopicker.core.selection.Selection
@@ -103,11 +106,13 @@ class SelectionBarFeatureTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
val testDispatcher = StandardTestDispatcher()
+ val sessionId = generatePickerSessionId()
/* Overrides for ActivityModule */
val testScope: TestScope = TestScope(testDispatcher)
@@ -183,44 +188,84 @@ class SelectionBarFeatureTest : PhotopickerFeatureBaseTest() {
}
@Test
- fun testSelectionBarIsEnabledWithSelectionLimit() {
- val configOne = PhotopickerConfiguration(action = "TEST_ACTION", selectionLimit = 5)
+ fun testSelectionBarIsEnabledWithSelectionLimitInActivityMode() {
+ val configOne =
+ PhotopickerConfiguration(
+ action = "TEST_ACTION",
+ selectionLimit = 5,
+ sessionId = sessionId
+ )
assertWithMessage("SelectionBarFeature is not always enabled for TEST_ACTION")
.that(SelectionBarFeature.Registration.isEnabled(configOne))
.isEqualTo(true)
val configTwo =
- PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES, selectionLimit = 5)
+ PhotopickerConfiguration(
+ action = MediaStore.ACTION_PICK_IMAGES,
+ selectionLimit = 5,
+ sessionId = sessionId
+ )
assertWithMessage("SelectionBarFeature is not always enabled")
.that(SelectionBarFeature.Registration.isEnabled(configTwo))
.isEqualTo(true)
val configThree =
- PhotopickerConfiguration(action = Intent.ACTION_GET_CONTENT, selectionLimit = 5)
+ PhotopickerConfiguration(
+ action = Intent.ACTION_GET_CONTENT,
+ selectionLimit = 5,
+ sessionId = sessionId
+ )
assertWithMessage("SelectionBarFeature is not always enabled")
.that(SelectionBarFeature.Registration.isEnabled(configThree))
.isEqualTo(true)
}
@Test
- fun testSelectionBarNotEnabledForSingleSelect() {
- val configOne = PhotopickerConfiguration(action = "TEST_ACTION")
+ fun testSelectionBarNotEnabledForSingleSelectInActivityMode() {
+ val configOne = PhotopickerConfiguration(action = "TEST_ACTION", sessionId = sessionId)
assertWithMessage("SelectionBarFeature is not always enabled for TEST_ACTION")
.that(SelectionBarFeature.Registration.isEnabled(configOne))
.isEqualTo(false)
- val configTwo = PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES)
+ val configTwo =
+ PhotopickerConfiguration(action = MediaStore.ACTION_PICK_IMAGES, sessionId = sessionId)
assertWithMessage("SelectionBarFeature is not always enabled")
.that(SelectionBarFeature.Registration.isEnabled(configTwo))
.isEqualTo(false)
- val configThree = PhotopickerConfiguration(action = Intent.ACTION_GET_CONTENT)
+ val configThree =
+ PhotopickerConfiguration(action = Intent.ACTION_GET_CONTENT, sessionId = sessionId)
assertWithMessage("SelectionBarFeature is not always enabled")
.that(SelectionBarFeature.Registration.isEnabled(configThree))
.isEqualTo(false)
}
@Test
+ fun testSelectionBarIsAlwaysEnabledInEmbeddedMode() {
+ val configOne =
+ PhotopickerConfiguration(
+ action = "",
+ runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED,
+ selectionLimit = 1,
+ sessionId = sessionId
+ )
+ assertWithMessage("SelectionBarFeature not always enabled for EMBEDDED mode")
+ .that(SelectionBarFeature.Registration.isEnabled(configOne))
+ .isEqualTo(true)
+
+ val configTwo =
+ PhotopickerConfiguration(
+ action = "",
+ runtimeEnv = PhotopickerRuntimeEnv.EMBEDDED,
+ selectionLimit = 20,
+ sessionId = sessionId
+ )
+ assertWithMessage("SelectionBarFeature not always enabled for EMBEDDED mode")
+ .that(SelectionBarFeature.Registration.isEnabled(configTwo))
+ .isEqualTo(true)
+ }
+
+ @Test
fun testSelectionBarIsShown() {
testScope.runTest {
val photopickerConfiguration: PhotopickerConfiguration = testPhotopickerConfiguration
diff --git a/photopicker/tests/src/com/android/photopicker/features/snackbar/SnackbarFeatureTest.kt b/photopicker/tests/src/com/android/photopicker/features/snackbar/SnackbarFeatureTest.kt
index a9fde63e7..10bd21753 100644
--- a/photopicker/tests/src/com/android/photopicker/features/snackbar/SnackbarFeatureTest.kt
+++ b/photopicker/tests/src/com/android/photopicker/features/snackbar/SnackbarFeatureTest.kt
@@ -45,6 +45,7 @@ import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.features.FeatureToken.CORE
import com.android.photopicker.core.features.LocalFeatureManager
import com.android.photopicker.core.features.Location
+import com.android.photopicker.core.glide.GlideTestRule
import com.android.photopicker.core.navigation.LocalNavController
import com.android.photopicker.core.selection.LocalSelection
import com.android.photopicker.core.selection.Selection
@@ -89,6 +90,7 @@ class SnackbarFeatureTest : PhotopickerFeatureBaseTest() {
@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
+ @get:Rule(order = 2) val glideRule = GlideTestRule()
/* Setup dependencies for the UninstallModules for the test class. */
@Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
diff --git a/photopicker/tests/src/com/android/photopicker/inject/EmbeddedTestModule.kt b/photopicker/tests/src/com/android/photopicker/inject/EmbeddedTestModule.kt
index 5f043beae..7b4453ee7 100644
--- a/photopicker/tests/src/com/android/photopicker/inject/EmbeddedTestModule.kt
+++ b/photopicker/tests/src/com/android/photopicker/inject/EmbeddedTestModule.kt
@@ -32,6 +32,7 @@ import com.android.photopicker.core.database.DatabaseManagerTestImpl
import com.android.photopicker.core.embedded.EmbeddedLifecycle
import com.android.photopicker.core.embedded.EmbeddedViewModelFactory
import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.selection.GrantsAwareSelectionImpl
import com.android.photopicker.core.selection.Selection
@@ -96,6 +97,7 @@ abstract class EmbeddedTestModule {
@Background backgroundDispatcher: CoroutineDispatcher,
featureManager: Lazy<FeatureManager>,
configurationManager: Lazy<ConfigurationManager>,
+ bannerManager: Lazy<BannerManager>,
selection: Lazy<Selection<Media>>,
userMonitor: Lazy<UserMonitor>,
dataService: Lazy<DataService>,
@@ -105,6 +107,7 @@ abstract class EmbeddedTestModule {
EmbeddedViewModelFactory(
backgroundDispatcher,
configurationManager,
+ bannerManager,
dataService,
events,
featureManager,
@@ -150,6 +153,7 @@ abstract class EmbeddedTestModule {
scope,
dispatcher,
deviceConfigProxy,
+ generatePickerSessionId()
)
}
@@ -225,13 +229,15 @@ abstract class EmbeddedTestModule {
@Provides
fun createSelection(
@Background scope: CoroutineScope,
- configurationManager: ConfigurationManager
+ configurationManager: ConfigurationManager,
+ dataService: DataService
): Selection<Media> {
return when (determineSelectionStrategy(configurationManager.configuration.value)) {
SelectionStrategy.GRANTS_AWARE_SELECTION ->
GrantsAwareSelectionImpl(
scope = scope,
configuration = configurationManager.configuration,
+ preGrantedItemsCount = dataService.preGrantedMediaCount,
)
SelectionStrategy.DEFAULT ->
SelectionImpl(
diff --git a/photopicker/tests/src/com/android/photopicker/inject/PhotopickerTestModule.kt b/photopicker/tests/src/com/android/photopicker/inject/PhotopickerTestModule.kt
index ffab11830..1c128b056 100644
--- a/photopicker/tests/src/com/android/photopicker/inject/PhotopickerTestModule.kt
+++ b/photopicker/tests/src/com/android/photopicker/inject/PhotopickerTestModule.kt
@@ -32,6 +32,7 @@ import com.android.photopicker.core.database.DatabaseManagerTestImpl
import com.android.photopicker.core.embedded.EmbeddedLifecycle
import com.android.photopicker.core.embedded.EmbeddedViewModelFactory
import com.android.photopicker.core.events.Events
+import com.android.photopicker.core.events.generatePickerSessionId
import com.android.photopicker.core.features.FeatureManager
import com.android.photopicker.core.selection.GrantsAwareSelectionImpl
import com.android.photopicker.core.selection.Selection
@@ -96,6 +97,7 @@ abstract class PhotopickerTestModule {
@Background backgroundDispatcher: CoroutineDispatcher,
featureManager: Lazy<FeatureManager>,
configurationManager: Lazy<ConfigurationManager>,
+ bannerManager: Lazy<BannerManager>,
selection: Lazy<Selection<Media>>,
userMonitor: Lazy<UserMonitor>,
dataService: Lazy<DataService>,
@@ -105,6 +107,7 @@ abstract class PhotopickerTestModule {
EmbeddedViewModelFactory(
backgroundDispatcher,
configurationManager,
+ bannerManager,
dataService,
events,
featureManager,
@@ -150,6 +153,7 @@ abstract class PhotopickerTestModule {
scope,
dispatcher,
deviceConfigProxy,
+ generatePickerSessionId()
)
}
@@ -232,6 +236,7 @@ abstract class PhotopickerTestModule {
GrantsAwareSelectionImpl(
scope = scope,
configuration = configurationManager.configuration,
+ preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount
)
SelectionStrategy.DEFAULT ->
SelectionImpl(
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 46e07b134..a96497df4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -522,10 +522,10 @@
<string name="safety_protection_icon_label">Safety protection</string>
<!-- Transcode alert channel name. -->
- <string name="transcode_alert_channel">Native Transcode Alerts</string>
+ <string name="transcode_alert_channel">Transcoding Notifications</string>
<!-- Transcode progress channel name. -->
- <string name="transcode_progress_channel">Native Transcode Progress</string>
+ <string name="transcode_progress_channel">Transcoding Progress</string>
<!-- Dialog error message-->
<string name="dialog_error_message">Try again later. Your photos will be available once the issue is resolved.</string>
diff --git a/src/com/android/providers/media/MediaGrants.java b/src/com/android/providers/media/MediaGrants.java
index 61d4ab141..649380108 100644
--- a/src/com/android/providers/media/MediaGrants.java
+++ b/src/com/android/providers/media/MediaGrants.java
@@ -19,6 +19,7 @@ package com.android.providers.media;
import static com.android.providers.media.LocalUriMatcher.PICKER_ID;
import static com.android.providers.media.util.DatabaseUtils.replaceMatchAnyChar;
+import android.annotation.Nullable;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
@@ -156,10 +157,13 @@ public class MediaGrants {
* Returns the cursor for file data of items for which the passed package has READ_GRANTS.
*
* @param packageNames the package name that has access.
- * @param packageUserId the user_id of the package
+ * @param packageUserId the user_id of the package.
+ * @param mimeTypes the mimeTypes of items for which the grants needs to be returned.
+ * @param availableVolumes volumes that are available, grants for items only in these volumes
+ * should be considered.
*/
- Cursor getMediaGrantsForPackages(String[] packageNames, int packageUserId,
- String[] mimeTypes, String[] availableVolumes)
+ Cursor getMediaGrantsForPackages(@NonNull String[] packageNames, int packageUserId,
+ @Nullable String[] mimeTypes, @NonNull String[] availableVolumes)
throws IllegalArgumentException {
Objects.requireNonNull(packageNames);
return mExternalDatabase.runWithoutTransaction((db) -> {
@@ -178,7 +182,9 @@ public class MediaGrants {
.build());
return queryBuilder.query(db,
- new String[]{FILE_ID_COLUMN, PACKAGE_USER_ID_COLUMN}, null,
+ new String[]{FILE_ID_COLUMN,
+ String.format("%s.%s", MEDIA_GRANTS_TABLE, OWNER_PACKAGE_NAME_COLUMN),
+ PACKAGE_USER_ID_COLUMN}, null,
selectionArgs, null, null, null, null, null);
});
}
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index bafaa67f4..896f22ba7 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -363,9 +363,9 @@ import java.util.stream.Collectors;
/**
* Media content provider. See {@link android.provider.MediaStore} for details.
- * Separate databases are kept for each external storage card we see (using the
- * card's ID as an index). The content visible at content://media/external/...
- * changes with the card.
+ * A single database keep track of media files on external storage
+ * The content visible at content://media/external/... is a combined view of all media files on all
+ * available external storage devices
*/
public class MediaProvider extends ContentProvider {
/**
@@ -6877,16 +6877,7 @@ public class MediaProvider extends ContentProvider {
int userId;
final List<Uri> uris;
String[] packageNames;
- if (checkPermissionSelf(caller)) {
- final PackageManager pm = getContext().getPackageManager();
- final int packageUid = extras.getInt(Intent.EXTRA_UID);
- packageNames = pm.getPackagesForUid(packageUid);
- // Get the userId from packageUid as the initiator could be a cloned app, which
- // accesses Media via MP of its parent user and Binder's callingUid reflects
- // the latter.
- userId = uidToUserId(packageUid);
- uris = extras.getParcelableArrayList(MediaStore.EXTRA_URI_LIST);
- } else if (checkPermissionShell(caller)) {
+ if (checkPermissionShell(caller)) {
// If the caller is the shell, the accepted parameter is EXTRA_PACKAGE_NAME
// (as string).
if (!extras.containsKey(Intent.EXTRA_PACKAGE_NAME)) {
@@ -6899,10 +6890,19 @@ public class MediaProvider extends ContentProvider {
// Caller is always shell which may not have the desired userId. Hence, use
// UserId from the MediaProvider process itself.
userId = UserHandle.myUserId();
+ } else if (checkPermissionSelf(caller) || isCallerPhotoPicker()) {
+ final PackageManager pm = getContext().getPackageManager();
+ final int packageUid = extras.getInt(Intent.EXTRA_UID);
+ packageNames = pm.getPackagesForUid(packageUid);
+ // Get the userId from packageUid as the initiator could be a cloned app, which
+ // accesses Media via MP of its parent user and Binder's callingUid reflects
+ // the latter.
+ userId = uidToUserId(packageUid);
+ uris = extras.getParcelableArrayList(MediaStore.EXTRA_URI_LIST);
} else {
// All other callers are unauthorized.
throw new SecurityException(
- getSecurityExceptionMessage("read media grants"));
+ getSecurityExceptionMessage("revoke media grants"));
}
mMediaGrants.removeMediaGrantsForPackage(packageNames, uris, userId);
@@ -7162,7 +7162,21 @@ public class MediaProvider extends ContentProvider {
int userId;
final List<Uri> uris;
String packageName;
- if (checkPermissionSelf(caller)) {
+ if (checkPermissionShell(caller)) {
+ // If the caller is the shell, the accepted parameters are EXTRA_URI (as string)
+ // and EXTRA_PACKAGE_NAME (as string).
+ if (!extras.containsKey(MediaStore.EXTRA_URI)
+ && !extras.containsKey(Intent.EXTRA_PACKAGE_NAME)) {
+ throw new IllegalArgumentException(
+ "Missing required extras arguments: EXTRA_URI or" + " EXTRA_PACKAGE_NAME");
+ }
+ packageName = extras.getString(Intent.EXTRA_PACKAGE_NAME);
+ uris = List.of(Uri.parse(extras.getString(MediaStore.EXTRA_URI)));
+ // Caller is always shell which may not have the desired userId. Hence, use
+ // UserId from the MediaProvider process itself.
+ userId = UserHandle.myUserId();
+
+ } else if (checkPermissionSelf(caller) || isCallerPhotoPicker()) {
// If the caller is MediaProvider the accepted parameters are EXTRA_URI_LIST
// and EXTRA_UID.
if (!extras.containsKey(MediaStore.EXTRA_URI_LIST)
@@ -7189,19 +7203,6 @@ public class MediaProvider extends ContentProvider {
// accesses Media via MP of its parent user and Binder's callingUid reflects
// the latter.
userId = uidToUserId(packageUid);
- } else if (checkPermissionShell(caller)) {
- // If the caller is the shell, the accepted parameters are EXTRA_URI (as string)
- // and EXTRA_PACKAGE_NAME (as string).
- if (!extras.containsKey(MediaStore.EXTRA_URI)
- && !extras.containsKey(Intent.EXTRA_PACKAGE_NAME)) {
- throw new IllegalArgumentException(
- "Missing required extras arguments: EXTRA_URI or" + " EXTRA_PACKAGE_NAME");
- }
- packageName = extras.getString(Intent.EXTRA_PACKAGE_NAME);
- uris = List.of(Uri.parse(extras.getString(MediaStore.EXTRA_URI)));
- // Caller is always shell which may not have the desired userId. Hence, use
- // UserId from the MediaProvider process itself.
- userId = UserHandle.myUserId();
} else {
// All other callers are unauthorized.
diff --git a/src/com/android/providers/media/MediaService.java b/src/com/android/providers/media/MediaService.java
index 5d236925b..75a2016b9 100644
--- a/src/com/android/providers/media/MediaService.java
+++ b/src/com/android/providers/media/MediaService.java
@@ -160,14 +160,12 @@ public class MediaService extends JobIntentService {
}
private void onPublicVolumeRecovery() {
- new Thread(() -> {
- try (ContentProviderClient cpc = getContentResolver()
- .acquireContentProviderClient(MediaStore.AUTHORITY)) {
- ((MediaProvider) cpc.getLocalContentProvider()).recoverPublicVolumes();
- } catch (Exception e) {
- Log.e(TAG, "Exception while starting public volume recovery thread", e);
- }
- }).start();
+ try (ContentProviderClient cpc = getContentResolver()
+ .acquireContentProviderClient(MediaStore.AUTHORITY)) {
+ ((MediaProvider) cpc.getLocalContentProvider()).recoverPublicVolumes();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while starting public volume recovery thread", e);
+ }
}
public static void onScanVolume(Context context, MediaVolume volume, int reason)
diff --git a/src/com/android/providers/media/photopicker/PickerDataLayer.java b/src/com/android/providers/media/photopicker/PickerDataLayer.java
index eefd1bc04..6b8f4a779 100644
--- a/src/com/android/providers/media/photopicker/PickerDataLayer.java
+++ b/src/com/android/providers/media/photopicker/PickerDataLayer.java
@@ -177,7 +177,7 @@ public class PickerDataLayer {
getWorkManager(mContext),
SyncTrackerRegistry.getLocalSyncTracker(),
IMMEDIATE_LOCAL_SYNC_WORK_NAME);
- Log.i(TAG, "Local sync is complete");
+ Log.i(TAG, "Grants sync and Local sync is complete");
// Wait for on cloud sync with timeout
if (!isLocalOnly) {
@@ -436,7 +436,7 @@ public class PickerDataLayer {
+ " Should sync with local provider only: "
+ syncRequestExtras.shouldSyncLocalOnlyData());
- mSyncManager.syncMediaImmediately(syncRequestExtras.shouldSyncLocalOnlyData());
+ mSyncManager.syncMediaImmediately(syncRequestExtras);
} else {
// Sync album media data
Log.i(TAG, String.format("Init data request for album content of: %s"
diff --git a/src/com/android/providers/media/photopicker/PickerSyncController.java b/src/com/android/providers/media/photopicker/PickerSyncController.java
index 0a32618ff..137a7d4cf 100644
--- a/src/com/android/providers/media/photopicker/PickerSyncController.java
+++ b/src/com/android/providers/media/photopicker/PickerSyncController.java
@@ -17,16 +17,18 @@
package com.android.providers.media.photopicker;
import static android.content.ContentResolver.EXTRA_HONORED_ARGS;
-import static android.provider.CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_NAME;
import static android.provider.CloudMediaProviderContract.EXTRA_ALBUM_ID;
import static android.provider.CloudMediaProviderContract.EXTRA_MEDIA_COLLECTION_ID;
import static android.provider.CloudMediaProviderContract.EXTRA_PAGE_SIZE;
import static android.provider.CloudMediaProviderContract.EXTRA_PAGE_TOKEN;
import static android.provider.CloudMediaProviderContract.EXTRA_SYNC_GENERATION;
+import static android.provider.CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_CONFIGURATION_INTENT;
+import static android.provider.CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_NAME;
import static android.provider.CloudMediaProviderContract.MediaCollectionInfo.LAST_MEDIA_SYNC_GENERATION;
import static android.provider.CloudMediaProviderContract.MediaCollectionInfo.MEDIA_COLLECTION_ID;
-import static android.provider.CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_CONFIGURATION_INTENT;
+import static android.provider.MediaStore.AUTHORITY;
import static android.provider.MediaStore.MY_UID;
+import static android.provider.MediaStore.PER_USER_RANGE;
import static com.android.providers.media.PickerUriResolver.INIT_PATH;
import static com.android.providers.media.PickerUriResolver.PICKER_INTERNAL_URI;
@@ -40,15 +42,19 @@ import static com.android.providers.media.photopicker.NotificationContentObserve
import static com.android.providers.media.photopicker.util.CursorUtils.getCursorString;
import android.annotation.IntDef;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.database.Cursor;
+import android.database.SQLException;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.Trace;
import android.os.storage.StorageManager;
import android.provider.CloudMediaProvider;
@@ -75,6 +81,7 @@ import com.android.providers.media.photopicker.sync.PickerSyncLockManager;
import com.android.providers.media.photopicker.util.CloudProviderUtils;
import com.android.providers.media.photopicker.util.exceptions.RequestObsoleteException;
import com.android.providers.media.photopicker.util.exceptions.UnableToAcquireLockException;
+import com.android.providers.media.photopicker.util.exceptions.WorkCancelledException;
import com.android.providers.media.photopicker.v2.PickerNotificationSender;
import com.android.providers.media.photopicker.v2.model.ProviderCollectionInfo;
@@ -161,6 +168,18 @@ public class PickerSyncController {
private static PickerSyncController sInstance;
/**
+ * This URI path when used in a MediaProvider.query() method redirects the call to media_grants
+ * table present in the external database.
+ */
+ private static final String MEDIA_GRANTS_URI_PATH = "content://media/media_grants";
+
+ /**
+ * Extra that can be passed in the grants sync query to ensure only the data corresponding to
+ * the required mimeTypes is synced.
+ */
+ public static final String EXTRA_MEDIA_GRANTS_MIME_TYPES = "media_grant_mime_type_selection";
+
+ /**
* Initialize {@link PickerSyncController} object.{@link PickerSyncController} should only be
* initialized from {@link com.android.providers.media.MediaProvider#onCreate}.
*
@@ -859,7 +878,7 @@ public class PickerSyncController {
executeSyncAddAlbum(
authority, isLocal, albumId, queryArgs, instanceId, cancellationSignal);
}
- } catch (RuntimeException | UnableToAcquireLockException e) {
+ } catch (RuntimeException | UnableToAcquireLockException | WorkCancelledException e) {
// Unlike syncAllMediaFromProvider, we don't retry here because any errors would have
// occurred in fetching all the album_media since incremental sync is not supported.
// A full sync is therefore unlikely to resolve any issue
@@ -965,6 +984,10 @@ public class PickerSyncController {
default:
throw new IllegalArgumentException("Unexpected sync type: " + params.syncType);
}
+ } catch (WorkCancelledException e) {
+ // Do not reset picker DB here so that the sync operation resumes the next time sync is
+ // triggered.
+ Log.e(TAG, "Failed to sync all media because the sync was cancelled.", e);
} catch (RequestObsoleteException e) {
Log.e(TAG, "Failed to sync all media because authority has changed.", e);
try {
@@ -1083,7 +1106,7 @@ public class PickerSyncController {
Bundle queryArgs,
InstanceId instanceId,
@Nullable CancellationSignal cancellationSignal)
- throws RequestObsoleteException, UnableToAcquireLockException {
+ throws RequestObsoleteException, UnableToAcquireLockException, WorkCancelledException {
final Uri uri = getMediaUri(authority);
final List<String> expectedHonoredArgs = new ArrayList<>();
if (isIncrementalSync) {
@@ -1133,7 +1156,7 @@ public class PickerSyncController {
Bundle queryArgs,
InstanceId instanceId,
@Nullable CancellationSignal cancellationSignal)
- throws RequestObsoleteException, UnableToAcquireLockException {
+ throws RequestObsoleteException, UnableToAcquireLockException, WorkCancelledException {
final Uri uri = getMediaUri(authority);
Log.i(TAG, "Executing SyncAddAlbum. "
@@ -1184,7 +1207,7 @@ public class PickerSyncController {
Bundle queryArgs,
InstanceId instanceId,
@Nullable CancellationSignal cancellationSignal)
- throws RequestObsoleteException, UnableToAcquireLockException {
+ throws RequestObsoleteException, UnableToAcquireLockException, WorkCancelledException {
final Uri uri = getDeletedMediaUri(authority);
Log.i(TAG, "Executing SyncRemove. isLocal: " + isLocal + ". authority: " + authority);
@@ -1589,7 +1612,7 @@ public class PickerSyncController {
String authority,
Boolean isLocal,
@Nullable CancellationSignal cancellationSignal)
- throws RequestObsoleteException, UnableToAcquireLockException {
+ throws RequestObsoleteException, UnableToAcquireLockException, WorkCancelledException {
return executePagedSync(
uri,
expectedMediaCollectionId,
@@ -1634,7 +1657,7 @@ public class PickerSyncController {
Boolean isLocal,
@Nullable String albumId,
@Nullable CancellationSignal cancellationSignal)
- throws RequestObsoleteException, UnableToAcquireLockException {
+ throws RequestObsoleteException, UnableToAcquireLockException, WorkCancelledException {
Trace.beginSection(traceSectionName("executePagedSync"));
try {
@@ -1655,7 +1678,7 @@ public class PickerSyncController {
// At the top of each loop check to see if we've received a CancellationSignal
// to stop the paged sync.
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- throw new RequestObsoleteException(
+ throw new WorkCancelledException(
"Aborting sync: cancellationSignal was received");
}
@@ -2068,4 +2091,98 @@ public class PickerSyncController {
}
}
}
+
+ /**
+ * Executes a sync for grants from the external database to the picker database.
+ *
+ * This should only be called when the picker is in MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP
+ * action. It requires a valid packageUid and mimeTypes with which the picker was invoked to
+ * ensure that the sync only happens for the items that:
+ * <li>match the mimeTypes</li>
+ * <li>are granted to the package and userId corresponding to the provided packageUid</li>
+ *
+ * It fetches the rows from media_grants table in the external.db that matches the criteria and
+ * inserts them in the media_grants table in picker.db
+ */
+ public void executeGrantsSync(
+ boolean shouldSyncGrants, int packageUid,
+ String[] mimeTypes) {
+ // empty the grants table.
+ executeClearAllGrants(packageUid);
+
+ // sync all grants into the table
+ if (shouldSyncGrants) {
+ final ContentResolver resolver = mContext.getContentResolver();
+ try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
+ assert client != null;
+ final Bundle extras = new Bundle();
+ extras.putInt(Intent.EXTRA_UID, packageUid);
+ extras.putStringArray(EXTRA_MEDIA_GRANTS_MIME_TYPES, mimeTypes);
+ try (Cursor c = client.query(Uri.parse(MEDIA_GRANTS_URI_PATH),
+ /* projection= */ null,
+ /* queryArgs= */ extras,
+ null)) {
+ Trace.beginSection(traceSectionName(
+ "executeGrantsSync", /* isLocal */ true));
+ try (PickerDbFacade.DbWriteOperation operation =
+ mDbFacade.beginInsertGrantsOperation()) {
+ int grantsInsertedCount = operation.execute(c);
+ operation.setSuccess();
+ Log.i(TAG, "Successfully executed grants sync operation operation."
+ + " Result count: " + grantsInsertedCount);
+ } finally {
+ Trace.endSection();
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote exception received while fetching grants. " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Before a sync for grants is initiated, this method is used to clear any stale grants that
+ * exists in the database.
+ *
+ * This should only be called when the picker is in MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP
+ * action. It requires a valid packageUid with which the picker was invoked to
+ * ensure that all the rows that represents the items granted to the package and userId
+ * corresponding to the provided packageUid are cleared from the media_grants table in picker.db
+ */
+ private void executeClearAllGrants(int packageUid) {
+ Trace.beginSection(traceSectionName("executeClearAllGrants", /* isLocal */ true));
+ int userId = uidToUserId(packageUid);
+ String[] packageNames = getPackageNameFromUid(mContext, packageUid);
+
+ try (PickerDbFacade.DbWriteOperation operation =
+ mDbFacade.beginClearGrantsOperation(packageNames, userId)) {
+ final int clearedGrantsCount = operation.execute(/* cursor */ null);
+ operation.setSuccess();
+
+ Log.i(TAG, "Successfully executed clear grants operation."
+ + " Result count: " + clearedGrantsCount);
+ } catch (SQLException e) {
+ Log.e(TAG, "Unable to clear grants for this session: " + e.getMessage());
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ /**
+ * Returns an Array of packageNames corresponding to the input package uid.
+ */
+ public static String[] getPackageNameFromUid(Context context, int callingPackageUid) {
+ final PackageManager pm = context.getPackageManager();
+ return pm.getPackagesForUid(callingPackageUid);
+ }
+
+ /**
+ * Generates and returns userId from the input package uid.
+ */
+ public static int uidToUserId(int uid) {
+ // Get the userId from packageUid as the initiator could be a cloned app, which
+ // accesses Media via MP of its parent user and Binder's callingUid reflects
+ // the latter.
+ return uid / PER_USER_RANGE;
+ }
}
diff --git a/src/com/android/providers/media/photopicker/data/PickerDatabaseHelper.java b/src/com/android/providers/media/photopicker/data/PickerDatabaseHelper.java
index 141807c46..ddeb1de10 100644
--- a/src/com/android/providers/media/photopicker/data/PickerDatabaseHelper.java
+++ b/src/com/android/providers/media/photopicker/data/PickerDatabaseHelper.java
@@ -40,8 +40,13 @@ public class PickerDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "PickerDatabaseHelper";
public static final String PICKER_DATABASE_NAME = "picker.db";
+
private static final int VERSION_U = 11;
- public static final int VERSION_LATEST = VERSION_U;
+ @VisibleForTesting
+ public static final int VERSION_INTRODUCING_MEDIA_GRANTS_TABLE = 12;
+ @VisibleForTesting
+ public static final int VERSION_INTRODUCING_DE_SELECTIONS_TABLE = 13;
+ public static final int VERSION_LATEST = VERSION_INTRODUCING_DE_SELECTIONS_TABLE;
final Context mContext;
final String mName;
@@ -71,7 +76,24 @@ public class PickerDatabaseHelper extends SQLiteOpenHelper {
public void onUpgrade(final SQLiteDatabase db, final int oldV, final int newV) {
Log.v(TAG, "onUpgrade() for " + mName + " from " + oldV + " to " + newV);
- resetData(db);
+ // Minimum compatible version with database migrations is VERSION_U.
+ // Any database lower than VERSION_U needs to be reset with latest schema.
+ if (oldV < VERSION_U) {
+ resetData(db);
+ return;
+ }
+
+ // If the version is at least VERSION_U (see block above), then the
+ // database schema is fine, and all that's required is to add the
+ // new media_grants table.
+ if (oldV < VERSION_INTRODUCING_MEDIA_GRANTS_TABLE) {
+ createMediaGrantsTable(db);
+ }
+ if (oldV < VERSION_INTRODUCING_DE_SELECTIONS_TABLE) {
+ // Create de_selection table in picker.db if we are upgrading from a version where
+ // de_selection table did not exist.
+ createDeselectionTable(db);
+ }
}
@Override
@@ -149,6 +171,30 @@ public class PickerDatabaseHelper extends SQLiteOpenHelper {
+ "OR (local_id IS NOT NULL AND cloud_id IS NULL)),"
+ "UNIQUE(local_id, album_id),"
+ "UNIQUE(cloud_id, album_id))");
+ createMediaGrantsTable(db);
+ createDeselectionTable(db);
+ }
+
+ private static void createMediaGrantsTable(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS media_grants");
+ db.execSQL("CREATE TABLE media_grants ("
+ + "owner_package_name TEXT,"
+ + "file_id INTEGER,"
+ + "package_user_id INTEGER,"
+ + "UNIQUE(owner_package_name, file_id, package_user_id)"
+ + " ON CONFLICT IGNORE "
+ + ")");
+ }
+
+ private static void createDeselectionTable(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS de_selections");
+ db.execSQL("CREATE TABLE de_selections ("
+ + "owner_package_name TEXT,"
+ + "file_id INTEGER,"
+ + "package_user_id INTEGER,"
+ + "UNIQUE(owner_package_name, file_id, package_user_id)"
+ + " ON CONFLICT IGNORE "
+ + ")");
}
private static void createLatestIndexes(SQLiteDatabase db) {
diff --git a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
index 4e248c869..fbb32ef42 100644
--- a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
+++ b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
@@ -22,6 +22,9 @@ import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_
import static android.provider.CloudMediaProviderContract.MediaColumns;
import static android.provider.MediaStore.PickerMediaColumns;
+import static com.android.providers.media.MediaGrants.FILE_ID_COLUMN;
+import static com.android.providers.media.MediaGrants.OWNER_PACKAGE_NAME_COLUMN;
+import static com.android.providers.media.MediaGrants.PACKAGE_USER_ID_COLUMN;
import static com.android.providers.media.photopicker.PickerSyncController.PAGE_SIZE;
import static com.android.providers.media.photopicker.util.CursorUtils.getCursorLong;
import static com.android.providers.media.photopicker.util.CursorUtils.getCursorString;
@@ -108,6 +111,8 @@ public class PickerDbFacade {
private static final String TABLE_ALBUM_MEDIA = "album_media";
+ private static final String TABLE_GRANTS = "media_grants";
+
public static final String KEY_ID = "_id";
public static final String KEY_LOCAL_ID = "local_id";
public static final String KEY_CLOUD_ID = "cloud_id";
@@ -127,6 +132,8 @@ public class PickerDbFacade {
public static final String KEY_WIDTH = "width";
@VisibleForTesting
public static final String KEY_ORIENTATION = "orientation";
+ public static final String EXTRA_OWNER_PACKAGE_NAMES = "owner_package_names";
+ public static final String EXTRA_PACKAGE_USER_ID = "package_user_id";
private static final String WHERE_ID = KEY_ID + " = ?";
private static final String WHERE_LOCAL_ID = KEY_LOCAL_ID + " = ?";
@@ -275,6 +282,20 @@ public class PickerDbFacade {
}
/**
+ * Returns {@link DbWriteOperation} that can be used to insert grants into the database.
+ */
+ public DbWriteOperation beginInsertGrantsOperation() {
+ return new InsertGrantsOperation(mDatabase, /* isLocal */ true);
+ }
+
+ /**
+ * Returns {@link DbWriteOperation} that can be used to clear all grants from the database.
+ */
+ public DbWriteOperation beginClearGrantsOperation(String[] packageNames, int userId) {
+ return new ClearGrantsOperation(mDatabase, /* isLocal */ true, packageNames, userId);
+ }
+
+ /**
* Returns {@link DbWriteOperation} to add album_media belonging to {@code authority}
* into the picker db.
*/
@@ -431,6 +452,85 @@ public class PickerDbFacade {
}
/**
+ * Database operation to insert the grants synced.
+ */
+ public static class InsertGrantsOperation extends DbWriteOperation {
+
+ public InsertGrantsOperation(SQLiteDatabase database, boolean isLocal) {
+ super(database, isLocal);
+ }
+
+ @Override
+ int executeInternal(@Nullable Cursor cursor) {
+ int numberOfGrantsInserted = 0;
+
+ // fetch ids from thw cursor.
+ if (cursor == null) {
+ Log.d(TAG, "No item grants to sync");
+ return numberOfGrantsInserted;
+ }
+
+ ContentValues values = new ContentValues();
+ SQLiteQueryBuilder qb = createGrantsQueryBuilder();
+ if (cursor.moveToFirst()) {
+ do {
+ Integer id = cursor.getInt(cursor.getColumnIndexOrThrow(FILE_ID_COLUMN));
+ String packageName = cursor.getString(cursor.getColumnIndexOrThrow(
+ OWNER_PACKAGE_NAME_COLUMN));
+ Integer userId = cursor.getInt(
+ cursor.getColumnIndexOrThrow(PACKAGE_USER_ID_COLUMN));
+
+ // insert the grant into the grants table.
+ values.clear();
+ values.put(FILE_ID_COLUMN, id);
+ values.put(OWNER_PACKAGE_NAME_COLUMN, packageName);
+ values.put(PACKAGE_USER_ID_COLUMN, userId);
+ try {
+ qb.insert(getDatabase(), values);
+ numberOfGrantsInserted++;
+ } catch (SQLiteConstraintException ignored) {
+ Log.e(TAG, "Duplicate row insertion encountered for table media_grants."
+ + ignored);
+ // If we hit a constraint exception it means this row is already in media,
+ // so nothing to do here.
+ }
+ } while (cursor.moveToNext());
+ }
+ Log.d(TAG, numberOfGrantsInserted + " grants synced.");
+ return numberOfGrantsInserted;
+ }
+ }
+
+ /**
+ * Represents an update to the picker database where all grants needs to be cleared.
+ *
+ * This needs to be happen before every sync.
+ */
+ public static class ClearGrantsOperation extends DbWriteOperation {
+
+ private final String[] mPackageNames;
+ private final int mUserId;
+
+ public ClearGrantsOperation(SQLiteDatabase database, boolean isLocal,
+ @NonNull String[] packageNames,
+ int userId) {
+ super(database, isLocal);
+ mPackageNames = packageNames;
+ mUserId = userId;
+ }
+
+ @Override
+ int executeInternal(@Nullable Cursor cursor) {
+ // Delete everything from the grants table for the calling package.
+ SQLiteQueryBuilder qb = createGrantsQueryBuilder();
+ Objects.requireNonNull(mPackageNames);
+ addWhereClausesForMediaGrantsTable(qb, mUserId, mPackageNames);
+ Log.d(TAG, "Clearing all picker database grants for calling package.");
+ return qb.delete(getDatabase(), /* selection */ null, /* selectionArgs */ null);
+ }
+ }
+
+ /**
* Represents an atomic media update operation to the picker database.
*
* <p>This class is not thread-safe and is meant to be used within a single thread only.
@@ -1527,6 +1627,34 @@ public class PickerDbFacade {
return qb;
}
+ private static SQLiteQueryBuilder createGrantsQueryBuilder() {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(TABLE_GRANTS);
+ return qb;
+ }
+
+ /**
+ * Appends where clause for package and user id selection to the input query builder.
+ */
+ public static void addWhereClausesForMediaGrantsTable(SQLiteQueryBuilder qb, int userId,
+ @NonNull String[] packageNames) {
+ // Add where clause for userId selection.
+ qb.appendWhereStandalone(
+ String.format("%s.%s = %s", TABLE_GRANTS, PACKAGE_USER_ID_COLUMN,
+ String.valueOf(userId)));
+
+ // Add where clause for package name selection.
+ Objects.requireNonNull(packageNames);
+ StringBuilder packageSelection = new StringBuilder("(");
+ for (int itr = 0; itr < packageNames.length; itr++) {
+ packageSelection.append("\"").append(packageNames[itr]).append("\",");
+ }
+ packageSelection.deleteCharAt(packageSelection.length() - 1);
+ packageSelection.append(")");
+ qb.appendWhereStandalone(OWNER_PACKAGE_NAME_COLUMN + " IN "
+ + packageSelection.toString());
+ }
+
private static SQLiteQueryBuilder createAlbumMediaQueryBuilder(boolean isLocal) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(TABLE_ALBUM_MEDIA);
diff --git a/src/com/android/providers/media/photopicker/data/PickerSyncRequestExtras.java b/src/com/android/providers/media/photopicker/data/PickerSyncRequestExtras.java
index f479d510e..49e2855e1 100644
--- a/src/com/android/providers/media/photopicker/data/PickerSyncRequestExtras.java
+++ b/src/com/android/providers/media/photopicker/data/PickerSyncRequestExtras.java
@@ -17,7 +17,9 @@
package com.android.providers.media.photopicker.data;
import static com.android.providers.media.photopicker.data.CloudProviderQueryExtras.isMergedAlbum;
+import static com.android.providers.media.photopicker.sync.PickerSyncManager.EXTRA_MIME_TYPES;
+import android.content.Intent;
import android.os.Bundle;
import android.provider.MediaStore;
import android.text.TextUtils;
@@ -25,23 +27,35 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.util.ArrayList;
import java.util.Objects;
/**
* Encapsulate all picker sync request arguments related logic.
*/
public class PickerSyncRequestExtras {
+ private static final String EXTRA_INTENT_ACTION = "intent_action";
@Nullable
private final String mAlbumId;
@Nullable
private final String mAlbumAuthority;
private final boolean mInitLocalOnlyData;
+ private final int mCallingPackageUid;
+ private final boolean mShouldSyncGrants;
+ private final String[] mMimeTypes;
+
public PickerSyncRequestExtras(@Nullable String albumId,
@Nullable String albumAuthority,
- boolean initLocalOnlyData) {
+ boolean initLocalOnlyData,
+ int callingPackageUid,
+ boolean shouldSyncGrants,
+ @Nullable String[] mimeTypes) {
mAlbumId = albumId;
mAlbumAuthority = albumAuthority;
mInitLocalOnlyData = initLocalOnlyData;
+ mCallingPackageUid = callingPackageUid;
+ mShouldSyncGrants = shouldSyncGrants;
+ mMimeTypes = mimeTypes;
}
/**
@@ -54,7 +68,22 @@ public class PickerSyncRequestExtras {
final String albumAuthority = extras.getString(MediaStore.EXTRA_ALBUM_AUTHORITY);
final boolean initLocalOnlyData =
extras.getBoolean(MediaStore.EXTRA_LOCAL_ONLY);
- return new PickerSyncRequestExtras(albumId, albumAuthority, initLocalOnlyData);
+ final int callingPackageUid = extras.getInt(Intent.EXTRA_UID, /* default value */ -1);
+ // Grants should only be synced when the intent action is
+ // MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP.
+ final String intentAction = extras.getString(EXTRA_INTENT_ACTION);
+ final boolean shouldSyncGrants = intentAction != null
+ && intentAction.equals(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP);
+ final ArrayList<String> mimeTypesList = extras.getStringArrayList(EXTRA_MIME_TYPES);
+ final String[] mimeTypes;
+ if (mimeTypesList != null) {
+ mimeTypes = mimeTypesList.stream().toArray(String[]::new);
+ } else {
+ mimeTypes = null;
+ }
+
+ return new PickerSyncRequestExtras(albumId, albumAuthority, initLocalOnlyData,
+ callingPackageUid, shouldSyncGrants, mimeTypes);
}
/**
@@ -93,4 +122,25 @@ public class PickerSyncRequestExtras {
public String getAlbumAuthority() {
return mAlbumAuthority;
}
+
+ /**
+ * Return calling package uid for current picker session.
+ */
+ public int getCallingPackageUid() {
+ return mCallingPackageUid;
+ }
+
+ /**
+ * Returns true if grants should be synced, false otherwise.
+ */
+ public boolean isShouldSyncGrants() {
+ return mShouldSyncGrants;
+ }
+
+ /**
+ * Returns mimeTypes that can be used as a filtering parameter for syncs.
+ */
+ public String[] getMimeTypes() {
+ return mMimeTypes == null ? new String[]{} : mMimeTypes;
+ }
}
diff --git a/src/com/android/providers/media/photopicker/sync/ImmediateGrantsSyncWorker.java b/src/com/android/providers/media/photopicker/sync/ImmediateGrantsSyncWorker.java
new file mode 100644
index 000000000..021a7907f
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/sync/ImmediateGrantsSyncWorker.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.photopicker.sync;
+
+
+import static com.android.providers.media.photopicker.sync.PickerSyncManager.EXTRA_MIME_TYPES;
+import static com.android.providers.media.photopicker.sync.PickerSyncManager.SHOULD_SYNC_GRANTS;
+import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_MEDIA_GRANTS;
+import static com.android.providers.media.photopicker.sync.SyncTrackerRegistry.markSyncAsComplete;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.work.ForegroundInfo;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
+
+import com.android.providers.media.photopicker.PickerSyncController;
+import com.android.providers.media.photopicker.util.exceptions.RequestObsoleteException;
+
+/**
+ * This is a {@link Worker} class responsible for syncing with the correct sync
+ * source[SYNC_MEDIA_GRANTS]
+ */
+public class ImmediateGrantsSyncWorker extends Worker {
+ private static final String TAG = "ISyncGrantsWorker";
+ private final Context mContext;
+
+ /**
+ * Creates an instance of the {@link Worker}.
+ *
+ * @param context the application {@link Context}
+ * @param workerParams the set of {@link WorkerParameters}
+ */
+ public ImmediateGrantsSyncWorker(@NonNull Context context,
+ @NonNull WorkerParameters workerParams) {
+ super(context, workerParams);
+ mContext = context;
+ }
+
+ @NonNull
+ @Override
+ public Result doWork() {
+ // Do not allow endless re-runs of this worker, if this isn't the original run,
+ // just succeed and wait until the next scheduled run.
+ if (getRunAttemptCount() > 0) {
+ Log.w(TAG, "Worker retry was detected, ending this run in failure.");
+ return Result.failure();
+ }
+
+ Log.i(TAG, "Starting immediate picker grants sync from external database.");
+
+ try {
+ final int callingPackageUid = getInputData().getInt(Intent.EXTRA_UID, -1);
+ final boolean shouldSyncGrants = getInputData().getBoolean(SHOULD_SYNC_GRANTS, false);
+ final String[] mimeTypes = getInputData().getStringArray(EXTRA_MIME_TYPES);
+ checkIsWorkerStopped();
+ if (callingPackageUid != -1) {
+ PickerSyncController.getInstanceOrThrow().executeGrantsSync(
+ shouldSyncGrants,
+ callingPackageUid,
+ mimeTypes);
+ Log.i(TAG, "Completed immediate picker grants sync from external database. ");
+ } else {
+ // Having package uid is a must to execute sync for grants.
+ return Result.failure();
+ }
+ return Result.success();
+ } catch (IllegalStateException | RequestObsoleteException e) {
+ Log.i(TAG, "Could not complete immediate sync for grants");
+ return Result.failure();
+ } finally {
+ // Mark all pending syncs as finished.
+ markSyncAsComplete(SYNC_MEDIA_GRANTS, getId());
+ }
+ }
+
+ private void checkIsWorkerStopped() throws RequestObsoleteException {
+ if (isStopped()) {
+ throw new RequestObsoleteException("Work is stopped " + getId());
+ }
+ }
+
+ @Override
+ @NonNull
+ public ForegroundInfo getForegroundInfo() {
+ return PickerSyncNotificationHelper.getForegroundInfo(mContext);
+ }
+
+ @Override
+ public void onStopped() {
+ Log.w(TAG, "Worker is stopped. Clearing all pending futures. It's possible that the sync "
+ + "still finishes running if it has started already.");
+ // Send CancellationSignal to any running tasks.
+ markSyncAsComplete(SYNC_MEDIA_GRANTS, getId());
+ }
+}
diff --git a/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java b/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java
index bce961f62..61d45d38b 100644
--- a/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java
+++ b/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java
@@ -24,6 +24,7 @@ import static com.android.providers.media.photopicker.sync.SyncTrackerRegistry.t
import static java.util.Objects.requireNonNull;
import android.content.Context;
+import android.content.Intent;
import android.util.Log;
import androidx.annotation.IntDef;
@@ -41,6 +42,7 @@ import androidx.work.Worker;
import com.android.modules.utils.BackgroundThread;
import com.android.providers.media.ConfigStore;
+import com.android.providers.media.photopicker.data.PickerSyncRequestExtras;
import org.jetbrains.annotations.NotNull;
@@ -67,8 +69,9 @@ public class PickerSyncManager {
public static final int SYNC_LOCAL_ONLY = 1;
public static final int SYNC_CLOUD_ONLY = 2;
public static final int SYNC_LOCAL_AND_CLOUD = 3;
+ public static final int SYNC_MEDIA_GRANTS = 4;
- @IntDef(value = { SYNC_LOCAL_ONLY, SYNC_CLOUD_ONLY, SYNC_LOCAL_AND_CLOUD })
+ @IntDef(value = { SYNC_LOCAL_ONLY, SYNC_CLOUD_ONLY, SYNC_LOCAL_AND_CLOUD, SYNC_MEDIA_GRANTS })
@Retention(RetentionPolicy.SOURCE)
public @interface SyncSource {}
@@ -96,6 +99,9 @@ public class PickerSyncManager {
public static final String IMMEDIATE_ALBUM_SYNC_WORK_NAME;
public static final String PERIODIC_ALBUM_RESET_WORK_NAME;
private static final String ENDLESS_WORK_NAME;
+ public static final String IMMEDIATE_GRANTS_SYNC_WORK_NAME;
+ public static final String SHOULD_SYNC_GRANTS;
+ public static final String EXTRA_MIME_TYPES;
static {
final String syncPeriodicPrefix = "SYNC_MEDIA_PERIODIC_";
@@ -104,15 +110,19 @@ public class PickerSyncManager {
final String syncAllSuffix = "ALL";
final String syncLocalSuffix = "LOCAL";
final String syncCloudSuffix = "CLOUD";
+ final String syncGrantsSuffix = "GRANTS";
PERIODIC_ALBUM_RESET_WORK_NAME = "RESET_ALBUM_MEDIA_PERIODIC";
PERIODIC_SYNC_WORK_NAME = syncPeriodicPrefix + syncAllSuffix;
PROACTIVE_LOCAL_SYNC_WORK_NAME = syncProactivePrefix + syncLocalSuffix;
PROACTIVE_SYNC_WORK_NAME = syncProactivePrefix + syncAllSuffix;
+ IMMEDIATE_GRANTS_SYNC_WORK_NAME = syncImmediatePrefix + syncGrantsSuffix;
IMMEDIATE_LOCAL_SYNC_WORK_NAME = syncImmediatePrefix + syncLocalSuffix;
IMMEDIATE_CLOUD_SYNC_WORK_NAME = syncImmediatePrefix + syncCloudSuffix;
IMMEDIATE_ALBUM_SYNC_WORK_NAME = "SYNC_ALBUM_MEDIA_IMMEDIATE";
ENDLESS_WORK_NAME = "ENDLESS_WORK";
+ SHOULD_SYNC_GRANTS = "SHOULD_SYNC_GRANTS";
+ EXTRA_MIME_TYPES = "mime_types";
}
private final WorkManager mWorkManager;
@@ -246,27 +256,68 @@ public class PickerSyncManager {
final OneTimeWorkRequest syncRequest = getOneTimeProactiveSyncRequest(inputData);
// Don't wait for the sync operation to enqueue so that Picker sync enqueue
- // requests in
- // order to avoid adding latency to critical MP code paths.
-
- mWorkManager.enqueueUniqueWork(workName, ExistingWorkPolicy.REPLACE, syncRequest);
+ // requests in order to avoid adding latency to critical MP code paths.
+ mWorkManager.enqueueUniqueWork(workName, ExistingWorkPolicy.KEEP, syncRequest);
}
/**
* Use this method for reactive syncs which are user triggered.
*
- * @param shouldSyncLocalOnlyData if true indicates that the sync should only be triggered with
- * the local provider. Otherwise, sync will be triggered for both
- * local and cloud provider.
+ * @param pickerSyncRequestExtras extras used to figure out which all syncs to trigger.
*/
- public void syncMediaImmediately(boolean shouldSyncLocalOnlyData) {
+ public void syncMediaImmediately(PickerSyncRequestExtras pickerSyncRequestExtras) {
+
+ if (mConfigStore.isModernPickerEnabled()) {
+ // sync for grants is only required for the modern picker, the java picker uses
+ // MediaStore to directly fetch the grants for all purposes of selection.
+ syncGrantsImmediately(
+ IMMEDIATE_GRANTS_SYNC_WORK_NAME,
+ pickerSyncRequestExtras.getCallingPackageUid(),
+ pickerSyncRequestExtras.isShouldSyncGrants(),
+ pickerSyncRequestExtras.getMimeTypes());
+ }
+
syncMediaImmediately(PickerSyncManager.SYNC_LOCAL_ONLY, IMMEDIATE_LOCAL_SYNC_WORK_NAME);
- if (!shouldSyncLocalOnlyData) {
+ if (!pickerSyncRequestExtras.shouldSyncLocalOnlyData()) {
syncMediaImmediately(PickerSyncManager.SYNC_CLOUD_ONLY, IMMEDIATE_CLOUD_SYNC_WORK_NAME);
}
}
/**
+ * Use this method for reactive syncs for grants from the external database.
+ */
+ private void syncGrantsImmediately(@NonNull String workName, int callingPackageUid,
+ boolean shouldSyncGrants, String[] mimeTypes) {
+ final Data inputData = new Data(
+ Map.of(
+ Intent.EXTRA_UID, callingPackageUid,
+ SHOULD_SYNC_GRANTS, shouldSyncGrants,
+ EXTRA_MIME_TYPES, mimeTypes
+ )
+ );
+
+ final OneTimeWorkRequest syncRequestForGrants =
+ buildOneTimeWorkerRequest(ImmediateGrantsSyncWorker.class, inputData);
+
+ // Track the new sync request(s)
+ trackNewSyncRequests(PickerSyncManager.SYNC_MEDIA_GRANTS, syncRequestForGrants.getId());
+
+ // Enqueue grants sync request
+ try {
+ final Operation enqueueOperation = mWorkManager
+ .enqueueUniqueWork(workName, ExistingWorkPolicy.APPEND_OR_REPLACE,
+ syncRequestForGrants);
+
+ // Check that the request has been successfully enqueued.
+ enqueueOperation.getResult().get();
+ } catch (Exception e) {
+ Log.e(TAG, "Could not enqueue expedited picker grants sync request", e);
+ markSyncAsComplete(PickerSyncManager.SYNC_MEDIA_GRANTS,
+ syncRequestForGrants.getId());
+ }
+ }
+
+ /**
* Use this method for reactive syncs with either, local and cloud providers, or both.
*/
private void syncMediaImmediately(@SyncSource int syncSource, @NonNull String workName) {
diff --git a/src/com/android/providers/media/photopicker/sync/SyncTrackerRegistry.java b/src/com/android/providers/media/photopicker/sync/SyncTrackerRegistry.java
index 5a5f6c90f..3eaa43504 100644
--- a/src/com/android/providers/media/photopicker/sync/SyncTrackerRegistry.java
+++ b/src/com/android/providers/media/photopicker/sync/SyncTrackerRegistry.java
@@ -19,6 +19,7 @@ package com.android.providers.media.photopicker.sync;
import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_CLOUD_ONLY;
import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_LOCAL_AND_CLOUD;
import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_LOCAL_ONLY;
+import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_MEDIA_GRANTS;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -33,6 +34,7 @@ public class SyncTrackerRegistry {
private static SyncTracker sLocalAlbumSyncTracker = new SyncTracker();
private static SyncTracker sCloudSyncTracker = new SyncTracker();
private static SyncTracker sCloudAlbumSyncTracker = new SyncTracker();
+ private static SyncTracker sGrantsSyncTracker = new SyncTracker();
public static SyncTracker getLocalSyncTracker() {
return sLocalSyncTracker;
@@ -59,6 +61,15 @@ public class SyncTrackerRegistry {
sLocalAlbumSyncTracker = localAlbumSyncTracker;
}
+ /**
+ * This setter is required to inject mock data for tests. Do not use this anywhere else.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public static void setGrantsSyncTracker(
+ SyncTracker grantsSyncTracker) {
+ sGrantsSyncTracker = grantsSyncTracker;
+ }
+
public static SyncTracker getCloudSyncTracker() {
return sCloudSyncTracker;
}
@@ -85,6 +96,10 @@ public class SyncTrackerRegistry {
sCloudAlbumSyncTracker = cloudAlbumSyncTracker;
}
+ public static SyncTracker getGrantsSyncTracker() {
+ return sGrantsSyncTracker;
+ }
+
/**
* Return the appropriate sync tracker.
* @param isLocal is true when sync with local provider needs to be tracked. It is false for
@@ -125,6 +140,9 @@ public class SyncTrackerRegistry {
if (syncSource == SYNC_CLOUD_ONLY || syncSource == SYNC_LOCAL_AND_CLOUD) {
getCloudSyncTracker().createSyncFuture(syncRequestId);
}
+ if (syncSource == SYNC_MEDIA_GRANTS) {
+ getGrantsSyncTracker().createSyncFuture(syncRequestId);
+ }
}
/**
@@ -154,6 +172,9 @@ public class SyncTrackerRegistry {
if (syncSource == SYNC_CLOUD_ONLY || syncSource == SYNC_LOCAL_AND_CLOUD) {
getCloudSyncTracker().markSyncCompleted(syncRequestId);
}
+ if (syncSource == SYNC_MEDIA_GRANTS) {
+ getGrantsSyncTracker().markSyncCompleted(syncRequestId);
+ }
}
/**
diff --git a/src/com/android/providers/media/photopicker/sync/WorkManagerInitializer.java b/src/com/android/providers/media/photopicker/sync/WorkManagerInitializer.java
index 638b07105..b8309d210 100644
--- a/src/com/android/providers/media/photopicker/sync/WorkManagerInitializer.java
+++ b/src/com/android/providers/media/photopicker/sync/WorkManagerInitializer.java
@@ -33,7 +33,7 @@ public class WorkManagerInitializer {
// {@link PickerSyncManager} to ensure that any request type is not blocked on other request
// types. It is advisable to use unique work requests because in case the number of queued
// requests grows, they should not block other work requests.
- private static final int WORK_MANAGER_THREAD_POOL_SIZE = 6;
+ private static final int WORK_MANAGER_THREAD_POOL_SIZE = 7;
@Nullable
private static volatile Executor sWorkManagerExecutor;
diff --git a/src/com/android/providers/media/photopicker/util/exceptions/WorkCancelledException.java b/src/com/android/providers/media/photopicker/util/exceptions/WorkCancelledException.java
new file mode 100644
index 000000000..9ee7ce4f5
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/util/exceptions/WorkCancelledException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.photopicker.util.exceptions;
+
+/**
+ * {@code WorkCancelledException} is thrown when the work in progress is cancelled.
+ */
+public class WorkCancelledException extends Exception {
+ public WorkCancelledException(String message) {
+ super(message);
+ }
+}
diff --git a/src/com/android/providers/media/photopicker/v2/PickerDataLayerV2.java b/src/com/android/providers/media/photopicker/v2/PickerDataLayerV2.java
index 1a1a55b74..1a082aaa2 100644
--- a/src/com/android/providers/media/photopicker/v2/PickerDataLayerV2.java
+++ b/src/com/android/providers/media/photopicker/v2/PickerDataLayerV2.java
@@ -16,7 +16,13 @@
package com.android.providers.media.photopicker.v2;
+import static com.android.providers.media.MediaGrants.MEDIA_GRANTS_TABLE;
+import static com.android.providers.media.MediaGrants.OWNER_PACKAGE_NAME_COLUMN;
+import static com.android.providers.media.MediaGrants.PACKAGE_USER_ID_COLUMN;
import static com.android.providers.media.PickerUriResolver.getAlbumUri;
+import static com.android.providers.media.photopicker.PickerSyncController.getPackageNameFromUid;
+import static com.android.providers.media.photopicker.PickerSyncController.uidToUserId;
+import static com.android.providers.media.photopicker.sync.PickerSyncManager.IMMEDIATE_GRANTS_SYNC_WORK_NAME;
import static com.android.providers.media.photopicker.sync.PickerSyncManager.IMMEDIATE_LOCAL_SYNC_WORK_NAME;
import static com.android.providers.media.photopicker.sync.WorkManagerInitializer.getWorkManager;
import static com.android.providers.media.photopicker.v2.model.AlbumsCursorWrapper.EMPTY_MEDIA_ID;
@@ -25,6 +31,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
@@ -32,9 +39,11 @@ import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
import android.os.Bundle;
import android.os.Process;
import android.provider.CloudMediaProviderContract.AlbumColumns;
+import android.provider.MediaStore;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -50,6 +59,7 @@ import com.android.providers.media.photopicker.v2.model.AlbumsCursorWrapper;
import com.android.providers.media.photopicker.v2.model.FavoritesMediaQuery;
import com.android.providers.media.photopicker.v2.model.MediaQuery;
import com.android.providers.media.photopicker.v2.model.MediaSource;
+import com.android.providers.media.photopicker.v2.model.PreviewMediaQuery;
import com.android.providers.media.photopicker.v2.model.ProviderCollectionInfo;
import com.android.providers.media.photopicker.v2.model.VideoMediaQuery;
@@ -57,7 +67,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -80,6 +92,7 @@ public class PickerDataLayerV2 {
AlbumColumns.ALBUM_ID_FAVORITES,
AlbumColumns.ALBUM_ID_VIDEOS
);
+
// Set of known local albums.
public static final Set<String> LOCAL_ALBUMS = Set.of(
AlbumColumns.ALBUM_ID_CAMERA,
@@ -88,6 +101,32 @@ public class PickerDataLayerV2 {
);
/**
+ * Table used to store the items for which the app hold read grants but have been de-selected
+ * by the user in the current photo-picker session.
+ */
+ public static final String DE_SELECTIONS_TABLE = "de_selections";
+
+ /**
+ * Table used to store the items for which the app hold read grants but have been de-selected
+ * by the user in the current photo-picker session, filtered by calling package name and userId.
+ */
+ public static final String CURRENT_DE_SELECTIONS_TABLE = "current_de_selections";
+
+ private static final String IS_FIRST_PAGE = "is_first_page";
+ /**
+ * In SQL joins for media_grants table, it is filtered to only provide the rows corresponding to
+ * the current package and userId. This is the name for the filtered table that is computed in a
+ * sub-query. Any references to the columns for media_grants table should use this table name
+ * instead.
+ */
+ public static final String CURRENT_GRANTS_TABLE = "current_media_grants";
+
+ public static final String COLUMN_GRANTS_COUNT = "grants_count";
+
+ private static final String PROJECTION_GRANTS_COUNT = String.format("COUNT(*) AS %s",
+ COLUMN_GRANTS_COUNT);
+
+ /**
* Refresh the cloud provider in-memory cache in PickerSyncController.
*/
public static void ensureProviders() {
@@ -131,6 +170,42 @@ public class PickerDataLayerV2 {
}
/**
+ * Returns a cursor with the Photo Picker media in response.
+ *
+ * @param appContext The application context.
+ * @param queryArgs The arguments help us filter on the media query to yield the desired
+ * results.
+ */
+ @NonNull
+ static Cursor queryPreviewMedia(@NonNull Context appContext, @NonNull Bundle queryArgs) {
+ final PreviewMediaQuery query = new PreviewMediaQuery(queryArgs);
+ final PickerSyncController syncController = PickerSyncController.getInstanceOrThrow();
+ final String effectiveLocalAuthority =
+ query.getProviders().contains(syncController.getLocalProvider())
+ ? syncController.getLocalProvider()
+ : null;
+ final String cloudAuthority = syncController
+ .getCloudProviderOrDefault(/* defaultValue */ null);
+ final String effectiveCloudAuthority =
+ syncController.shouldQueryCloudMedia(query.getProviders(), cloudAuthority)
+ ? cloudAuthority
+ : null;
+
+ if (queryArgs.getBoolean(IS_FIRST_PAGE)) {
+ PreviewMediaQuery.insertDeSelections(appContext, syncController,
+ query.getCallingPackageUid(), query.getCurrentDeSelection());
+ }
+
+ return queryMediaInternal(
+ appContext,
+ syncController,
+ query,
+ effectiveLocalAuthority,
+ effectiveCloudAuthority
+ );
+ }
+
+ /**
* Returns a cursor with the Photo Picker albums in response.
*
* @param appContext The application context.
@@ -163,7 +238,7 @@ public class PickerDataLayerV2 {
for (String albumId: PINNED_ALBUMS_ORDER) {
final AlbumsCursorWrapper albumCursor;
if (MERGED_ALBUMS.contains(albumId)) {
- albumCursor = getMergedAlbumsCursor(albumId, queryArgs, database,
+ albumCursor = getMergedAlbumsCursor(albumId, appContext, queryArgs, database,
effectiveLocalAuthority, effectiveCloudAuthority);
} else if (LOCAL_ALBUMS.contains(albumId)) {
albumCursor = localAlbums.getOrDefault(albumId, null);
@@ -237,6 +312,52 @@ public class PickerDataLayerV2 {
}
/**
+ * Queries the picker database and fetches the count of pre-granted media for the current
+ * package and userId.
+ *
+ * @return a [Cursor] containing only one column [COLUMN_GRANTS_COUNT] which have a single
+ * row representing the count.
+ */
+ static Cursor fetchMediaGrantsCount(
+ @NonNull Context appContext,
+ @NonNull Bundle queryArgs) {
+ String[] projectionIn = new String[]{PROJECTION_GRANTS_COUNT};
+ final PickerSyncController syncController = PickerSyncController.getInstanceOrThrow();
+ final SQLiteDatabase database = syncController.getDbFacade().getDatabase();
+
+ waitForOngoingGrantsSync(appContext);
+
+ int packageUid = queryArgs.getInt(Intent.EXTRA_UID);
+ int userId = uidToUserId(packageUid);
+ String[] packageNames = getPackageNameFromUid(appContext,
+ packageUid);
+
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(MEDIA_GRANTS_TABLE);
+ addWhereClausesForPackageAndUserIdSelection(userId, packageNames, MEDIA_GRANTS_TABLE, qb);
+
+ Cursor result = qb.query(database, projectionIn, null,
+ null, null, null, null);
+ return result;
+ }
+
+ /**
+ * Adds the clause to select rows based on calling packageName and userId.
+ */
+ public static void addWhereClausesForPackageAndUserIdSelection(int userId,
+ @NonNull String[] packageNames, String table, SQLiteQueryBuilder qb) {
+ // Add where clause for userId selection.
+ qb.appendWhereStandalone(
+ String.format(Locale.ROOT,
+ "%s.%s = %d", table, PACKAGE_USER_ID_COLUMN, userId));
+
+ // Add where clause for package name selection.
+ Objects.requireNonNull(packageNames);
+ qb.appendWhereStandalone(getPackageSelectionWhereClause(packageNames,
+ table).toString());
+ }
+
+ /**
* Query media from the database and prepare a cursor in response.
*
* We need to make multiple queries to prepare a response for the media query.
@@ -266,13 +387,13 @@ public class PickerDataLayerV2 {
) {
try {
final SQLiteDatabase database = syncController.getDbFacade().getDatabase();
-
- waitForOngoingSync(appContext, localAuthority, cloudAuthority);
+ waitForOngoingSync(appContext, localAuthority, cloudAuthority, query.getIntentAction());
try {
database.beginTransactionNonExclusive();
Cursor pageData = database.rawQuery(
getMediaPageQuery(
+ appContext,
query,
database,
PickerSQLConstants.Table.MEDIA,
@@ -281,10 +402,10 @@ public class PickerDataLayerV2 {
),
/* selectionArgs */ null
);
-
Bundle extraArgs = new Bundle();
Cursor nextPageKeyCursor = database.rawQuery(
getMediaNextPageKeyQuery(
+ appContext,
query,
database,
PickerSQLConstants.Table.MEDIA,
@@ -297,6 +418,7 @@ public class PickerDataLayerV2 {
Cursor prevPageKeyCursor = database.rawQuery(
getMediaPreviousPageQuery(
+ appContext,
query,
database,
PickerSQLConstants.Table.MEDIA,
@@ -306,17 +428,13 @@ public class PickerDataLayerV2 {
/* selectionArgs */ null
);
addPrevPageKey(extraArgs, prevPageKeyCursor);
-
database.setTransactionSuccessful();
-
pageData.setExtras(extraArgs);
Log.i(TAG, "Returning " + pageData.getCount() + " media metadata");
return pageData;
} finally {
database.endTransaction();
}
-
-
} catch (Exception e) {
throw new RuntimeException("Could not fetch media", e);
}
@@ -325,16 +443,28 @@ public class PickerDataLayerV2 {
private static void waitForOngoingSync(
@NonNull Context appContext,
@Nullable String localAuthority,
- @Nullable String cloudAuthority) {
+ @Nullable String cloudAuthority, String intentAction) {
+ // when the intent action is ACTION_USER_SELECT_IMAGES_FOR_APP, the flow should wait for
+ // the sync of grants and since this is a localOnly session. It should not wait or check
+ // cloud media.
+ boolean isUserSelectAction = MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP.equals(
+ intentAction);
if (localAuthority != null) {
SyncCompletionWaiter.waitForSync(
getWorkManager(appContext),
- SyncTrackerRegistry.getLocalSyncTracker(),
- IMMEDIATE_LOCAL_SYNC_WORK_NAME
+ SyncTrackerRegistry.getGrantsSyncTracker(),
+ IMMEDIATE_GRANTS_SYNC_WORK_NAME
);
+ if (isUserSelectAction) {
+ SyncCompletionWaiter.waitForSync(
+ getWorkManager(appContext),
+ SyncTrackerRegistry.getLocalSyncTracker(),
+ IMMEDIATE_LOCAL_SYNC_WORK_NAME
+ );
+ }
}
- if (cloudAuthority != null) {
+ if (cloudAuthority != null && !isUserSelectAction) {
boolean syncIsComplete = SyncCompletionWaiter.waitForSyncWithTimeout(
SyncTrackerRegistry.getCloudSyncTracker(),
CLOUD_SYNC_TIMEOUT_MILLIS);
@@ -343,6 +473,15 @@ public class PickerDataLayerV2 {
}
}
+ private static void waitForOngoingGrantsSync(
+ @NonNull Context appContext) {
+ SyncCompletionWaiter.waitForSync(
+ getWorkManager(appContext),
+ SyncTrackerRegistry.getGrantsSyncTracker(),
+ IMMEDIATE_GRANTS_SYNC_WORK_NAME
+ );
+ }
+
/**
* @param appContext The application context.
* @param query The AlbumMediaQuery object instance that tells us about the media query args.
@@ -431,13 +570,15 @@ public class PickerDataLayerV2 {
* Builds and returns the SQL query to get the page contents from the Media table in Picker DB.
*/
private static String getMediaPageQuery(
+ @Nullable Context appContext,
@NonNull MediaQuery query,
@NonNull SQLiteDatabase database,
@NonNull PickerSQLConstants.Table table,
@Nullable String localAuthority,
@Nullable String cloudAuthority) {
SelectSQLiteQueryBuilder queryBuilder = new SelectSQLiteQueryBuilder(database)
- .setTables(table.name())
+ .setTables(query.getTableWithRequiredJoins(table.toString(), appContext,
+ query.getCallingPackageUid(), query.getIntentAction()))
.setProjection(List.of(
PickerSQLConstants.MediaResponse.MEDIA_ID.getProjection(),
PickerSQLConstants.MediaResponse.PICKER_ID.getProjection(),
@@ -452,7 +593,9 @@ public class PickerDataLayerV2 {
PickerSQLConstants.MediaResponse.SIZE_IN_BYTES.getProjection(),
PickerSQLConstants.MediaResponse.MIME_TYPE.getProjection(),
PickerSQLConstants.MediaResponse.STANDARD_MIME_TYPE.getProjection(),
- PickerSQLConstants.MediaResponse.DURATION_MS.getProjection()
+ PickerSQLConstants.MediaResponse.DURATION_MS.getProjection(),
+ PickerSQLConstants.MediaResponse.IS_PRE_GRANTED.getProjection(
+ query.getIntentAction())
))
.setSortOrder(
String.format(
@@ -478,6 +621,7 @@ public class PickerDataLayerV2 {
*/
@Nullable
private static String getMediaNextPageKeyQuery(
+ @Nullable Context appContext,
@NonNull MediaQuery query,
@NonNull SQLiteDatabase database,
@NonNull PickerSQLConstants.Table table,
@@ -488,7 +632,9 @@ public class PickerDataLayerV2 {
}
SelectSQLiteQueryBuilder queryBuilder = new SelectSQLiteQueryBuilder(database)
- .setTables(table.name())
+ .setTables(
+ query.getTableWithRequiredJoins(table.toString(), appContext,
+ query.getCallingPackageUid(), query.getIntentAction()))
.setProjection(List.of(
PickerSQLConstants.MediaResponse.PICKER_ID.getProjection(),
PickerSQLConstants.MediaResponse.DATE_TAKEN_MS.getProjection()
@@ -522,13 +668,16 @@ public class PickerDataLayerV2 {
* get the previous page key.
*/
private static String getMediaPreviousPageQuery(
+ @Nullable Context appContext,
@NonNull MediaQuery query,
@NonNull SQLiteDatabase database,
@NonNull PickerSQLConstants.Table table,
@Nullable String localAuthority,
@Nullable String cloudAuthority) {
SelectSQLiteQueryBuilder queryBuilder = new SelectSQLiteQueryBuilder(database)
- .setTables(table.name())
+ .setTables(
+ query.getTableWithRequiredJoins(table.toString(), appContext,
+ query.getCallingPackageUid(), query.getIntentAction()))
.setProjection(List.of(
PickerSQLConstants.MediaResponse.PICKER_ID.getProjection(),
PickerSQLConstants.MediaResponse.DATE_TAKEN_MS.getProjection()
@@ -552,11 +701,28 @@ public class PickerDataLayerV2 {
}
/**
+ * Returns a clause that can be used to filter OWNER_PACKAGE_NAME_COLUMN using the input
+ * packageNames in a query.
+ */
+ public static @NonNull StringBuilder getPackageSelectionWhereClause(String[] packageNames,
+ String table) {
+ StringBuilder packageSelection = new StringBuilder();
+ String packageColumn = String.format("%s.%s", table, OWNER_PACKAGE_NAME_COLUMN);
+ packageSelection.append(packageColumn).append(" IN (\'");
+
+ String joinedPackageNames = String.join("\',\'", packageNames);
+ packageSelection.append(joinedPackageNames);
+
+ packageSelection.append("\')");
+ return packageSelection;
+ }
+
+ /**
* Return merged albums cursor for the given merged album id.
*
- * @param albumId Merged album id.
- * @param queryArgs Query arguments bundle that will be used to filter albums.
- * @param database Instance of Picker SQLiteDatabase.
+ * @param albumId Merged album id.
+ * @param queryArgs Query arguments bundle that will be used to filter albums.
+ * @param database Instance of Picker SQLiteDatabase.
* @param localAuthority The local authority if local albums should be returned, otherwise this
* argument should be null.
* @param cloudAuthority The cloud authority if cloud albums should be returned, otherwise this
@@ -564,6 +730,7 @@ public class PickerDataLayerV2 {
*/
private static AlbumsCursorWrapper getMergedAlbumsCursor(
@NonNull String albumId,
+ Context appContext,
@NonNull Bundle queryArgs,
@NonNull SQLiteDatabase database,
@Nullable String localAuthority,
@@ -591,6 +758,7 @@ public class PickerDataLayerV2 {
database.beginTransactionNonExclusive();
Cursor pickerDBResponse = database.rawQuery(
getMediaPageQuery(
+ appContext,
query,
database,
PickerSQLConstants.Table.MEDIA,
@@ -620,22 +788,21 @@ public class PickerDataLayerV2 {
return new AlbumsCursorWrapper(result, authority, localAuthority);
}
- // Show merged albums even if no data is currently available in the DB when cloud media
- // feature is enabled.
- if (cloudAuthority != null) {
- // Conform to the album response projection. Temporary code, this will change once
- // we start caching album metadata.
- final MatrixCursor result = new MatrixCursor(AlbumColumns.ALL_PROJECTION);
- final String[] projectionValue = new String[]{
- /* albumId */ albumId,
- /* dateTakenMillis */ Long.toString(Long.MAX_VALUE),
- /* displayName */ albumId,
- /* mediaId */ EMPTY_MEDIA_ID,
- /* count */ "0", // This value is not used anymore
- localAuthority,
- };
- result.addRow(projectionValue);
- return new AlbumsCursorWrapper(result, localAuthority, localAuthority);
+ // Always show Videos album if cloud feature is turned on and the MIME types filter
+ // would allow for video format(s).
+ if (albumId.equals(AlbumColumns.ALBUM_ID_VIDEOS) && cloudAuthority != null) {
+ return new AlbumsCursorWrapper(
+ getDefaultEmptyAlbum(albumId),
+ /* albumAuthority */ localAuthority,
+ /* localAuthority */ localAuthority);
+ }
+
+ // Always show Favorites album.
+ if (albumId.equals(AlbumColumns.ALBUM_ID_FAVORITES)) {
+ return new AlbumsCursorWrapper(
+ getDefaultEmptyAlbum(albumId),
+ /* albumAuthority */ localAuthority,
+ /* localAuthority */ localAuthority);
}
return null;
@@ -643,7 +810,22 @@ public class PickerDataLayerV2 {
database.setTransactionSuccessful();
database.endTransaction();
}
+ }
+ private static Cursor getDefaultEmptyAlbum(@NonNull String albumId) {
+ // Conform to the album response projection. Temporary code, this will change once we start
+ // caching album metadata.
+ final MatrixCursor result = new MatrixCursor(AlbumColumns.ALL_PROJECTION);
+ final String[] projectionValue = new String[]{
+ /* albumId */ albumId,
+ /* dateTakenMillis */ Long.toString(Long.MAX_VALUE),
+ /* displayName */ albumId,
+ /* mediaId */ EMPTY_MEDIA_ID,
+ /* count */ "0", // This value is not used anymore
+ /* authority */ null, // Authority is populated in AlbumsCursorWrapper
+ };
+ result.addRow(projectionValue);
+ return result;
}
/**
@@ -718,6 +900,17 @@ public class PickerDataLayerV2 {
// be used again.
if (localAlbumsCursor != null) localAlbumsCursor.close();
+ // Always show Camera album.
+ if (!localAlbumsMap.containsKey(AlbumColumns.ALBUM_ID_CAMERA)) {
+ localAlbumsMap.put(
+ AlbumColumns.ALBUM_ID_CAMERA,
+ new AlbumsCursorWrapper(
+ getDefaultEmptyAlbum(AlbumColumns.ALBUM_ID_CAMERA),
+ /* albumAuthority */ localAuthority,
+ /* localAuthority */ localAuthority)
+ );
+ }
+
return localAlbumsMap;
}
@@ -796,6 +989,7 @@ public class PickerDataLayerV2 {
database.beginTransactionNonExclusive();
Cursor pageData = database.rawQuery(
getMediaPageQuery(
+ appContext,
query,
database,
PickerSQLConstants.Table.ALBUM_MEDIA,
@@ -808,6 +1002,7 @@ public class PickerDataLayerV2 {
Bundle extraArgs = new Bundle();
Cursor nextPageKeyCursor = database.rawQuery(
getMediaNextPageKeyQuery(
+ appContext,
query,
database,
PickerSQLConstants.Table.ALBUM_MEDIA,
@@ -820,6 +1015,7 @@ public class PickerDataLayerV2 {
Cursor prevPageKeyCursor = database.rawQuery(
getMediaPreviousPageQuery(
+ appContext,
query,
database,
PickerSQLConstants.Table.ALBUM_MEDIA,
@@ -884,12 +1080,13 @@ public class PickerDataLayerV2 {
final SQLiteDatabase database = syncController.getDbFacade().getDatabase();
- waitForOngoingSync(appContext, localAuthority, cloudAuthority);
+ waitForOngoingSync(appContext, localAuthority, cloudAuthority, query.getIntentAction());
try {
database.beginTransactionNonExclusive();
Cursor pageData = database.rawQuery(
getMediaPageQuery(
+ appContext,
query,
database,
PickerSQLConstants.Table.MEDIA,
@@ -902,6 +1099,7 @@ public class PickerDataLayerV2 {
Bundle extraArgs = new Bundle();
Cursor nextPageKeyCursor = database.rawQuery(
getMediaNextPageKeyQuery(
+ appContext,
query,
database,
PickerSQLConstants.Table.MEDIA,
@@ -914,6 +1112,7 @@ public class PickerDataLayerV2 {
Cursor prevPageKeyCursor = database.rawQuery(
getMediaPreviousPageQuery(
+ appContext,
query,
database,
PickerSQLConstants.Table.MEDIA,
diff --git a/src/com/android/providers/media/photopicker/v2/PickerSQLConstants.java b/src/com/android/providers/media/photopicker/v2/PickerSQLConstants.java
index ff7f33f38..1354830e1 100644
--- a/src/com/android/providers/media/photopicker/v2/PickerSQLConstants.java
+++ b/src/com/android/providers/media/photopicker/v2/PickerSQLConstants.java
@@ -34,6 +34,7 @@ import android.provider.MediaStore;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.providers.media.MediaGrants;
import com.android.providers.media.photopicker.v2.model.MediaSource;
import java.util.Arrays;
@@ -142,7 +143,8 @@ public class PickerSQLConstants {
MIME_TYPE(KEY_MIME_TYPE, CloudMediaProviderContract.MediaColumns.MIME_TYPE),
STANDARD_MIME_TYPE(KEY_STANDARD_MIME_TYPE_EXTENSION,
CloudMediaProviderContract.MediaColumns.STANDARD_MIME_TYPE_EXTENSION),
- DURATION_MS(KEY_DURATION_MS, CloudMediaProviderContract.MediaColumns.DURATION_MILLIS);
+ DURATION_MS(KEY_DURATION_MS, CloudMediaProviderContract.MediaColumns.DURATION_MILLIS),
+ IS_PRE_GRANTED("is_pre_granted");
private static final String DEFAULT_PROJECTION = "%s AS %s";
@Nullable
@@ -236,6 +238,22 @@ public class PickerSQLConstants {
}
}
+ @NonNull
+ public String getProjection(String intentAction) {
+ switch (this) {
+ case IS_PRE_GRANTED:
+ return String.format(DEFAULT_PROJECTION, getIsPregranted(intentAction),
+ mProjectedName);
+ default:
+ if (mColumnName == null) {
+ throw new IllegalArgumentException(
+ "Could not get projection for " + this.name()
+ );
+ }
+ return String.format(DEFAULT_PROJECTION, mColumnName, mProjectedName);
+ }
+ }
+
private String getMediaId() {
return String.format(
"IFNULL(%s, %s)",
@@ -295,6 +313,15 @@ public class PickerSQLConstants {
getMediaId()
);
}
+
+ private String getIsPregranted(String intentAction) {
+ if (MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP.equals(intentAction)) {
+ return String.format("CASE WHEN %s.%s IS NOT NULL THEN 1 ELSE 0 END",
+ PickerDataLayerV2.CURRENT_GRANTS_TABLE, MediaGrants.FILE_ID_COLUMN);
+ } else {
+ return "0"; // default case for other intent actions
+ }
+ }
}
enum MediaResponseExtras {
diff --git a/src/com/android/providers/media/photopicker/v2/PickerUriResolverV2.java b/src/com/android/providers/media/photopicker/v2/PickerUriResolverV2.java
index 0364848f6..0e9e1aa90 100644
--- a/src/com/android/providers/media/photopicker/v2/PickerUriResolverV2.java
+++ b/src/com/android/providers/media/photopicker/v2/PickerUriResolverV2.java
@@ -42,6 +42,9 @@ public class PickerUriResolverV2 {
public static final String MEDIA_PATH_SEGMENT = "media";
public static final String ALBUM_PATH_SEGMENT = "album";
public static final String UPDATE_PATH_SEGMENT = "update";
+ public static final String MEDIA_GRANTS_COUNT_PATH_SEGMENT = "media_grants_count";
+ public static final String PREVIEW_PATH_SEGMENT = "preview";
+
static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static final int PICKER_INTERNAL_MEDIA = 1;
@@ -49,6 +52,8 @@ public class PickerUriResolverV2 {
static final int PICKER_INTERNAL_ALBUM_CONTENT = 3;
static final int PICKER_INTERNAL_AVAILABLE_PROVIDERS = 4;
static final int PICKER_INTERNAL_COLLECTION_INFO = 5;
+ static final int PICKER_INTERNAL_MEDIA_GRANTS_COUNT = 6;
+ static final int PICKER_INTERNAL_MEDIA_PREVIEW = 7;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -58,6 +63,8 @@ public class PickerUriResolverV2 {
PICKER_INTERNAL_ALBUM_CONTENT,
PICKER_INTERNAL_AVAILABLE_PROVIDERS,
PICKER_INTERNAL_COLLECTION_INFO,
+ PICKER_INTERNAL_MEDIA_GRANTS_COUNT,
+ PICKER_INTERNAL_MEDIA_PREVIEW
})
private @interface PickerQuery {}
@@ -81,6 +88,11 @@ public class PickerUriResolverV2 {
BASE_PICKER_PATH + COLLECTION_INFO_PATH_SEGMENT,
PICKER_INTERNAL_COLLECTION_INFO
);
+ sUriMatcher.addURI(MediaStore.AUTHORITY, BASE_PICKER_PATH + MEDIA_GRANTS_COUNT_PATH_SEGMENT,
+ PICKER_INTERNAL_MEDIA_GRANTS_COUNT);
+ sUriMatcher.addURI(MediaStore.AUTHORITY,
+ BASE_PICKER_PATH + MEDIA_PATH_SEGMENT + "/" + PREVIEW_PATH_SEGMENT,
+ PICKER_INTERNAL_MEDIA_PREVIEW);
}
/**
@@ -110,6 +122,11 @@ public class PickerUriResolverV2 {
return PickerDataLayerV2.queryAvailableProviders(appContext);
case PICKER_INTERNAL_COLLECTION_INFO:
return PickerDataLayerV2.queryCollectionInfo();
+ case PICKER_INTERNAL_MEDIA_GRANTS_COUNT:
+ return PickerDataLayerV2.fetchMediaGrantsCount(appContext,
+ requireNonNull(queryArgs));
+ case PICKER_INTERNAL_MEDIA_PREVIEW:
+ return PickerDataLayerV2.queryPreviewMedia(appContext, queryArgs);
default:
throw new UnsupportedOperationException("Could not recognize content URI " + uri);
}
diff --git a/src/com/android/providers/media/photopicker/v2/model/MediaQuery.java b/src/com/android/providers/media/photopicker/v2/model/MediaQuery.java
index e13bcf8a1..28e770d64 100644
--- a/src/com/android/providers/media/photopicker/v2/model/MediaQuery.java
+++ b/src/com/android/providers/media/photopicker/v2/model/MediaQuery.java
@@ -16,24 +16,35 @@
package com.android.providers.media.photopicker.v2.model;
+import static com.android.providers.media.MediaGrants.FILE_ID_COLUMN;
+import static com.android.providers.media.MediaGrants.MEDIA_GRANTS_TABLE;
+import static com.android.providers.media.MediaGrants.PACKAGE_USER_ID_COLUMN;
+import static com.android.providers.media.photopicker.PickerSyncController.getPackageNameFromUid;
+import static com.android.providers.media.photopicker.PickerSyncController.uidToUserId;
import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_CLOUD_ID;
import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_DATE_TAKEN_MS;
import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_ID;
import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_IS_VISIBLE;
import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_LOCAL_ID;
import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_MIME_TYPE;
+import static com.android.providers.media.photopicker.v2.PickerDataLayerV2.CURRENT_GRANTS_TABLE;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.provider.MediaStore;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.providers.media.MediaGrants;
+import com.android.providers.media.photopicker.v2.PickerDataLayerV2;
import com.android.providers.media.photopicker.v2.SelectSQLiteQueryBuilder;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
/**
@@ -46,6 +57,7 @@ public class MediaQuery {
private final String mIntentAction;
@NonNull
private final List<String> mProviders;
+ private final int mCallingPackageUid;
// If this is not null or empty, only fetch the rows that match at least one of the
// given mime types.
@Nullable
@@ -64,11 +76,11 @@ public class MediaQuery {
mProviders = new ArrayList<>(
Objects.requireNonNull(queryArgs.getStringArrayList("providers")));
mMimeTypes = queryArgs.getStringArrayList("mime_types") != null
- ? new ArrayList<>(queryArgs.getStringArrayList("mime_types"))
- : null;
+ ? new ArrayList<>(queryArgs.getStringArrayList("mime_types")) : null;
// This is true by default.
mShouldDedupe = true;
+ mCallingPackageUid = queryArgs.getInt(Intent.EXTRA_UID, -1);
}
@NonNull
@@ -91,6 +103,11 @@ public class MediaQuery {
return mIntentAction;
}
+ public int getCallingPackageUid() {
+ return mCallingPackageUid;
+ }
+
+
/**
* Create and return a bundle for extras for CMP queries made from Media Provider.
*/
@@ -104,6 +121,62 @@ public class MediaQuery {
}
/**
+ * Returns the table that should be used in the query operations including any joins that are
+ * required with other tables in the database.
+ */
+ public String getTableWithRequiredJoins(String table,
+ @NonNull Context appContext, int callingPackageUid, String intentAction) {
+
+ if (!MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP.equals(intentAction)) {
+ // No joins are required for a ACTION_USER_SELECT_IMAGES_FOR_APP action query.
+ return table;
+ }
+ Objects.requireNonNull(appContext);
+ if (callingPackageUid == -1) {
+ throw new IllegalArgumentException("Calling package uid in"
+ + "ACTION_USER_SELECT_IMAGES_FOR_APP mode should not be -1. Invalid UID");
+ }
+
+ int userId = uidToUserId(callingPackageUid);
+ String[] packageNames = getPackageNameFromUid(appContext,
+ callingPackageUid);
+ Objects.requireNonNull(packageNames);
+ StringBuilder packageSelection =
+ PickerDataLayerV2.getPackageSelectionWhereClause(packageNames, MEDIA_GRANTS_TABLE);
+
+ // The following join is performed for the query media operation to obtain information on
+ // which items are preGranted.
+ String filterQueryBasedOnPackageNameAndUserId =
+ "(SELECT %s.%s FROM %s "
+ + "WHERE "
+ + " %s AND "
+ + "%s = %d) "
+ + "AS %s";
+
+ String filteredMediaGrantsTable = String.format(
+ Locale.ROOT,
+ filterQueryBasedOnPackageNameAndUserId,
+ MEDIA_GRANTS_TABLE,
+ FILE_ID_COLUMN,
+ MEDIA_GRANTS_TABLE,
+ packageSelection,
+ PACKAGE_USER_ID_COLUMN,
+ userId,
+ CURRENT_GRANTS_TABLE);
+
+ return String.format(
+ "%s LEFT JOIN %s"
+ + " ON %s.%s = %s.%s ",
+ table,
+ filteredMediaGrantsTable,
+ table,
+ KEY_LOCAL_ID,
+ CURRENT_GRANTS_TABLE,
+ MediaGrants.FILE_ID_COLUMN
+ );
+ }
+
+ /**
* @param queryBuilder Adds SQL query where clause based on the Media query arguments to the
* given query builder.
* @param localAuthority the authority of the local provider if we should include local media in
diff --git a/src/com/android/providers/media/photopicker/v2/model/PreviewMediaQuery.java b/src/com/android/providers/media/photopicker/v2/model/PreviewMediaQuery.java
new file mode 100644
index 000000000..76aa8de5d
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/v2/model/PreviewMediaQuery.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.photopicker.v2.model;
+
+import static com.android.providers.media.MediaGrants.FILE_ID_COLUMN;
+import static com.android.providers.media.MediaGrants.MEDIA_GRANTS_TABLE;
+import static com.android.providers.media.MediaGrants.OWNER_PACKAGE_NAME_COLUMN;
+import static com.android.providers.media.MediaGrants.PACKAGE_USER_ID_COLUMN;
+import static com.android.providers.media.photopicker.PickerSyncController.getPackageNameFromUid;
+import static com.android.providers.media.photopicker.PickerSyncController.uidToUserId;
+import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_LOCAL_ID;
+import static com.android.providers.media.photopicker.v2.PickerDataLayerV2.CURRENT_DE_SELECTIONS_TABLE;
+import static com.android.providers.media.photopicker.v2.PickerDataLayerV2.CURRENT_GRANTS_TABLE;
+import static com.android.providers.media.photopicker.v2.PickerDataLayerV2.DE_SELECTIONS_TABLE;
+import static com.android.providers.media.photopicker.v2.PickerDataLayerV2.addWhereClausesForPackageAndUserIdSelection;
+import static com.android.providers.media.photopicker.v2.PickerDataLayerV2.getPackageSelectionWhereClause;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.providers.media.MediaGrants;
+import com.android.providers.media.photopicker.PickerSyncController;
+import com.android.providers.media.photopicker.v2.SelectSQLiteQueryBuilder;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * This is a convenience class for Preview media content related SQL queries performed on the Picker
+ * Database.
+ */
+public class PreviewMediaQuery extends MediaQuery {
+ private final ArrayList<String> mCurrentSelection;
+ private final ArrayList<String> mCurrentDeSelection;
+
+ public PreviewMediaQuery(
+ @NonNull Bundle queryArgs) {
+ super(queryArgs);
+ mCurrentSelection = queryArgs.getStringArrayList("current_selection");
+ mCurrentDeSelection = queryArgs.getStringArrayList("current_de_selection");
+ }
+
+ public ArrayList<String> getCurrentSelection() {
+ return mCurrentSelection;
+ }
+
+ public ArrayList<String> getCurrentDeSelection() {
+ return mCurrentDeSelection;
+ }
+
+ @Override
+ public void addWhereClause(
+ @NonNull SelectSQLiteQueryBuilder queryBuilder,
+ @Nullable String localAuthority,
+ @Nullable String cloudAuthority,
+ boolean reverseOrder
+ ) {
+ super.addWhereClause(queryBuilder, localAuthority, cloudAuthority, reverseOrder);
+
+ addIdSelectionClause(queryBuilder);
+ }
+
+ private void addIdSelectionClause(@NonNull SelectSQLiteQueryBuilder queryBuilder) {
+ StringBuilder idSelectionPlaceholder = new StringBuilder();
+ if (mCurrentSelection != null && !mCurrentSelection.isEmpty()) {
+ idSelectionPlaceholder.append("local_id IN (");
+ String joinedIds = String.join(",", mCurrentSelection);
+ idSelectionPlaceholder.append(joinedIds);
+ idSelectionPlaceholder.append(")");
+ }
+
+ if (!idSelectionPlaceholder.toString().isEmpty()) {
+ idSelectionPlaceholder.append(" OR ");
+ }
+
+ idSelectionPlaceholder.append(
+ String.format("(%s.%s IS NOT NULL AND %s.%s IS NULL)",
+ // current_media_grants.file_id IS NOT NULL
+ CURRENT_GRANTS_TABLE, MediaGrants.FILE_ID_COLUMN,
+ // current_de_selections.file_id IS NULL
+ CURRENT_DE_SELECTIONS_TABLE, MediaGrants.FILE_ID_COLUMN));
+ queryBuilder.appendWhereStandalone(idSelectionPlaceholder.toString());
+ }
+
+ /**
+ * Returns the table that should be used in the query operations including any joins that are
+ * required with other tables in the database.
+ */
+ @Override
+ public String getTableWithRequiredJoins(String table,
+ @NonNull Context appContext, int callingPackageUid, String intentAction) {
+ Objects.requireNonNull(appContext);
+ if (callingPackageUid == -1) {
+ throw new IllegalArgumentException("Calling package uid in"
+ + "ACTION_USER_SELECT_IMAGES_FOR_APP mode should not be -1. Invalid UID");
+ }
+ int userId = uidToUserId(callingPackageUid);
+ String[] packageNames = getPackageNameFromUid(appContext,
+ callingPackageUid);
+ Objects.requireNonNull(packageNames);
+
+ // The following joins for the table is performed for the preview request.
+ // Media items needs to be filtered based on:
+ // 1. if they are selected by the user in the current session
+ // 2. if they have been pre-granted i.e. their grant is in media_grants table AND they have
+ // not be de-selected by the user in the current session i.e. the item is not part of
+ // the de_selections table.
+ // To find such a union of items, the current selection mentioned in point one can be
+ // handled with a where clause on media table itself but for point 2 to be satisfied the
+ // media table needs to be joined with media_grants and de_selections tables.
+ // These tables can contain data for multiple apps hence they need to be separately
+ // filtered with the help of a sub-query based on the current calling package name and
+ // userId.
+
+ String filterQueryBasedOnPackageNameAndUserId = "(SELECT %s.%s FROM %s "
+ + "WHERE "
+ + " %s AND "
+ + "%s = %d) "
+ + "AS %s";
+
+ String filteredMediaGrantsTable = String.format(Locale.ROOT,
+ filterQueryBasedOnPackageNameAndUserId,
+ MEDIA_GRANTS_TABLE,
+ FILE_ID_COLUMN,
+ MEDIA_GRANTS_TABLE,
+ getPackageSelectionWhereClause(packageNames, MEDIA_GRANTS_TABLE),
+ PACKAGE_USER_ID_COLUMN,
+ userId,
+ CURRENT_GRANTS_TABLE);
+
+ String filteredDeSelectionsTable = String.format(Locale.ROOT,
+ filterQueryBasedOnPackageNameAndUserId,
+ DE_SELECTIONS_TABLE,
+ FILE_ID_COLUMN,
+ DE_SELECTIONS_TABLE,
+ getPackageSelectionWhereClause(packageNames, DE_SELECTIONS_TABLE),
+ PACKAGE_USER_ID_COLUMN,
+ userId,
+ CURRENT_DE_SELECTIONS_TABLE);
+
+ return String.format(
+ "%s LEFT JOIN %s"
+ + " ON %s.%s = %s.%s "
+ + "LEFT JOIN %s"
+ + " ON %s.%s = %s.%s",
+ table,
+ filteredMediaGrantsTable,
+ table,
+ KEY_LOCAL_ID,
+ CURRENT_GRANTS_TABLE,
+ MediaGrants.FILE_ID_COLUMN,
+ filteredDeSelectionsTable,
+ table,
+ KEY_LOCAL_ID,
+ CURRENT_DE_SELECTIONS_TABLE,
+ FILE_ID_COLUMN
+ );
+ }
+
+ /**
+ * Insert ids in 'de_selection' table in the picker.db to be used for exclusions in the query
+ * operation.
+ */
+ public static void insertDeSelections(
+ @NonNull Context appContext,
+ @NonNull PickerSyncController syncController,
+ int callingUid,
+ @NonNull ArrayList<String> currentDeSelection
+ ) {
+
+ final SQLiteDatabase database = syncController.getDbFacade().getDatabase();
+ try {
+ database.beginTransaction();
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(DE_SELECTIONS_TABLE);
+ String[] ownerPackageName = getPackageNameFromUid(appContext,
+ callingUid);
+ int userId = uidToUserId(callingUid);
+ addWhereClausesForPackageAndUserIdSelection(userId, ownerPackageName,
+ DE_SELECTIONS_TABLE, qb);
+ qb.delete(database, null, null);
+
+ qb = new SQLiteQueryBuilder();
+ qb.setTables(DE_SELECTIONS_TABLE);
+
+ if (!currentDeSelection.isEmpty()) {
+ ContentValues cv = new ContentValues();
+ for (int i = 0; i < currentDeSelection.size(); i++) {
+ cv.clear();
+ cv.put(FILE_ID_COLUMN, currentDeSelection.get(i));
+ cv.put(OWNER_PACKAGE_NAME_COLUMN, ownerPackageName[0]);
+ cv.put(PACKAGE_USER_ID_COLUMN, userId);
+ qb.insert(database, cv);
+ }
+ }
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ }
+}
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index faac7a09d..c791cc1b5 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -49,6 +49,7 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.providers.media.util.FileUtils.canonicalize;
+import static com.android.providers.media.util.IsoInterface.MAX_XMP_SIZE_BYTES;
import static com.android.providers.media.util.Metrics.translateReason;
import static java.util.Objects.requireNonNull;
@@ -175,7 +176,6 @@ public class ModernMediaScanner implements MediaScanner {
}
private static final int BATCH_SIZE = 32;
- private static final int MAX_XMP_SIZE_BYTES = 1024 * 1024;
// |excludeDirs * 2| < 1000 which is the max SQL expression size
// Because we add |excludeDir| and |excludeDir/| in the SQL expression to match dir and subdirs
// See SQLITE_MAX_EXPR_DEPTH in sqlite3.c
diff --git a/src/com/android/providers/media/util/IsoInterface.java b/src/com/android/providers/media/util/IsoInterface.java
index 5fb5130a8..43a00f00c 100644
--- a/src/com/android/providers/media/util/IsoInterface.java
+++ b/src/com/android/providers/media/util/IsoInterface.java
@@ -42,12 +42,15 @@ import java.util.UUID;
/**
* Simple parser for ISO base media file format. Designed to mirror ergonomics
- * of {@link ExifInterface}.
+ * of {@link ExifInterface}. Stores boxes related to xmp and gps in order to
+ * prevent from {@link OutOfMemoryError}.
*/
public class IsoInterface {
private static final String TAG = "IsoInterface";
private static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+ public static final int MAX_XMP_SIZE_BYTES = 1024 * 1024;
+
public static final int BOX_ILST = 0x696c7374;
public static final int BOX_FTYP = 0x66747970;
public static final int BOX_HDLR = 0x68646c72;
@@ -60,6 +63,9 @@ public class IsoInterface {
public static final int BOX_GPS = 0x67707320;
public static final int BOX_GPS0 = 0x67707330;
+ public static final UUID XMP_UUID =
+ UUID.fromString("be7acfcb-97a9-42e8-9c71-999491e3afac");
+
/**
* Test if given box type is a well-known parent box type.
*/
@@ -90,17 +96,14 @@ public class IsoInterface {
}
}
- /** Top-level boxes */
- private List<Box> mRoots = new ArrayList<>();
- /** Flattened view of all boxes */
- private List<Box> mFlattened = new ArrayList<>();
+ /** Flattened view of some boxes */
+ private final List<Box> mFlattened = new ArrayList<>();
private static class Box {
public final int type;
public long[] range;
public UUID uuid;
public byte[] data;
- public List<Box> children;
public int headerSize;
public Box(int type, long[] range) {
@@ -133,13 +136,26 @@ public class IsoInterface {
return new UUID(high, low);
}
- private static @Nullable Box parseNextBox(@NonNull FileDescriptor fd, long end, int parentType,
- @NonNull String prefix) throws ErrnoException, IOException {
+ private static @Nullable byte[] allocateBuffer(int type, int size) {
+ try {
+ if (size > MAX_XMP_SIZE_BYTES) {
+ Log.w(TAG, "Iso box(" + type + ") data size is too large: " + size);
+ return null;
+ }
+ return new byte[size];
+ } catch (OutOfMemoryError e) {
+ Log.w(TAG, "Couldn't read large box(" + type + "), size: " + size, e);
+ return null;
+ }
+ }
+
+ private static boolean parseNextBox(@NonNull List<Box> flatten, @NonNull FileDescriptor fd,
+ long end, int parentType, @NonNull String prefix) throws ErrnoException, IOException {
final long pos = Os.lseek(fd, 0, OsConstants.SEEK_CUR);
int headerSize = 8;
if (end - pos < headerSize) {
- return null;
+ return false;
}
long len = Integer.toUnsignedLong(readInt(fd));
@@ -159,7 +175,7 @@ public class IsoInterface {
if (len < headerSize || pos + len > end) {
Log.w(TAG, "Invalid box at " + pos + " of length " + len
+ ". End of parent " + end);
- return null;
+ return false;
}
final Box box = new Box(type, new long[] { pos, len });
@@ -173,30 +189,16 @@ public class IsoInterface {
Log.v(TAG, prefix + " UUID " + box.uuid);
}
- if (len > Integer.MAX_VALUE) {
- Log.w(TAG, "Skipping abnormally large uuid box");
- return null;
- }
+ if (Objects.equals(box.uuid, XMP_UUID)) {
+ box.data = allocateBuffer(type, (int) (len - box.headerSize));
+ if (box.data == null) return false;
- try {
- box.data = new byte[(int) (len - box.headerSize)];
- } catch (OutOfMemoryError e) {
- Log.w(TAG, "Couldn't read large uuid box", e);
- return null;
+ Os.read(fd, box.data, 0, box.data.length);
}
- Os.read(fd, box.data, 0, box.data.length);
} else if (type == BOX_XMP) {
- if (len > Integer.MAX_VALUE) {
- Log.w(TAG, "Skipping abnormally large xmp box");
- return null;
- }
+ box.data = allocateBuffer(type, (int) (len - box.headerSize));
+ if (box.data == null) return false;
- try {
- box.data = new byte[(int) (len - box.headerSize)];
- } catch (OutOfMemoryError e) {
- Log.w(TAG, "Couldn't read large xmp box", e);
- return null;
- }
Os.read(fd, box.data, 0, box.data.length);
} else if (type == BOX_META && len != headerSize) {
// The format of this differs in ISO and QT encoding:
@@ -221,19 +223,29 @@ public class IsoInterface {
+ " at " + pos + " hdr " + box.headerSize + " length " + len);
}
+ switch (type) {
+ case BOX_UUID:
+ if (!Objects.equals(box.uuid, XMP_UUID)) break;
+ // fall through
+ case BOX_META:
+ case BOX_HDLR:
+ case BOX_XYZ:
+ case BOX_LOCI:
+ case BOX_GPS:
+ case BOX_GPS0:
+ flatten.add(box);
+ break;
+ }
+
// Recursively parse any children boxes
if (isBoxParent(type)) {
- box.children = new ArrayList<>();
-
- Box child;
- while ((child = parseNextBox(fd, pos + len, type, prefix + " ")) != null) {
- box.children.add(child);
- }
+ //noinspection StatementWithEmptyBody
+ while (parseNextBox(flatten, fd, pos + len, type, prefix + " ")) {}
}
// Skip completely over ourselves
Os.lseek(fd, pos + len, OsConstants.SEEK_SET);
- return box;
+ return true;
}
private IsoInterface(@NonNull FileDescriptor fd) throws IOException {
@@ -255,26 +267,15 @@ public class IsoInterface {
final long end = Os.lseek(fd, 0, OsConstants.SEEK_END);
Os.lseek(fd, 0, OsConstants.SEEK_SET);
- Box box;
- while ((box = parseNextBox(fd, end, -1, "")) != null) {
- mRoots.add(box);
- }
+
+ //noinspection StatementWithEmptyBody
+ while (parseNextBox(mFlattened, fd, end, -1, "")) {}
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
} catch (OutOfMemoryError e) {
Log.e(TAG, "Too many boxes in file. This might imply a corrupted file.", e);
throw new IOException(e.getMessage());
}
-
- // Also create a flattened structure to speed up searching
- final Queue<Box> queue = new ArrayDeque<>(mRoots);
- while (!queue.isEmpty()) {
- final Box box = queue.poll();
- mFlattened.add(box);
- if (box.children != null) {
- queue.addAll(box.children);
- }
- }
}
public static @NonNull IsoInterface fromFile(@NonNull File file)
@@ -308,10 +309,10 @@ public class IsoInterface {
return res.toArray();
}
- public @NonNull long[] getBoxRanges(@NonNull UUID uuid) {
+ public @NonNull long[] getBoxRangesForXmpUuid() {
LongArray res = new LongArray();
for (Box box : mFlattened) {
- if (box.type == BOX_UUID && Objects.equals(box.uuid, uuid)) {
+ if (box.type == BOX_UUID && Objects.equals(box.uuid, XMP_UUID)) {
for (int i = 0; i < box.range.length; i += 2) {
res.add(box.range[i] + box.headerSize);
res.add(box.range[i] + box.range[i + 1]);
@@ -334,11 +335,11 @@ public class IsoInterface {
}
/**
- * Return contents of the first UUID box of requested type.
+ * Return contents of the first XMP UUID box of requested type.
*/
- public @Nullable byte[] getBoxBytes(@NonNull UUID uuid) {
+ public @Nullable byte[] getBoxBytesForXmpUuid() {
for (Box box : mFlattened) {
- if (box.type == BOX_UUID && Objects.equals(box.uuid, uuid)) {
+ if (box.type == BOX_UUID && Objects.equals(box.uuid, XMP_UUID)) {
return box.data;
}
}
diff --git a/src/com/android/providers/media/util/XmpDataParser.java b/src/com/android/providers/media/util/XmpDataParser.java
index 7ee0adda0..cbd4bcf59 100644
--- a/src/com/android/providers/media/util/XmpDataParser.java
+++ b/src/com/android/providers/media/util/XmpDataParser.java
@@ -36,7 +36,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
-import java.util.UUID;
public final class XmpDataParser implements Closeable {
@@ -223,9 +222,8 @@ public final class XmpDataParser implements Closeable {
}
static @NonNull XmpData extractXmpData(@NonNull IsoInterface iso) {
- UUID uuid = UUID.fromString("be7acfcb-97a9-42e8-9c71-999491e3afac");
- byte[] buf = iso.getBoxBytes(uuid);
- long[] xmpOffsets = iso.getBoxRanges(uuid);
+ byte[] buf = iso.getBoxBytesForXmpUuid();
+ long[] xmpOffsets = iso.getBoxRangesForXmpUuid();
if (buf == null) {
buf = iso.getBoxBytes(IsoInterface.BOX_XMP);
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index 55d21a6fe..31d9e1535 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -14,6 +14,13 @@
limitations under the License.
-->
<configuration description="Runs Tests for MediaProvder.">
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="force-skip-system-props" value="true" />
+ <option name="set-global-setting" key="verifier_engprod" value="1" />
+ <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="MediaProviderTests.apk" />
<option name="test-file-name" value="MediaProviderTestAppForPermissionActivity.apk" />
diff --git a/tests/src/com/android/providers/media/MediaProviderTest.java b/tests/src/com/android/providers/media/MediaProviderTest.java
index 197556261..02a1999af 100644
--- a/tests/src/com/android/providers/media/MediaProviderTest.java
+++ b/tests/src/com/android/providers/media/MediaProviderTest.java
@@ -16,6 +16,8 @@
package com.android.providers.media;
+import static android.provider.MediaStore.getGeneration;
+
import static com.android.providers.media.scan.MediaScannerTest.stage;
import static com.android.providers.media.util.FileUtils.extractDisplayName;
import static com.android.providers.media.util.FileUtils.extractRelativePath;
@@ -36,6 +38,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.Manifest;
+import android.content.ContentInterface;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
@@ -53,6 +56,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Environment;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.MediaStore;
@@ -88,6 +92,7 @@ import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -304,7 +309,7 @@ public class MediaProviderTest {
public void testMetadata() {
assertNotNull(MediaStore.getVersion(sIsolatedContext,
MediaStore.VOLUME_EXTERNAL_PRIMARY));
- assertNotNull(MediaStore.getGeneration(sIsolatedResolver,
+ assertNotNull(getGeneration(sIsolatedResolver,
MediaStore.VOLUME_EXTERNAL_PRIMARY));
}
@@ -1830,6 +1835,25 @@ public class MediaProviderTest {
}
}
+ @Test
+ public void testIllegalStateExceptionOnGetGenerationForNullValue() throws RemoteException {
+ ContentInterface contentInterface = Mockito.mock(MediaProvider.class);
+ Mockito.doReturn(null).when(contentInterface).call(Mockito.anyString(),
+ Mockito.anyString(), Mockito.any(String.class), Mockito.any(Bundle.class));
+ String volumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
+
+ ContentResolver contentResolver = ContentResolver.wrap(contentInterface);
+
+ try {
+ getGeneration(contentResolver, volumeName);
+ fail("Expected a IllegalStateException Exception");
+ } catch (IllegalStateException e) {
+ assertEquals("Failed to get generation for volume '" + volumeName
+ + "'. The ContentResolver call returned null.", e.getMessage());
+ }
+
+ }
+
private void testRedactionForFileExtension(int resId, String extension) throws Exception {
final File dir = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
diff --git a/tests/src/com/android/providers/media/TestConfigStore.java b/tests/src/com/android/providers/media/TestConfigStore.java
index c3c71ba34..9a173878a 100644
--- a/tests/src/com/android/providers/media/TestConfigStore.java
+++ b/tests/src/com/android/providers/media/TestConfigStore.java
@@ -37,6 +37,7 @@ public class TestConfigStore implements ConfigStore {
private boolean mCloudMediaInPhotoPickerEnabled = false;
private boolean mPrivateSpaceEnabled = false;
+ private boolean mIsModernPickerEnabled = false;
private boolean mPickerChoiceManagedSelectionEnabled = false;
private List<String> mAllowedCloudProviderPackages = Collections.emptyList();
private @Nullable String mDefaultCloudProviderPackage = null;
@@ -48,6 +49,10 @@ public class TestConfigStore implements ConfigStore {
notifyObservers();
}
+ public void setIsModernPickerEnabled(boolean isModernPickerEnabled) {
+ mIsModernPickerEnabled = isModernPickerEnabled;
+ }
+
/**
* Enables private space flag for PhotoPicker in test config
*/
@@ -68,6 +73,11 @@ public class TestConfigStore implements ConfigStore {
return mPrivateSpaceEnabled;
}
+ @Override
+ public boolean isModernPickerEnabled() {
+ return mIsModernPickerEnabled;
+ }
+
public void enableCloudMediaFeature() {
mCloudMediaInPhotoPickerEnabled = true;
notifyObservers();
diff --git a/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java b/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
index 8d1a16387..ba98432fc 100644
--- a/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
+++ b/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
@@ -40,6 +40,7 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Process;
import android.os.storage.StorageManager;
@@ -602,6 +603,81 @@ public class PickerSyncControllerTest {
}
@Test
+ public void testCancelledLocalSyncWork() {
+ // Init picker DB with one local media item and verify it.
+ addMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
+ mController.syncAllMediaFromLocalProvider(/* cancellationSignal=*/ null);
+ try (Cursor cr = queryMedia()) {
+ assertWithMessage(
+ "Unexpected number of media on queryMedia() after syncing local media.")
+ .that(cr.getCount()).isEqualTo(1);
+ assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
+ }
+
+ // Create a cancellation signal and mark it as cancelled
+ final CancellationSignal cancellationSignal = new CancellationSignal();
+ cancellationSignal.cancel();
+
+ // Add another local media item in local media generator
+ addMedia(mLocalMediaGenerator, LOCAL_ONLY_2);
+
+ // Check that running the sync with the cancellation does not add the new local item to the
+ // Picker DB and also does not clear the existing items in the Picker DB.
+ mController.syncAllMediaFromLocalProvider(cancellationSignal);
+
+ try (Cursor cr = queryMedia()) {
+ assertWithMessage(
+ "Unexpected number of media on queryMedia() after syncing local media.")
+ .that(cr.getCount()).isEqualTo(1);
+ assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
+ }
+ }
+
+ @Test
+ public void testCancelledCloudSyncWork() {
+ // Init picker DB with one cloud media item and verify it.
+ addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1);
+ setCloudProviderAndSyncAllMedia(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+ try (Cursor cr = queryMedia()) {
+ assertWithMessage(
+ "Unexpected number of media on queryMedia() after syncing all media.")
+ .that(cr.getCount()).isEqualTo(1);
+ assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+ }
+
+ // Create a cancellation signal and mark it as cancelled
+ final CancellationSignal cancellationSignal = new CancellationSignal();
+ cancellationSignal.cancel();
+
+ // Add another cloud media item in cloud media generator
+ addMedia(mLocalMediaGenerator, CLOUD_ONLY_2);
+
+ // Check that running the sync with the cancellation does not add the new cloud item to the
+ // Picker DB and also does not clear the existing items in the Picker DB.
+ mController.syncAllMediaFromCloudProvider(cancellationSignal);
+
+ try (Cursor cr = queryMedia()) {
+ assertWithMessage(
+ "Unexpected number of media on queryMedia() after syncing cloud media.")
+ .that(cr.getCount()).isEqualTo(1);
+ assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+ }
+ }
+
+ @Test
+ public void testCancelledAlbumSyncWork() {
+ // Create a cancellation signal and mark it as cancelled
+ final CancellationSignal cancellationSignal = new CancellationSignal();
+ cancellationSignal.cancel();
+
+ // Check that running the sync with the cancellation does not add the new local item to the
+ // Picker DB.
+ addAlbumMedia(mLocalMediaGenerator, LOCAL_ONLY_1.first, LOCAL_ONLY_1.second, ALBUM_ID_1);
+ mController.syncAlbumMediaFromLocalProvider(ALBUM_ID_1, cancellationSignal);
+ assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, true);
+ }
+
+ @Test
public void testCloudResetSync() {
setCloudProviderAndSyncAllMedia(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
diff --git a/tests/src/com/android/providers/media/photopicker/data/PickerDatabaseHelperTest.java b/tests/src/com/android/providers/media/photopicker/data/PickerDatabaseHelperTest.java
index 6d1d4de64..39d9f71d6 100644
--- a/tests/src/com/android/providers/media/photopicker/data/PickerDatabaseHelperTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/PickerDatabaseHelperTest.java
@@ -28,6 +28,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.providers.media.IsolatedContext;
+import com.android.providers.media.MediaGrants;
import org.junit.Before;
import org.junit.Test;
@@ -40,6 +41,7 @@ public class PickerDatabaseHelperTest {
private static final String TEST_PICKER_DB = "test_picker";
static final String MEDIA_TABLE = "media";
static final String ALBUM_MEDIA_TABLE = "album_media";
+ static final String GRANTS_TABLE = "media_grants";
private static final String KEY_LOCAL_ID = "local_id";
private static final String KEY_CLOUD_ID = "cloud_id";
@@ -155,6 +157,38 @@ public class PickerDatabaseHelperTest {
}
@Test
+ public void testGrantsColumns() {
+ String[] projection = new String[] {
+ MediaGrants.FILE_ID_COLUMN,
+ MediaGrants.OWNER_PACKAGE_NAME_COLUMN,
+ MediaGrants.PACKAGE_USER_ID_COLUMN
+ };
+
+ try (PickerDatabaseHelper helper = new PickerDatabaseHelperT(sIsolatedContext)) {
+ SQLiteDatabase db = helper.getWritableDatabase();
+
+ int testInputId = 1234;
+ // All fields specified
+ ContentValues values = new ContentValues();
+ values.put(MediaGrants.FILE_ID_COLUMN, testInputId);
+ values.put(MediaGrants.OWNER_PACKAGE_NAME_COLUMN, "abc");
+ values.put(MediaGrants.PACKAGE_USER_ID_COLUMN, 123);
+ assertThat(db.insert(GRANTS_TABLE, null, values))
+ .isNotEqualTo(-1);
+
+ try (Cursor cr = db.query(GRANTS_TABLE, projection, null,
+ null, null, null, null)) {
+ assertThat(cr.getCount()).isEqualTo(1);
+ while (cr.moveToNext()) {
+ assertThat(cr.getInt(0)).isEqualTo(testInputId);
+ assertThat(cr.getString(1)).isEqualTo("abc");
+ assertThat(cr.getInt(2)).isEqualTo(123);
+ }
+ }
+ }
+ }
+
+ @Test
public void testCheck_cloudOrLocal() throws Exception {
try (PickerDatabaseHelper helper = new PickerDatabaseHelperT(sIsolatedContext)) {
SQLiteDatabase db = helper.getWritableDatabase();
diff --git a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
index 1283dd133..68438b176 100644
--- a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
@@ -43,13 +43,17 @@ import static com.android.providers.media.photopicker.util.PickerDbTestUtils.MPE
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.PNG_IMAGE_MIME_TYPE;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.SIZE_BYTES;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.STANDARD_MIME_TYPE_EXTENSION;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.TEST_PACKAGE_NAME;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.VIDEO_MIME_TYPES_QUERY;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.WEBM_VIDEO_MIME_TYPE;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertAddAlbumMediaOperation;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertAddMediaOperation;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertAllMediaCursor;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertClearGrantsOperation;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertCloudAlbumCursor;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertCloudMediaCursor;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertGrantsCursor;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertInsertGrantsOperation;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertMediaStoreCursor;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertRemoveMediaOperation;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertResetAlbumMediaOperation;
@@ -60,7 +64,9 @@ import static com.android.providers.media.photopicker.util.PickerDbTestUtils.get
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getDeletedMediaCursor;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getLocalMediaCursor;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getMediaCursor;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getMediaGrantsCursor;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.queryAlbumMedia;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.queryGrants;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.queryMediaAll;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -72,6 +78,8 @@ import static org.mockito.MockitoAnnotations.initMocks;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.os.UserHandle;
import android.provider.CloudMediaProviderContract.MediaColumns;
import android.provider.Column;
import android.provider.ExportedSince;
@@ -80,6 +88,7 @@ import android.provider.MediaStore.PickerMediaColumns;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.providers.media.MediaGrants;
import com.android.providers.media.PickerUriResolver;
import com.android.providers.media.ProjectionHelper;
import com.android.providers.media.photopicker.sync.PickerSyncLockManager;
@@ -342,6 +351,55 @@ public class PickerDbFacadeTest {
}
@Test
+ public void testAddAndClearGrants() {
+ Cursor cursor1 = getMediaGrantsCursor(LOCAL_ID);
+
+ // insert a grants.
+ assertInsertGrantsOperation(mFacade, cursor1, 1);
+ // verify the grants is present in the database.
+ try (Cursor cr = queryGrants(mFacade)) {
+ assertWithMessage(
+ "Unexpected number of grants ")
+ .that(cr.getCount()).isEqualTo(1);
+ cr.moveToFirst();
+ assertGrantsCursor(cr, LOCAL_ID);
+ }
+
+ // clear all grants.
+ assertClearGrantsOperation(mFacade, 1, new String[]{TEST_PACKAGE_NAME},
+ UserHandle.myUserId());
+ // verify that the grants have been cleared.
+ try (Cursor cr = queryGrants(mFacade)) {
+ assertWithMessage(
+ "Unexpected number of grants ")
+ .that(cr.getCount()).isEqualTo(0);
+ }
+ }
+
+ @Test
+ public void testAddWhereClausesForMediaGrantsTable() {
+ // set up
+ SQLiteQueryBuilder sqb = new SQLiteQueryBuilder();
+ int testUserId = 1;
+ String[] testPackageNames = {"com.test.example"};
+
+ // adding where clause
+ PickerDbFacade.addWhereClausesForMediaGrantsTable(sqb, testUserId, testPackageNames);
+
+ // verify where clauses have been added to the query.
+ String resultQuery = sqb.buildQuery(null, null, null, null, null, null);
+
+ assertWithMessage("Query should contain clause for userId.").that(
+ resultQuery.contains(String.format("%s = %d", MediaGrants.PACKAGE_USER_ID_COLUMN,
+ testUserId))).isEqualTo(true);
+ assertWithMessage("Query should contain clause for packageNames.")
+ .that(resultQuery.contains(String.format("%s IN (\"%s\")",
+ MediaGrants.OWNER_PACKAGE_NAME_COLUMN,
+ testPackageNames[0]))).isEqualTo(
+ true);
+ }
+
+ @Test
public void testAddCloudAlbumMediaWhileCloudSyncIsRunning() {
diff --git a/tests/src/com/android/providers/media/photopicker/sync/ImmediateGrantsSyncWorkerTest.java b/tests/src/com/android/providers/media/photopicker/sync/ImmediateGrantsSyncWorkerTest.java
new file mode 100644
index 000000000..f16bd2331
--- /dev/null
+++ b/tests/src/com/android/providers/media/photopicker/sync/ImmediateGrantsSyncWorkerTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.photopicker.sync;
+
+import static com.android.providers.media.photopicker.sync.PickerSyncNotificationHelper.NOTIFICATION_CHANNEL_ID;
+import static com.android.providers.media.photopicker.sync.PickerSyncNotificationHelper.NOTIFICATION_ID;
+import static com.android.providers.media.photopicker.sync.SyncWorkerTestUtils.buildGrantsTestWorker;
+import static com.android.providers.media.photopicker.sync.SyncWorkerTestUtils.getGrantsSyncInputData;
+import static com.android.providers.media.photopicker.sync.SyncWorkerTestUtils.initializeTestWorkManager;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.work.ForegroundInfo;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkInfo;
+import androidx.work.WorkManager;
+
+import com.android.providers.media.photopicker.PickerSyncController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.concurrent.ExecutionException;
+
+
+/**
+ * Tests to verify sync of grants used in photopicker when invoked with
+ * MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP.
+ *
+ * This action is available SDK T and above hence this test has a minSdkVersion to respect this
+ * restriction.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
+public class ImmediateGrantsSyncWorkerTest {
+ @Mock
+ private PickerSyncController mMockPickerSyncController;
+
+ @Mock
+ private SyncTracker mMockGrantsSyncTracker;
+
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ initMocks(this);
+
+ // Inject mock tracker
+ SyncTrackerRegistry.setGrantsSyncTracker(mMockGrantsSyncTracker);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ initializeTestWorkManager(mContext);
+ }
+
+ @After
+ public void teardown() {
+ // Reset mock trackers
+ SyncTrackerRegistry.setLocalSyncTracker(new SyncTracker());
+ SyncTrackerRegistry.setCloudSyncTracker(new SyncTracker());
+ SyncTrackerRegistry.setGrantsSyncTracker(new SyncTracker());
+ }
+
+ @Test
+ public void testGrantsImmediateSync() throws ExecutionException, InterruptedException {
+ // Setup
+ PickerSyncController.setInstance(mMockPickerSyncController);
+ final OneTimeWorkRequest request =
+ new OneTimeWorkRequest.Builder(ImmediateGrantsSyncWorker.class)
+ .setInputData(getGrantsSyncInputData())
+ .build();
+
+ // Test run
+ final WorkManager workManager = WorkManager.getInstance(mContext);
+ workManager.enqueue(request).getResult().get();
+
+ // Verify
+ final WorkInfo workInfo = workManager.getWorkInfoById(request.getId()).get();
+ assertThat(workInfo.getState()).isEqualTo(WorkInfo.State.SUCCEEDED);
+
+ verify(mMockPickerSyncController, times(/* wantedNumberOfInvocations */ 1))
+ .executeGrantsSync(true, 1, null);
+
+ verify(mMockGrantsSyncTracker, times(/* wantedNumberOfInvocations */ 0))
+ .createSyncFuture(any());
+ verify(mMockGrantsSyncTracker, times(/* wantedNumberOfInvocations */ 1))
+ .markSyncCompleted(any());
+ }
+
+ @Test
+ public void testLocalAndCloudImmediateSyncFailure()
+ throws ExecutionException, InterruptedException {
+ // Setup
+ PickerSyncController.setInstance(null);
+ final OneTimeWorkRequest request =
+ new OneTimeWorkRequest.Builder(ImmediateGrantsSyncWorker.class)
+ .setInputData(getGrantsSyncInputData())
+ .build();
+
+ // Test run
+ final WorkManager workManager = WorkManager.getInstance(mContext);
+ workManager.enqueue(request).getResult().get();
+
+ // Verify
+ final WorkInfo workInfo = workManager.getWorkInfoById(request.getId()).get();
+ assertThat(workInfo.getState()).isEqualTo(WorkInfo.State.FAILED);
+
+ verify(mMockPickerSyncController, times(/* wantedNumberOfInvocations */ 0))
+ .executeGrantsSync(true, 1, null);
+ verify(mMockPickerSyncController, times(/* wantedNumberOfInvocations */ 0))
+ .executeGrantsSync(true, 1, null);
+
+ verify(mMockGrantsSyncTracker, times(/* wantedNumberOfInvocations */ 0))
+ .createSyncFuture(any());
+ verify(mMockGrantsSyncTracker, times(/* wantedNumberOfInvocations */ 1))
+ .markSyncCompleted(any());
+ }
+
+ @Test
+ public void testImmediateSyncWorkerOnStopped() {
+ // Setup
+ final ImmediateGrantsSyncWorker immediateGrantsSyncWorker =
+ buildGrantsTestWorker(mContext, ImmediateGrantsSyncWorker.class);
+
+ // Test onStopped
+ immediateGrantsSyncWorker.onStopped();
+
+ verify(mMockGrantsSyncTracker, times(/* wantedNumberOfInvocations */ 0))
+ .createSyncFuture(any());
+ verify(mMockGrantsSyncTracker, times(/* wantedNumberOfInvocations */ 1))
+ .markSyncCompleted(any());
+ }
+
+ @Test
+ public void testGetForegroundInfo() {
+ final ForegroundInfo foregroundInfo =
+ buildGrantsTestWorker(mContext, ImmediateGrantsSyncWorker.class)
+ .getForegroundInfo();
+
+ assertThat(foregroundInfo.getNotificationId()).isEqualTo(NOTIFICATION_ID);
+ assertThat(foregroundInfo.getNotification().getChannelId())
+ .isEqualTo(NOTIFICATION_CHANNEL_ID);
+ }
+}
diff --git a/tests/src/com/android/providers/media/photopicker/sync/PickerSyncManagerTest.java b/tests/src/com/android/providers/media/photopicker/sync/PickerSyncManagerTest.java
index 433c35fdc..857d14359 100644
--- a/tests/src/com/android/providers/media/photopicker/sync/PickerSyncManagerTest.java
+++ b/tests/src/com/android/providers/media/photopicker/sync/PickerSyncManagerTest.java
@@ -16,6 +16,7 @@
package com.android.providers.media.photopicker.sync;
+import static com.android.providers.media.photopicker.sync.PickerSyncManager.SHOULD_SYNC_GRANTS;
import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_CLOUD_ONLY;
import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_LOCAL_AND_CLOUD;
import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_LOCAL_ONLY;
@@ -34,6 +35,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
import androidx.work.ExistingPeriodicWorkPolicy;
@@ -47,6 +49,7 @@ import androidx.work.WorkRequest;
import com.android.providers.media.TestConfigStore;
import com.android.providers.media.photopicker.PickerSyncController;
+import com.android.providers.media.photopicker.data.PickerSyncRequestExtras;
import com.google.common.util.concurrent.ListenableFuture;
@@ -254,15 +257,55 @@ public class PickerSyncManagerTest {
}
@Test
+ public void testImmediateGrantsSync() {
+ setupPickerSyncManager(/* schedulePeriodicSyncs */ false);
+
+ mConfigStore.setIsModernPickerEnabled(true);
+ reset(mMockWorkManager);
+ mPickerSyncManager.syncMediaImmediately(new PickerSyncRequestExtras(/* albumId */null,
+ /* albumAuthority */ null, /* initLocalDataOnly */ true,
+ /* callingPackageUid */ 0, /* shouldSyncGrants */ true, null));
+ verify(mMockWorkManager, times(2))
+ .enqueueUniqueWork(anyString(), any(), mOneTimeWorkRequestArgumentCaptor.capture());
+
+ final List<OneTimeWorkRequest> workRequestList =
+ mOneTimeWorkRequestArgumentCaptor.getAllValues();
+ assertThat(workRequestList.size()).isEqualTo(2);
+
+ // work request 0 is for grants sync.
+ WorkRequest workRequest = workRequestList.get(0);
+ assertThat(workRequest.getWorkSpec().workerClassName)
+ .isEqualTo(ImmediateGrantsSyncWorker.class.getName());
+ assertThat(workRequest.getWorkSpec().expedited).isTrue();
+ assertThat(workRequest.getWorkSpec().isPeriodic()).isFalse();
+ assertThat(workRequest.getWorkSpec().id).isNotNull();
+ assertThat(workRequest.getWorkSpec().constraints.requiresBatteryNotLow()).isFalse();
+ assertThat(workRequest.getWorkSpec().input
+ .getInt(Intent.EXTRA_UID, -1))
+ .isEqualTo(0);
+ assertThat(workRequest.getWorkSpec().input
+ .getBoolean(SHOULD_SYNC_GRANTS, false))
+ .isEqualTo(true);
+ }
+
+ @Test
public void testImmediateLocalSync() {
+ mConfigStore.setIsModernPickerEnabled(true);
setupPickerSyncManager(/* schedulePeriodicSyncs */ false);
reset(mMockWorkManager);
- mPickerSyncManager.syncMediaImmediately(true);
- verify(mMockWorkManager, times(1))
+ mPickerSyncManager.syncMediaImmediately(new PickerSyncRequestExtras(/* albumId */null,
+ /* albumAuthority */ null, /* initLocalDataOnly */ true,
+ /* callingPackageUid */ 0, /* shouldSyncGrants */ false, null));
+ verify(mMockWorkManager, times(2))
.enqueueUniqueWork(anyString(), any(), mOneTimeWorkRequestArgumentCaptor.capture());
- final OneTimeWorkRequest workRequest = mOneTimeWorkRequestArgumentCaptor.getValue();
+ final List<OneTimeWorkRequest> workRequestList =
+ mOneTimeWorkRequestArgumentCaptor.getAllValues();
+ assertThat(workRequestList.size()).isEqualTo(2);
+
+ // work request 0 is for grants sync, so use request number 1 for local syncs.
+ WorkRequest workRequest = workRequestList.get(1);
assertThat(workRequest.getWorkSpec().workerClassName)
.isEqualTo(ImmediateSyncWorker.class.getName());
assertThat(workRequest.getWorkSpec().expedited).isTrue();
@@ -276,19 +319,24 @@ public class PickerSyncManagerTest {
@Test
public void testImmediateCloudSync() {
+ mConfigStore.setIsModernPickerEnabled(true);
setupPickerSyncManager(/* schedulePeriodicSyncs */ false);
reset(mMockWorkManager);
- mPickerSyncManager.syncMediaImmediately(false);
- verify(mMockWorkManager, times(2))
+ mPickerSyncManager.syncMediaImmediately(new PickerSyncRequestExtras(/* albumId */null,
+ /* albumAuthority */ null, /* initLocalDataOnly */ false,
+ /* callingPackageUid */ 0, /* shouldSyncGrants */ false, null));
+ verify(mMockWorkManager, times(3))
.enqueueUniqueWork(anyString(), any(), mOneTimeWorkRequestArgumentCaptor.capture());
final List<OneTimeWorkRequest> workRequestList =
mOneTimeWorkRequestArgumentCaptor.getAllValues();
- assertThat(workRequestList.size()).isEqualTo(2);
+ assertThat(workRequestList.size()).isEqualTo(3);
+
+ // work request 0 is for grants sync, 1 for local syncs and 2 for cloud syncs.
- WorkRequest localWorkRequest = workRequestList.get(0);
+ WorkRequest localWorkRequest = workRequestList.get(1);
assertThat(localWorkRequest.getWorkSpec().workerClassName)
.isEqualTo(ImmediateSyncWorker.class.getName());
assertThat(localWorkRequest.getWorkSpec().expedited).isTrue();
@@ -299,7 +347,7 @@ public class PickerSyncManagerTest {
.getInt(SYNC_WORKER_INPUT_SYNC_SOURCE, -1))
.isEqualTo(SYNC_LOCAL_ONLY);
- WorkRequest cloudWorkRequest = workRequestList.get(1);
+ WorkRequest cloudWorkRequest = workRequestList.get(2);
assertThat(cloudWorkRequest.getWorkSpec().workerClassName)
.isEqualTo(ImmediateSyncWorker.class.getName());
assertThat(cloudWorkRequest.getWorkSpec().expedited).isTrue();
diff --git a/tests/src/com/android/providers/media/photopicker/sync/SyncTrackerTests.java b/tests/src/com/android/providers/media/photopicker/sync/SyncTrackerTests.java
index ed1d117b7..1eac73a78 100644
--- a/tests/src/com/android/providers/media/photopicker/sync/SyncTrackerTests.java
+++ b/tests/src/com/android/providers/media/photopicker/sync/SyncTrackerTests.java
@@ -64,6 +64,8 @@ public class SyncTrackerTests {
@Test
public void getSyncTrackerFromRegistry() {
+ assertThat(SyncTrackerRegistry.getGrantsSyncTracker())
+ .isNotNull();
assertThat(SyncTrackerRegistry.getSyncTracker(/* isLocal */ true))
.isEqualTo(SyncTrackerRegistry.getLocalSyncTracker());
assertThat(SyncTrackerRegistry.getSyncTracker(/* isLocal */ false))
diff --git a/tests/src/com/android/providers/media/photopicker/sync/SyncWorkerTestUtils.java b/tests/src/com/android/providers/media/photopicker/sync/SyncWorkerTestUtils.java
index 7eb9326a5..52702ac7f 100644
--- a/tests/src/com/android/providers/media/photopicker/sync/SyncWorkerTestUtils.java
+++ b/tests/src/com/android/providers/media/photopicker/sync/SyncWorkerTestUtils.java
@@ -16,6 +16,7 @@
package com.android.providers.media.photopicker.sync;
+import static com.android.providers.media.photopicker.sync.PickerSyncManager.SHOULD_SYNC_GRANTS;
import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_CLOUD_ONLY;
import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_LOCAL_AND_CLOUD;
import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_LOCAL_ONLY;
@@ -26,6 +27,7 @@ import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYN
import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_WORKER_INPUT_SYNC_SOURCE;
import android.content.Context;
+import android.content.Intent;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -63,6 +65,14 @@ public class SyncWorkerTestUtils {
}
@NonNull
+ public static Data getGrantsSyncInputData() {
+ return new Data(Map.of(
+ Intent.EXTRA_UID, /* test uid */ 1,
+ SHOULD_SYNC_GRANTS, true
+ ));
+ }
+
+ @NonNull
public static Data getCloudSyncInputData() {
return new Data(Map.of(SYNC_WORKER_INPUT_SYNC_SOURCE, SYNC_CLOUD_ONLY));
}
@@ -104,4 +114,11 @@ public class SyncWorkerTestUtils {
.setInputData(getLocalAndCloudSyncInputData())
.build();
}
+
+ static <W extends Worker> W buildGrantsTestWorker(@NonNull Context context,
+ @NonNull Class<W> workerClass) {
+ return TestWorkerBuilder.from(context, workerClass)
+ .setInputData(getGrantsSyncInputData())
+ .build();
+ }
}
diff --git a/tests/src/com/android/providers/media/photopicker/util/PickerDbTestUtils.java b/tests/src/com/android/providers/media/photopicker/util/PickerDbTestUtils.java
index 989e333c7..df29f9d40 100644
--- a/tests/src/com/android/providers/media/photopicker/util/PickerDbTestUtils.java
+++ b/tests/src/com/android/providers/media/photopicker/util/PickerDbTestUtils.java
@@ -22,9 +22,11 @@ import static com.google.common.truth.Truth.assertWithMessage;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.os.UserHandle;
import android.provider.CloudMediaProviderContract;
import android.provider.MediaStore;
+import com.android.providers.media.MediaGrants;
import com.android.providers.media.PickerUriResolver;
import com.android.providers.media.photopicker.data.PickerDbFacade;
@@ -59,6 +61,7 @@ public class PickerDbTestUtils {
public static final String[] IMAGE_MIME_TYPES_QUERY = new String[]{"image/jpeg"};
public static final int STANDARD_MIME_TYPE_EXTENSION =
CloudMediaProviderContract.MediaColumns.STANDARD_MIME_TYPE_EXTENSION_GIF;
+ public static final String TEST_PACKAGE_NAME = "com.test.package";
public static final String LOCAL_PROVIDER = "com.local.provider";
public static final String CLOUD_PROVIDER = "com.cloud.provider";
@@ -76,6 +79,11 @@ public class PickerDbTestUtils {
authority);
}
+ public static Cursor queryGrants(PickerDbFacade mFacade) {
+ return mFacade.getDatabase().query(
+ "media_grants", null, null, null, null, null, null);
+ }
+
public static void assertAddMediaOperation(PickerDbFacade mFacade, String authority,
Cursor cursor, int writeCount) {
try (PickerDbFacade.DbWriteOperation operation =
@@ -94,6 +102,24 @@ public class PickerDbTestUtils {
}
}
+ public static void assertInsertGrantsOperation(PickerDbFacade mFacade,
+ Cursor cursor, int writeCount) {
+ try (PickerDbFacade.DbWriteOperation operation =
+ mFacade.beginInsertGrantsOperation()) {
+ assertWriteOperation(operation, cursor, writeCount);
+ operation.setSuccess();
+ }
+ }
+
+ public static void assertClearGrantsOperation(PickerDbFacade mFacade,
+ int writeCount, String[] packageNames, int userId) {
+ try (PickerDbFacade.DbWriteOperation operation =
+ mFacade.beginClearGrantsOperation(packageNames, userId)) {
+ assertWriteOperation(operation, null, writeCount);
+ operation.setSuccess();
+ }
+ }
+
public static void assertRemoveMediaOperation(PickerDbFacade mFacade, String authority,
Cursor cursor, int writeCount) {
try (PickerDbFacade.DbWriteOperation operation =
@@ -236,6 +262,32 @@ public class PickerDbTestUtils {
return c;
}
+ public static Cursor getMediaGrantsCursor(
+ String id) {
+ return getMediaGrantsCursor(id, TEST_PACKAGE_NAME, UserHandle.myUserId());
+ }
+
+ public static Cursor getMediaGrantsCursor(
+ String id, String packageName, int userId) {
+ String[] projectionKey =
+ new String[]{
+ MediaGrants.FILE_ID_COLUMN,
+ MediaGrants.OWNER_PACKAGE_NAME_COLUMN,
+ MediaGrants.PACKAGE_USER_ID_COLUMN
+ };
+
+ String[] projectionValue =
+ new String[]{
+ id,
+ packageName,
+ String.valueOf(userId)
+ };
+
+ MatrixCursor c = new MatrixCursor(projectionKey);
+ c.addRow(projectionValue);
+ return c;
+ }
+
public static Cursor getLocalMediaCursor(String localId, long dateTakenMs) {
return getMediaCursor(localId, dateTakenMs, GENERATION_MODIFIED, toMediaStoreUri(localId),
SIZE_BYTES, MP4_VIDEO_MIME_TYPE, STANDARD_MIME_TYPE_EXTENSION,
@@ -357,6 +409,19 @@ public class PickerDbTestUtils {
.isEqualTo(DURATION_MS);
}
+ public static void assertGrantsCursor(Cursor cursor, String fileId) {
+ assertWithMessage("Unexpected value of grants.file_id")
+ .that(cursor.getString(cursor.getColumnIndexOrThrow(
+ MediaGrants.FILE_ID_COLUMN))).isEqualTo(fileId);
+ assertWithMessage("Unexpected value of grants.owner_package_name")
+ .that(cursor.getString(cursor.getColumnIndexOrThrow(
+ MediaGrants.OWNER_PACKAGE_NAME_COLUMN))).isEqualTo(TEST_PACKAGE_NAME);
+ assertWithMessage("Unexpected value of grants.package_user_id")
+ .that(cursor.getInt(cursor.getColumnIndexOrThrow(
+ MediaGrants.PACKAGE_USER_ID_COLUMN))).isEqualTo(
+ UserHandle.myUserId());
+ }
+
public static void assertCloudMediaCursor(
Cursor cursor, String id, long dateTakenMs, String mimeType) {
assertCloudMediaCursor(cursor, id, mimeType);
diff --git a/tests/src/com/android/providers/media/photopicker/v2/PickerDataLayerV2Test.java b/tests/src/com/android/providers/media/photopicker/v2/PickerDataLayerV2Test.java
index f637858d5..fe3268148 100644
--- a/tests/src/com/android/providers/media/photopicker/v2/PickerDataLayerV2Test.java
+++ b/tests/src/com/android/providers/media/photopicker/v2/PickerDataLayerV2Test.java
@@ -25,6 +25,8 @@ import static com.android.providers.media.photopicker.util.PickerDbTestUtils.CLO
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.CLOUD_PROVIDER;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.DATE_TAKEN_MS;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.GENERATION_MODIFIED;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.GIF_IMAGE_MIME_TYPE;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.JPEG_IMAGE_MIME_TYPE;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOCAL_ID;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOCAL_ID_1;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOCAL_ID_2;
@@ -32,17 +34,19 @@ import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOC
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOCAL_ID_4;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOCAL_PROVIDER;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.MP4_VIDEO_MIME_TYPE;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.PNG_IMAGE_MIME_TYPE;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.STANDARD_MIME_TYPE_EXTENSION;
-import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertAddMediaOperation;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.TEST_PACKAGE_NAME;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertAddAlbumMediaOperation;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertAddMediaOperation;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertInsertGrantsOperation;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getAlbumCursor;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getAlbumMediaCursor;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getCloudMediaCursor;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getLocalMediaCursor;
import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getMediaCursor;
-import static com.android.providers.media.photopicker.util.PickerDbTestUtils.GIF_IMAGE_MIME_TYPE;
-import static com.android.providers.media.photopicker.util.PickerDbTestUtils.PNG_IMAGE_MIME_TYPE;
-import static com.android.providers.media.photopicker.util.PickerDbTestUtils.JPEG_IMAGE_MIME_TYPE;
+import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getMediaGrantsCursor;
+import static com.android.providers.media.photopicker.v2.PickerDataLayerV2.COLUMN_GRANTS_COUNT;
import static com.android.providers.media.photopicker.v2.model.AlbumsCursorWrapper.EMPTY_MEDIA_ID;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -64,6 +68,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Process;
+import android.os.UserHandle;
import android.provider.CloudMediaProviderContract;
import android.provider.MediaStore;
import android.test.mock.MockContentProvider;
@@ -417,6 +422,181 @@ public class PickerDataLayerV2Test {
}
@Test
+ public void testQueryLocalMediaWithGrants() {
+ Cursor cursorForMediaWithoutGrants = getMediaCursor(LOCAL_ID_1, DATE_TAKEN_MS + 1,
+ GENERATION_MODIFIED, /* mediaStoreUri */ null, /* sizeBytes */ 1,
+ MP4_VIDEO_MIME_TYPE, STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
+ Cursor cursorForMediaWithGrants = getMediaCursor(LOCAL_ID_2, DATE_TAKEN_MS,
+ GENERATION_MODIFIED,
+ /* mediaStoreUri */ null, /* sizeBytes */ 2, MP4_VIDEO_MIME_TYPE,
+ STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
+
+ assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursorForMediaWithoutGrants,
+ /* writeCount */1);
+ assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursorForMediaWithGrants,
+ /* writeCount */1);
+ int testUid = 123;
+ doReturn(mMockPackageManager)
+ .when(mMockContext).getPackageManager();
+ String[] packageNames = new String[]{TEST_PACKAGE_NAME};
+ doReturn(packageNames).when(mMockPackageManager).getPackagesForUid(testUid);
+ // insert a grant for the second item inserted in media.
+ assertInsertGrantsOperation(mFacade, getMediaGrantsCursor(LOCAL_ID_2), /* writeCount */1);
+
+ doReturn(false).when(mMockSyncController).shouldQueryCloudMedia(any());
+
+ try (Cursor cr = PickerDataLayerV2.queryMedia(
+ mMockContext, getMediaQueryExtras(Long.MAX_VALUE, Long.MAX_VALUE, /* pageSize */ 3,
+ new ArrayList<>(Arrays.asList(LOCAL_PROVIDER, CLOUD_PROVIDER)),
+ new ArrayList<>(Arrays.asList("video/*")),
+ MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP,
+ testUid))) {
+ assertWithMessage(
+ "Unexpected number of rows in media query result")
+ .that(cr.getCount()).isEqualTo(2);
+
+ // verify item with isPreGranted as false.
+ cr.moveToFirst();
+ assertMediaCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER, DATE_TAKEN_MS + 1,
+ MP4_VIDEO_MIME_TYPE, MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP,
+ /* isPreGranted */ false);
+
+ // verify item with isPreGranted as true.
+ cr.moveToNext();
+ assertMediaCursor(cr, LOCAL_ID_2, LOCAL_PROVIDER, DATE_TAKEN_MS, MP4_VIDEO_MIME_TYPE,
+ MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP,
+ /* isPreGranted */ true);
+ }
+ }
+
+ @Test
+ public void testQueryLocalMediaForPreview() {
+ Cursor cursorForMediaWithoutGrants = getMediaCursor(LOCAL_ID_1, DATE_TAKEN_MS + 1,
+ GENERATION_MODIFIED, /* mediaStoreUri */ null, /* sizeBytes */ 1,
+ MP4_VIDEO_MIME_TYPE, STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
+ Cursor cursorForMediaWithGrants = getMediaCursor(LOCAL_ID_2, DATE_TAKEN_MS,
+ GENERATION_MODIFIED,
+ /* mediaStoreUri */ null, /* sizeBytes */ 2, MP4_VIDEO_MIME_TYPE,
+ STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
+ Cursor cursorForMediaWithGrantsButDeSelected = getMediaCursor(LOCAL_ID_3, DATE_TAKEN_MS,
+ GENERATION_MODIFIED,
+ /* mediaStoreUri */ null, /* sizeBytes */ 2, MP4_VIDEO_MIME_TYPE,
+ STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
+
+ assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursorForMediaWithoutGrants,
+ /* writeCount */1);
+ assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursorForMediaWithGrants,
+ /* writeCount */1);
+ assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursorForMediaWithGrantsButDeSelected,
+ /* writeCount */1);
+
+ int testUid = 123;
+ doReturn(mMockPackageManager)
+ .when(mMockContext).getPackageManager();
+ String[] packageNames = new String[]{TEST_PACKAGE_NAME};
+ doReturn(packageNames).when(mMockPackageManager).getPackagesForUid(testUid);
+ // insert a grant for the second item inserted in media.
+ assertInsertGrantsOperation(mFacade, getMediaGrantsCursor(LOCAL_ID_2), /* writeCount */1);
+ // insert a grant for the third item inserted in media.
+ assertInsertGrantsOperation(mFacade, getMediaGrantsCursor(LOCAL_ID_3), /* writeCount */1);
+
+ doReturn(false).when(mMockSyncController).shouldQueryCloudMedia(any());
+
+ Bundle extras = getMediaQueryExtras(Long.MAX_VALUE, Long.MAX_VALUE, /* pageSize */ 3,
+ new ArrayList<>(Arrays.asList(LOCAL_PROVIDER, CLOUD_PROVIDER)),
+ new ArrayList<>(Arrays.asList("video/*")),
+ MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP,
+ testUid);
+
+ extras.putBoolean("is_preview_session", true);
+ extras.putBoolean("is_first_page", true);
+ extras.putStringArrayList("current_de_selection", new ArrayList<>(List.of(LOCAL_ID_3)));
+ extras.putStringArrayList("current_selection", new ArrayList<>(List.of(LOCAL_ID_1)));
+
+ // Expected result:
+ // 1. one item with LOCAL_ID_1 that has been added as current selection.
+ // 2. one item with LOCAL_ID_2 which is a pre-granted item.
+ // 3. item with LOCAL_ID_3 should not be included in the cursor because it is de-selected.
+
+ try (Cursor cr = PickerDataLayerV2.queryPreviewMedia(
+ mMockContext, extras)) {
+ assertWithMessage(
+ "Unexpected number of rows in media query result")
+ .that(cr.getCount()).isEqualTo(2);
+
+ // verify item with isPreGranted as false.
+ cr.moveToFirst();
+ assertMediaCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER, DATE_TAKEN_MS + 1,
+ MP4_VIDEO_MIME_TYPE, MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP,
+ /* isPreGranted */ false);
+
+ // verify item with isPreGranted as true.
+ cr.moveToNext();
+ assertMediaCursor(cr, LOCAL_ID_2, LOCAL_PROVIDER, DATE_TAKEN_MS, MP4_VIDEO_MIME_TYPE,
+ MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP,
+ /* isPreGranted */ true);
+ }
+ }
+
+ @Test
+ public void testFetchMediaGrantsCount() {
+ int testUid = 123;
+ int userId = PickerSyncController.uidToUserId(testUid);
+ doReturn(mMockPackageManager)
+ .when(mMockContext).getPackageManager();
+ String[] packageNames = new String[]{TEST_PACKAGE_NAME};
+ doReturn(packageNames).when(mMockPackageManager).getPackagesForUid(testUid);
+
+
+ // insert 2 grants corresponding to testUid.
+ assertInsertGrantsOperation(mFacade,
+ getMediaGrantsCursor(LOCAL_ID_1, TEST_PACKAGE_NAME, userId), /* writeCount */1);
+ assertInsertGrantsOperation(mFacade,
+ getMediaGrantsCursor(LOCAL_ID_2, TEST_PACKAGE_NAME, userId), /* writeCount */1);
+
+ // insert grants with different packageName or userIds.
+ String TEST_PACKAGE_NAME_2 = "package.name.two";
+ int TEST_USER_ID_2 = 10;
+
+ // same id but different packageName
+ assertInsertGrantsOperation(mFacade, getMediaGrantsCursor(LOCAL_ID_2, TEST_PACKAGE_NAME_2,
+ UserHandle.myUserId()), /* writeCount */1);
+ // same id but different userId
+ assertInsertGrantsOperation(mFacade, getMediaGrantsCursor(LOCAL_ID_2, TEST_PACKAGE_NAME,
+ TEST_USER_ID_2), /* writeCount */1);
+ // both packageName and userId different
+ assertInsertGrantsOperation(mFacade,
+ getMediaGrantsCursor(LOCAL_ID_2, TEST_PACKAGE_NAME_2, TEST_USER_ID_2), 1);
+ // every aspect different
+ assertInsertGrantsOperation(mFacade,
+ getMediaGrantsCursor(LOCAL_ID_3, TEST_PACKAGE_NAME_2, TEST_USER_ID_2), 1);
+
+ Bundle input = new Bundle();
+ input.putInt(Intent.EXTRA_UID, testUid);
+
+ try (Cursor cr = PickerDataLayerV2.fetchMediaGrantsCount(
+ mMockContext, input)) {
+
+ // cursor should only contain 1 row that represents the count.
+ assertWithMessage(
+ "Unexpected number of rows in media query result")
+ .that(cr.getCount()).isEqualTo(1);
+
+ // verify that the cursor contains the count. Ensure that only 2 grants are considered
+ // even when there were total 4 grants inserted. This ensures that the grants were
+ // filtered properly based on the packageName and UserId.
+ cr.moveToFirst();
+ int columnIndexForCount = cr.getColumnIndex(COLUMN_GRANTS_COUNT);
+ assertWithMessage(
+ "column index should not be -1.")
+ .that(columnIndexForCount).isNotEqualTo(-1);
+ assertWithMessage(
+ "Unexpected number grants count, expected to be 2.")
+ .that(cr.getInt(columnIndexForCount)).isEqualTo(2);
+ }
+ }
+
+ @Test
public void queryMediaWithCloudQueryEnabled() {
Cursor cursor1 = getMediaCursor(LOCAL_ID, DATE_TAKEN_MS, GENERATION_MODIFIED,
/* mediaStoreUri */ null, /* sizeBytes */ 1, MP4_VIDEO_MIME_TYPE,
@@ -756,7 +936,7 @@ public class PickerDataLayerV2Test {
@Test
- public void testMergedAlbumsWithCloudQueriesDisabled() {
+ public void testDefaultAlbumsWithCloudQueriesDisabled() {
Cursor cursor1 = getMediaCursor(CLOUD_ID_1, DATE_TAKEN_MS, GENERATION_MODIFIED,
/* mediaStoreUri */ null, /* sizeBytes */ 1, JPEG_IMAGE_MIME_TYPE,
STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
@@ -781,11 +961,23 @@ public class PickerDataLayerV2Test {
try (Cursor cr = PickerDataLayerV2.queryAlbums(
mMockContext, getMediaQueryExtras(Long.MAX_VALUE, Long.MAX_VALUE, /* pageSize */ 10,
new ArrayList<>(Arrays.asList(LOCAL_PROVIDER, CLOUD_PROVIDER))))) {
- // Verify that merged albums are not displayed by default when cloud albums are
- // disabled.
assertWithMessage(
"Unexpected number of rows in media query result")
- .that(cr).isNull();
+ .that(cr.getCount()).isEqualTo(2);
+
+ // Favorites album will be displayed by default
+ cr.moveToFirst();
+ assertAlbumCursor(cr,
+ /* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_FAVORITES,
+ LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
+ /* coverMediaId */ EMPTY_MEDIA_ID);
+
+ // Camera album will be displayed by default
+ cr.moveToNext();
+ assertAlbumCursor(cr,
+ /* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA,
+ LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
+ /* coverMediaId */ EMPTY_MEDIA_ID);
}
}
@@ -1073,10 +1265,24 @@ public class PickerDataLayerV2Test {
new ArrayList<>(Arrays.asList(LOCAL_PROVIDER, CLOUD_PROVIDER))))) {
assertWithMessage(
"Unexpected number of rows in media query result")
- .that(cr.getCount()).isEqualTo(1);
+ .that(cr.getCount()).isEqualTo(3);
+ // Favorites album will be displayed by default
cr.moveToFirst();
assertAlbumCursor(cr,
+ /* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_FAVORITES,
+ LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
+ /* coverMediaId */ EMPTY_MEDIA_ID);
+
+ // Camera album will be displayed by default
+ cr.moveToNext();
+ assertAlbumCursor(cr,
+ /* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA,
+ LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
+ /* coverMediaId */ EMPTY_MEDIA_ID);
+
+ cr.moveToNext();
+ assertAlbumCursor(cr,
/* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS,
LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE, /* coverMediaId */ LOCAL_ID_2);
}
@@ -1110,7 +1316,7 @@ public class PickerDataLayerV2Test {
new ArrayList<>(Arrays.asList(LOCAL_PROVIDER, CLOUD_PROVIDER))))) {
assertWithMessage(
"Unexpected number of rows in media query result")
- .that(cr.getCount()).isEqualTo(2);
+ .that(cr.getCount()).isEqualTo(3);
cr.moveToFirst();
// Favorites albums will be displayed by default
@@ -1119,6 +1325,13 @@ public class PickerDataLayerV2Test {
LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
/* coverMediaId */ EMPTY_MEDIA_ID, MediaSource.LOCAL);
+ // Camera album will be displayed by default
+ cr.moveToNext();
+ assertAlbumCursor(cr,
+ /* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA,
+ LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
+ /* coverMediaId */ EMPTY_MEDIA_ID);
+
cr.moveToNext();
assertAlbumCursor(cr,
/* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS,
@@ -1149,7 +1362,7 @@ public class PickerDataLayerV2Test {
new ArrayList<>(Arrays.asList(LOCAL_PROVIDER, CLOUD_PROVIDER))))) {
assertWithMessage(
"Unexpected number of rows in media query result")
- .that(cr.getCount()).isEqualTo(2);
+ .that(cr.getCount()).isEqualTo(3);
cr.moveToFirst();
// Favorites albums will be displayed by default
@@ -1158,6 +1371,13 @@ public class PickerDataLayerV2Test {
LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
/* coverMediaId */ EMPTY_MEDIA_ID);
+ // Camera album will be displayed by default
+ cr.moveToNext();
+ assertAlbumCursor(cr,
+ /* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA,
+ LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
+ /* coverMediaId */ EMPTY_MEDIA_ID);
+
cr.moveToNext();
assertAlbumCursor(cr,
/* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS,
@@ -1193,13 +1413,20 @@ public class PickerDataLayerV2Test {
new ArrayList<>(Arrays.asList(LOCAL_PROVIDER, CLOUD_PROVIDER))))) {
assertWithMessage(
"Unexpected number of rows in media query result")
- .that(cr.getCount()).isEqualTo(1);
+ .that(cr.getCount()).isEqualTo(2);
cr.moveToFirst();
// Favorites albums will be displayed by default
assertAlbumCursor(cr,
/* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_FAVORITES,
LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE, /* coverMediaId */ LOCAL_ID_2);
+
+ // Camera album will be displayed by default
+ cr.moveToNext();
+ assertAlbumCursor(cr,
+ /* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA,
+ LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
+ /* coverMediaId */ EMPTY_MEDIA_ID);
}
}
@@ -1231,7 +1458,7 @@ public class PickerDataLayerV2Test {
new ArrayList<>(Arrays.asList(LOCAL_PROVIDER, CLOUD_PROVIDER))))) {
assertWithMessage(
"Unexpected number of rows in media query result")
- .that(cr.getCount()).isEqualTo(2);
+ .that(cr.getCount()).isEqualTo(3);
cr.moveToFirst();
assertAlbumCursor(cr,
@@ -1239,6 +1466,13 @@ public class PickerDataLayerV2Test {
LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE, /* coverMediaId */ CLOUD_ID_1,
MediaSource.REMOTE);
+ // Camera album will be displayed by default
+ cr.moveToNext();
+ assertAlbumCursor(cr,
+ /* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA,
+ LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
+ /* coverMediaId */ EMPTY_MEDIA_ID);
+
cr.moveToNext();
// Videos album will be displayed by default
assertAlbumCursor(cr,
@@ -1272,7 +1506,7 @@ public class PickerDataLayerV2Test {
new ArrayList<>(Arrays.asList(LOCAL_PROVIDER, CLOUD_PROVIDER))))) {
assertWithMessage(
"Unexpected number of rows in media query result")
- .that(cr.getCount()).isEqualTo(2);
+ .that(cr.getCount()).isEqualTo(3);
cr.moveToFirst();
assertAlbumCursor(cr,
@@ -1280,6 +1514,13 @@ public class PickerDataLayerV2Test {
LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE, /* coverMediaId */ LOCAL_ID_1);
cr.moveToNext();
+ // Camera album will be displayed by default
+ assertAlbumCursor(cr,
+ /* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA,
+ LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
+ /* coverMediaId */ EMPTY_MEDIA_ID);
+
+ cr.moveToNext();
// Videos album will be displayed by default
assertAlbumCursor(cr,
/* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS,
@@ -1308,9 +1549,17 @@ public class PickerDataLayerV2Test {
new ArrayList<>(Arrays.asList(LOCAL_PROVIDER, CLOUD_PROVIDER))))) {
assertWithMessage(
"Unexpected number of rows in media query result")
- .that(cr.getCount()).isEqualTo(2);
+ .that(cr.getCount()).isEqualTo(3);
cr.moveToFirst();
+ // Favorites album will be displayed by default
+ assertAlbumCursor(cr,
+ /* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_FAVORITES,
+ LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
+ /* coverMediaId */ EMPTY_MEDIA_ID);
+
+ cr.moveToNext();
+ // Camera album will be displayed by default
assertAlbumCursor(cr,
/* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA,
LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE, /* coverMediaId */ LOCAL_ID_2);
@@ -1341,7 +1590,7 @@ public class PickerDataLayerV2Test {
new ArrayList<>(Arrays.asList(LOCAL_PROVIDER, CLOUD_PROVIDER))))) {
assertWithMessage(
"Unexpected number of rows in media query result")
- .that(cr.getCount()).isEqualTo(3);
+ .that(cr.getCount()).isEqualTo(4);
cr.moveToFirst();
// Favorites albums will be displayed by default
@@ -1351,6 +1600,13 @@ public class PickerDataLayerV2Test {
/* coverMediaId */ EMPTY_MEDIA_ID);
cr.moveToNext();
+ // Camera album will be displayed by default
+ assertAlbumCursor(cr,
+ /* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA,
+ LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE,
+ /* coverMediaId */ EMPTY_MEDIA_ID);
+
+ cr.moveToNext();
assertAlbumCursor(cr,
/* albumId */ CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS,
LOCAL_PROVIDER, /* dateTaken */ Long.MAX_VALUE, /* coverMediaId */ LOCAL_ID_1);
@@ -1660,11 +1916,16 @@ public class PickerDataLayerV2Test {
private static void assertMediaCursor(Cursor cursor, String id, String authority,
Long dateTaken, String mimeType) {
assertMediaCursor(cursor, id, authority, dateTaken, mimeType,
- MediaStore.ACTION_PICK_IMAGES);
+ MediaStore.ACTION_PICK_IMAGES, /* isPreGranted */ false);
}
-
private static void assertMediaCursor(Cursor cursor, String id, String authority,
Long dateTaken, String mimeType, String intent) {
+ assertMediaCursor(cursor, id, authority, dateTaken, mimeType,
+ intent, /* isPreGranted */ false);
+ }
+
+ private static void assertMediaCursor(Cursor cursor, String id, String authority,
+ Long dateTaken, String mimeType, String intent, boolean isPreGranted) {
assertWithMessage("Unexpected value of id in the media cursor.")
.that(cursor.getString(cursor.getColumnIndexOrThrow(
PickerSQLConstants.MediaResponse.MEDIA_ID.getProjectedName())))
@@ -1691,6 +1952,11 @@ public class PickerDataLayerV2Test {
.that(cursor.getString(cursor.getColumnIndexOrThrow(
PickerSQLConstants.MediaResponse.WRAPPED_URI.getProjectedName())))
.isEqualTo(expectedUri.toString());
+
+ assertWithMessage("Unexpected value of grants in the media cursor.")
+ .that(cursor.getInt(cursor.getColumnIndexOrThrow(
+ PickerSQLConstants.MediaResponse.IS_PRE_GRANTED.getProjectedName())))
+ .isEqualTo(isPreGranted ? 1 : 0);
}
private static void assertAlbumCursor(Cursor cursor, String albumId, String authority,
@@ -1748,30 +2014,46 @@ public class PickerDataLayerV2Test {
}
private Bundle getMediaQueryExtras(Long pickerId, Long dateTakenMillis, int pageSize,
- ArrayList<String> providers) {
+ List<String> providers) {
Bundle extras = new Bundle();
extras.putLong("picker_id", pickerId);
extras.putLong("date_taken_millis", dateTakenMillis);
extras.putInt("page_size", pageSize);
- extras.putStringArrayList("providers", providers);
+ extras.putStringArrayList("providers", new ArrayList<>(providers));
extras.putString("intent_action", MediaStore.ACTION_PICK_IMAGES);
return extras;
}
private Bundle getMediaQueryExtras(Long pickerId, Long dateTakenMillis, int pageSize,
- ArrayList<String> providers, ArrayList<String> mimeTypes) {
+ List<String> providers, List<String> mimeTypes) {
Bundle extras = getMediaQueryExtras(
pickerId,
dateTakenMillis,
pageSize,
providers
);
- extras.putStringArrayList("mime_types", mimeTypes);
+ extras.putStringArrayList("mime_types", new ArrayList<>(mimeTypes));
+ return extras;
+ }
+
+ private Bundle getMediaQueryExtras(
+ Long pickerId, Long dateTakenMillis, int pageSize,
+ List<String> providers, List<String> mimeTypes,
+ String intentAction, int callingUid) {
+ Bundle extras = getMediaQueryExtras(
+ pickerId,
+ dateTakenMillis,
+ pageSize,
+ providers,
+ mimeTypes
+ );
+ extras.putInt(Intent.EXTRA_UID, callingUid);
+ extras.putString("intent_action", intentAction);
return extras;
}
private Bundle getAlbumMediaQueryExtras(Long pickerId, Long dateTakenMillis, int pageSize,
- ArrayList<String> providers, String albumAuthority) {
+ List<String> providers, String albumAuthority) {
Bundle extras = getMediaQueryExtras(
pickerId,
dateTakenMillis,
diff --git a/tests/src/com/android/providers/media/util/IsoInterfaceTest.java b/tests/src/com/android/providers/media/util/IsoInterfaceTest.java
index 8805881c7..0a2a3c795 100644
--- a/tests/src/com/android/providers/media/util/IsoInterfaceTest.java
+++ b/tests/src/com/android/providers/media/util/IsoInterfaceTest.java
@@ -53,12 +53,14 @@ public class IsoInterfaceTest {
final File file = stageMp4File(R.raw.test_video);
final IsoInterface mp4 = IsoInterface.fromFile(file);
- final long[] ranges = mp4.getBoxRanges(0x746b6864); // tkhd
- assertThat(ranges.length).isEqualTo(4);
- assertThat(ranges[0]).isEqualTo(105534 + 8);
- assertThat(ranges[1]).isEqualTo(105534 + 92);
- assertThat(ranges[2]).isEqualTo(118275 + 8);
- assertThat(ranges[3]).isEqualTo(118275 + 92);
+ final long[] ranges = mp4.getBoxRanges(0x68646c72); // hdlr
+ assertThat(ranges.length).isEqualTo(6);
+ assertThat(ranges[0]).isEqualTo(105702 + 8);
+ assertThat(ranges[1]).isEqualTo(105702 + 45);
+ assertThat(ranges[2]).isEqualTo(118407 + 8);
+ assertThat(ranges[3]).isEqualTo(118407 + 45);
+ assertThat(ranges[4]).isEqualTo(135507 + 8);
+ assertThat(ranges[5]).isEqualTo(135507 + 33);
}
@Test
diff --git a/tools/photopickerV2/Android.bp b/tools/photopickerV2/Android.bp
index 7b6d84afe..a8e6ac5aa 100644
--- a/tools/photopickerV2/Android.bp
+++ b/tools/photopickerV2/Android.bp
@@ -14,6 +14,7 @@ android_app {
"androidx.activity_activity-compose",
"androidx.compose.foundation_foundation",
"androidx.compose.material3_material3",
+ "androidx.compose.runtime_runtime-livedata",
"androidx.compose.runtime_runtime",
"androidx.compose.ui_ui",
"androidx.core_core-ktx",
@@ -40,6 +41,6 @@ android_app {
],
srcs: ["src/**/*.kt"],
sdk_version: "module_current",
- target_sdk_version: "30",
- min_sdk_version: "30",
+ target_sdk_version: "34",
+ min_sdk_version: "34",
}
diff --git a/tools/photopickerV2/AndroidManifest.xml b/tools/photopickerV2/AndroidManifest.xml
index f176fa716..f861aa5ed 100644
--- a/tools/photopickerV2/AndroidManifest.xml
+++ b/tools/photopickerV2/AndroidManifest.xml
@@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.providers.media.tools.photopickerv2">
+ <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
+ <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
+ <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<application
android:allowBackup="false"
@@ -10,7 +13,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false"
android:theme="@style/Theme.PhotoPickerToolV2"
- tools:targetApi="30">
+ tools:targetApi="34">
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
diff --git a/tools/photopickerV2/res/values-af/strings.xml b/tools/photopickerV2/res/values-af/strings.xml
index 298703297..ab5655d0c 100644
--- a/tools/photopickerV2/res/values-af/strings.xml
+++ b/tools/photopickerV2/res/values-af/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"Fotokieser V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Kies prente"</string>
<string name="action_get_content" msgid="4319210475508093083">"Action Get Content"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Wys prente in volgorde"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Laat veelvuldige keuse toe"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maksimum aantal media-items"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Voer ’n geldige telling groter as een in"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Kies media"</string>
<string name="working_on_it" msgid="1373762827081252341">"Werk tans daaraan"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-am/strings.xml b/tools/photopickerV2/res/values-am/strings.xml
index 5b7f354aa..bb205eae1 100644
--- a/tools/photopickerV2/res/values-am/strings.xml
+++ b/tools/photopickerV2/res/values-am/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"ፎቶ መራጭV2"</string>
<string name="pick_images" msgid="5326258471545526911">"ምስሎችን ይምረጡ"</string>
<string name="action_get_content" msgid="4319210475508093083">"እርምጃ ይዘትን አግኝ"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"ምስሎችን በቅደም ተከተል አሳይ"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"በርካታ ምርጫን ይፍቀዱ"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"ከፍተኛ የሚዲያ ንጥሎች ቁጥር"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"ከአንድ የሚበልጥ ትክክለኛ ቁጥር ያስገቡ"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"ሚዲያ ይምረጡ"</string>
<string name="working_on_it" msgid="1373762827081252341">"በእሱ ላይ እየሰራን ነው"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ar/strings.xml b/tools/photopickerV2/res/values-ar/strings.xml
index 0cabcc8c9..f885a8666 100644
--- a/tools/photopickerV2/res/values-ar/strings.xml
+++ b/tools/photopickerV2/res/values-ar/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"اختيار الصور"</string>
<string name="action_get_content" msgid="4319210475508093083">"إجراء الحصول على محتوى"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"عرض الصور بالترتيب"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"السماع بتحديد خيارات متعددة"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"أقصى عدد من ملفات الوسائط"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"أدخِل عددًا صالحًا أكبر من واحد"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"اختيار ملفات الوسائط"</string>
<string name="working_on_it" msgid="1373762827081252341">"تجري الآن المعالجة"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-as/strings.xml b/tools/photopickerV2/res/values-as/strings.xml
index abd12f516..5b21ca845 100644
--- a/tools/photopickerV2/res/values-as/strings.xml
+++ b/tools/photopickerV2/res/values-as/strings.xml
@@ -8,10 +8,18 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"প্ৰতিচ্ছবিসমূহ বাছনি কৰক"</string>
<string name="action_get_content" msgid="4319210475508093083">"Action Get Content"</string>
+ <string name="open_document" msgid="8593796561386540777">"নথি খোলক"</string>
+ <string name="open_document_tree" msgid="8979404185180480396">"নথি ট্ৰী খোলক"</string>
<string name="display_images_in_order" msgid="7880116254468536174">"প্ৰতিচ্ছবিসমূহ ক্ৰম অনুসৰি প্ৰদৰ্শন কৰক"</string>
+ <string name="show_images_only" msgid="6365019348435132030">"কেৱল প্ৰতিচ্ছবি দেখুৱাওক"</string>
+ <string name="show_videos_only" msgid="7302756380142587762">"কেৱল ভিডিঅ’ দেখুৱাওক"</string>
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <string name="select_launch_tab" msgid="1219436289162294907">"লঞ্চ কৰা টেব বাছনি কৰক"</string>
<string name="allow_multiple_selection" msgid="3485101220559262266">"একাধিক বাছনি কৰাৰ অনুমতি দিয়ক"</string>
+ <string name="allow_custom_mime_type" msgid="770032039896650761">"কাষ্টম MIMEৰ ধৰণৰ অনুমতি দিয়ক"</string>
<string name="max_number_of_media_items" msgid="2736386927806685966">"মিডিয়া বস্তুৰ সৰ্বাধিক সংখ্যা"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"একতকৈ ডাঙৰ এটা বৈধ সংখ্যা দিয়ক"</string>
+ <string name="enter_valid_number" msgid="650407643348891234">"একতকৈ ডাঙৰ এটা মান্য সংখ্যা দিয়ক"</string>
<string name="pick_media" msgid="5269447618857205416">"মিডিয়া বাছনি কৰক"</string>
<string name="working_on_it" msgid="1373762827081252341">"এইটোত কাম কৰি আছোঁ"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-az/strings.xml b/tools/photopickerV2/res/values-az/strings.xml
index 1f497b565..899f5becc 100644
--- a/tools/photopickerV2/res/values-az/strings.xml
+++ b/tools/photopickerV2/res/values-az/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Şəkillər seçin"</string>
<string name="action_get_content" msgid="4319210475508093083">"Kontent əldə etmə əməliyyatı"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Şəkilləri ardıcıllıqla göstərin"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Bir neçə seçimə icazə verin"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maksimum sayda media elementi"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Birdən böyük etibarlı say daxil edin"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Media seçin"</string>
<string name="working_on_it" msgid="1373762827081252341">"Üzərində işləyirik"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-b+sr+Latn/strings.xml b/tools/photopickerV2/res/values-b+sr+Latn/strings.xml
index 21cafd1a4..619eba483 100644
--- a/tools/photopickerV2/res/values-b+sr+Latn/strings.xml
+++ b/tools/photopickerV2/res/values-b+sr+Latn/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker verzije 2"</string>
<string name="pick_images" msgid="5326258471545526911">"Izaberite slike"</string>
<string name="action_get_content" msgid="4319210475508093083">"Radnja preuzimanja sadržaja"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Prikazujte slike u navedenom redosledu"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Dozvolite izbor više stavki"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maksimalan broj medijskih elemenata"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Unesite važeći broj veći od jedan"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Izaberite medije"</string>
<string name="working_on_it" msgid="1373762827081252341">"Radimo na tome"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-be/strings.xml b/tools/photopickerV2/res/values-be/strings.xml
index 4cfb743e9..87d2ddf4f 100644
--- a/tools/photopickerV2/res/values-be/strings.xml
+++ b/tools/photopickerV2/res/values-be/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker, версія 2"</string>
<string name="pick_images" msgid="5326258471545526911">"Выберыце відарысы"</string>
<string name="action_get_content" msgid="4319210475508093083">"Дзеянне \"Атрымаць змесціва\""</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Паказваць відарысы па парадку"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Дазволіць выбар некалькіх фота"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Максімальная колькасць медыяфайлаў"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Увядзіце дапушчальную лічбу, якая перавышае 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Выберыце медыяфайлы"</string>
<string name="working_on_it" msgid="1373762827081252341">"Ідзе апрацоўка"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-bg/strings.xml b/tools/photopickerV2/res/values-bg/strings.xml
index 891277a3b..844129735 100644
--- a/tools/photopickerV2/res/values-bg/strings.xml
+++ b/tools/photopickerV2/res/values-bg/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker версия 2"</string>
<string name="pick_images" msgid="5326258471545526911">"Избиране на изображения"</string>
<string name="action_get_content" msgid="4319210475508093083">"Action Get Content"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Показване на изображенията в определен ред"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Разрешаване на множествен избор"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Максимален брой мултимедийни елементи"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Въведете валиден брой, по-голям от едно"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Избиране на мултимедия"</string>
<string name="working_on_it" msgid="1373762827081252341">"Работим по въпроса"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-bn/strings.xml b/tools/photopickerV2/res/values-bn/strings.xml
index 1b058ec96..558c86864 100644
--- a/tools/photopickerV2/res/values-bn/strings.xml
+++ b/tools/photopickerV2/res/values-bn/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"ছবি বেছে নিন"</string>
<string name="action_get_content" msgid="4319210475508093083">"কন্টেন্ট পাওয়ার অ্যাকশন"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"ক্রম অনুযায়ী ছবি দেখান"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"একাধিক বেছে নেওয়ার অনুমতি দিন"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"মিডিয়া আইটেমের সর্বোচ্চ সংখ্যা"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"একের থেকে বড় একটি সঠিক সংখ্যা লিখুন"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"মিডিয়া বেছে নিন"</string>
<string name="working_on_it" msgid="1373762827081252341">"এটির জন্য কাজ চলছে"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-bs/strings.xml b/tools/photopickerV2/res/values-bs/strings.xml
index e3667378f..80e5e23a2 100644
--- a/tools/photopickerV2/res/values-bs/strings.xml
+++ b/tools/photopickerV2/res/values-bs/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Odaberite slike"</string>
<string name="action_get_content" msgid="4319210475508093083">"Radnja dohvatanja sadržaja"</string>
- <string name="display_images_in_order" msgid="7880116254468536174">"Prikazuj slike prema redoslijedu"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
+ <string name="display_images_in_order" msgid="7880116254468536174">"Prikaži slike prema redoslijedu"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Dozvoli višestruki odabir"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maksimalni broj medijskih fajlova"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Unesite važeći broj veći od jedan"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Odaberite medij"</string>
<string name="working_on_it" msgid="1373762827081252341">"Radimo na tome"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ca/strings.xml b/tools/photopickerV2/res/values-ca/strings.xml
index b81f5c8e2..1c4aee51e 100644
--- a/tools/photopickerV2/res/values-ca/strings.xml
+++ b/tools/photopickerV2/res/values-ca/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Tria imatges"</string>
<string name="action_get_content" msgid="4319210475508093083">"Acció per obtenir contingut"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Mostra les imatges en ordre"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Permet la selecció múltiple"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Nombre màxim d\'elements multimèdia"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Introdueix un recompte vàlid superior a u"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Tria contingut multimèdia"</string>
<string name="working_on_it" msgid="1373762827081252341">"Hi estem treballant"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-cs/strings.xml b/tools/photopickerV2/res/values-cs/strings.xml
index 8b19e6ad9..4048de9f7 100644
--- a/tools/photopickerV2/res/values-cs/strings.xml
+++ b/tools/photopickerV2/res/values-cs/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"Výběr fotek V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Vyberte obrázky"</string>
<string name="action_get_content" msgid="4319210475508093083">"Akce načtení obsahu"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Zobrazit obrázky popořadě"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Povolit více výběrů"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maximální počet mediálních položek"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Zadejte platný počet vyšší než jedna"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Vyberte média"</string>
<string name="working_on_it" msgid="1373762827081252341">"Pracujeme na tom"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-da/strings.xml b/tools/photopickerV2/res/values-da/strings.xml
index 7d19bfa0e..e4401bfe1 100644
--- a/tools/photopickerV2/res/values-da/strings.xml
+++ b/tools/photopickerV2/res/values-da/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Vælg billeder"</string>
<string name="action_get_content" msgid="4319210475508093083">"Handlingsknap for at hente indhold"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Vis billeder i rækkefølge"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Tillad flere valg"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Det maksimale antal medieelementer"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Angiv et gyldigt antal, der er større end ét"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Vælg medie"</string>
<string name="working_on_it" msgid="1373762827081252341">"Vi arbejder på sagen"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-de/strings.xml b/tools/photopickerV2/res/values-de/strings.xml
index 366a61795..503e44ec0 100644
--- a/tools/photopickerV2/res/values-de/strings.xml
+++ b/tools/photopickerV2/res/values-de/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Bilder auswählen"</string>
<string name="action_get_content" msgid="4319210475508093083">"Aktion „Inhalte erhalten“"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Bilder in Reihenfolge anzeigen"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Mehrfachauswahl zulassen"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maximale Anzahl an Mediendateien"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Gib eine gültige Zahl größer als eins ein"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Medien auswählen"</string>
<string name="working_on_it" msgid="1373762827081252341">"Wird bearbeitet"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-el/strings.xml b/tools/photopickerV2/res/values-el/strings.xml
index 97cbee31a..00095ee18 100644
--- a/tools/photopickerV2/res/values-el/strings.xml
+++ b/tools/photopickerV2/res/values-el/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Επιλογή εικόνων"</string>
<string name="action_get_content" msgid="4319210475508093083">"Ενέργεια για τη λήψη περιεχομένου"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Προβολή εικόνων σε σειρά"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Να επιτρέπεται η πολλαπλή επιλογή"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Μέγιστος αριθμός στοιχείων μέσων"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Εισαγωγή έγκυρου πλήθους μεγαλύτερου από ένα"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Επιλογή μέσων"</string>
<string name="working_on_it" msgid="1373762827081252341">"Γίνεται επεξεργασία"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-en-rAU/strings.xml b/tools/photopickerV2/res/values-en-rAU/strings.xml
index 0a75e6e24..1028a2e98 100644
--- a/tools/photopickerV2/res/values-en-rAU/strings.xml
+++ b/tools/photopickerV2/res/values-en-rAU/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Pick images"</string>
<string name="action_get_content" msgid="4319210475508093083">"Action get content"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Display images in order"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Allow multiple selection"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Max number of media items"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Enter a valid count greater than one"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Pick media"</string>
<string name="working_on_it" msgid="1373762827081252341">"Working on it"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-en-rCA/strings.xml b/tools/photopickerV2/res/values-en-rCA/strings.xml
index 58c8ff8d2..17bef980d 100644
--- a/tools/photopickerV2/res/values-en-rCA/strings.xml
+++ b/tools/photopickerV2/res/values-en-rCA/strings.xml
@@ -8,10 +8,17 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Pick Images"</string>
<string name="action_get_content" msgid="4319210475508093083">"Action Get Content"</string>
+ <string name="open_document" msgid="8593796561386540777">"Open Document"</string>
+ <string name="open_document_tree" msgid="8979404185180480396">"Open Document Tree"</string>
<string name="display_images_in_order" msgid="7880116254468536174">"Display Images in Order"</string>
+ <string name="show_images_only" msgid="6365019348435132030">"Show Images Only"</string>
+ <string name="show_videos_only" msgid="7302756380142587762">"Show Videos Only"</string>
+ <string name="enter_mime_type" msgid="6599304148898478294">"Enter Mime Type"</string>
+ <string name="select_launch_tab" msgid="1219436289162294907">"Select Launch Tab"</string>
<string name="allow_multiple_selection" msgid="3485101220559262266">"Allow Multiple Selection"</string>
+ <string name="allow_custom_mime_type" msgid="770032039896650761">"Allow Custom Mime Type"</string>
<string name="max_number_of_media_items" msgid="2736386927806685966">"Max number of media items"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Enter a valid count greater than one"</string>
+ <string name="enter_valid_number" msgid="650407643348891234">"Enter a valid number greater than one"</string>
<string name="pick_media" msgid="5269447618857205416">"Pick Media"</string>
<string name="working_on_it" msgid="1373762827081252341">"Working on it"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-en-rGB/strings.xml b/tools/photopickerV2/res/values-en-rGB/strings.xml
index 0a75e6e24..1028a2e98 100644
--- a/tools/photopickerV2/res/values-en-rGB/strings.xml
+++ b/tools/photopickerV2/res/values-en-rGB/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Pick images"</string>
<string name="action_get_content" msgid="4319210475508093083">"Action get content"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Display images in order"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Allow multiple selection"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Max number of media items"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Enter a valid count greater than one"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Pick media"</string>
<string name="working_on_it" msgid="1373762827081252341">"Working on it"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-en-rIN/strings.xml b/tools/photopickerV2/res/values-en-rIN/strings.xml
index 0a75e6e24..1028a2e98 100644
--- a/tools/photopickerV2/res/values-en-rIN/strings.xml
+++ b/tools/photopickerV2/res/values-en-rIN/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Pick images"</string>
<string name="action_get_content" msgid="4319210475508093083">"Action get content"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Display images in order"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Allow multiple selection"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Max number of media items"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Enter a valid count greater than one"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Pick media"</string>
<string name="working_on_it" msgid="1373762827081252341">"Working on it"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-en-rXC/strings.xml b/tools/photopickerV2/res/values-en-rXC/strings.xml
index d35df81ed..5bab75f26 100644
--- a/tools/photopickerV2/res/values-en-rXC/strings.xml
+++ b/tools/photopickerV2/res/values-en-rXC/strings.xml
@@ -8,10 +8,17 @@
<string name="title_photopicker" msgid="3154038243490840415">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‎‎‏‎‏‎‎‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‎PhotoPicker V2‎‏‎‎‏‎"</string>
<string name="pick_images" msgid="5326258471545526911">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‎‏‎‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‎Pick Images‎‏‎‎‏‎"</string>
<string name="action_get_content" msgid="4319210475508093083">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‏‏‎‎‏‎‏‎‎‎‎‏‎‎‏‏‎‏‏‎Action Get Content‎‏‎‎‏‎"</string>
+ <string name="open_document" msgid="8593796561386540777">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‏‎‎‏‏‎‎‎‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‎‎‏‏‏‎‏‏‏‎‏‎‎‏‎Open Document‎‏‎‎‏‎"</string>
+ <string name="open_document_tree" msgid="8979404185180480396">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‏‎‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‎Open Document Tree‎‏‎‎‏‎"</string>
<string name="display_images_in_order" msgid="7880116254468536174">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‏‏‏‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‎Display Images in Order‎‏‎‎‏‎"</string>
+ <string name="show_images_only" msgid="6365019348435132030">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎Show Images Only‎‏‎‎‏‎"</string>
+ <string name="show_videos_only" msgid="7302756380142587762">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‎Show Videos Only‎‏‎‎‏‎"</string>
+ <string name="enter_mime_type" msgid="6599304148898478294">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‎‏‎‏‎‎‎‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‎Enter Mime Type‎‏‎‎‏‎"</string>
+ <string name="select_launch_tab" msgid="1219436289162294907">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‏‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‎Select Launch Tab‎‏‎‎‏‎"</string>
<string name="allow_multiple_selection" msgid="3485101220559262266">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‎‎‎‏‏‏‎‏‎‎Allow Multiple Selection‎‏‎‎‏‎"</string>
+ <string name="allow_custom_mime_type" msgid="770032039896650761">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‏‎‎‏‎Allow Custom Mime Type‎‏‎‎‏‎"</string>
<string name="max_number_of_media_items" msgid="2736386927806685966">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‎‏‏‎‏‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‏‏‎‎Max number of media items‎‏‎‎‏‎"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‎‎‎Enter a valid count greater than one‎‏‎‎‏‎"</string>
+ <string name="enter_valid_number" msgid="650407643348891234">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‏‎‎‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‎‎Enter a valid number greater than one‎‏‎‎‏‎"</string>
<string name="pick_media" msgid="5269447618857205416">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎Pick Media‎‏‎‎‏‎"</string>
<string name="working_on_it" msgid="1373762827081252341">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎‎‎‎‎‎‎‏‏‎‏‎‎‎‎‎‏‏‏‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎Working on it‎‏‎‎‏‎"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-es-rUS/strings.xml b/tools/photopickerV2/res/values-es-rUS/strings.xml
index 5b7d754b5..50966a66e 100644
--- a/tools/photopickerV2/res/values-es-rUS/strings.xml
+++ b/tools/photopickerV2/res/values-es-rUS/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Elegir imágenes"</string>
<string name="action_get_content" msgid="4319210475508093083">"Acción para obtener el contenido"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Mostrar imágenes en orden"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Permitir selección múltiple"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Número máximo de elementos multimedia"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Ingresa un número válido mayor que uno"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Elegir contenido multimedia"</string>
<string name="working_on_it" msgid="1373762827081252341">"Estamos trabajando en ello"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-es/strings.xml b/tools/photopickerV2/res/values-es/strings.xml
index 44615897f..70abf5001 100644
--- a/tools/photopickerV2/res/values-es/strings.xml
+++ b/tools/photopickerV2/res/values-es/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Elegir imágenes"</string>
<string name="action_get_content" msgid="4319210475508093083">"Acción para obtener el contenido"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Mostrar imágenes en orden"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Permitir selección múltiple"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Número máximo de elementos multimedia"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Introduce un número válido mayor que uno"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Elegir contenido multimedia"</string>
<string name="working_on_it" msgid="1373762827081252341">"Estamos trabajando en ello"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-et/strings.xml b/tools/photopickerV2/res/values-et/strings.xml
index 6a1c2cbcb..9f1cf52af 100644
--- a/tools/photopickerV2/res/values-et/strings.xml
+++ b/tools/photopickerV2/res/values-et/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Valige pildid"</string>
<string name="action_get_content" msgid="4319210475508093083">"ACTION_GET_CONTENT"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Piltide kuvamine järjekorras"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Mitme valimise lubamine"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Meediaüksuste maksimaalne arv"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Sisestage kehtiv arv, mis on suurem kui 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Valige meedia"</string>
<string name="working_on_it" msgid="1373762827081252341">"Toiming on pooleli"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-eu/strings.xml b/tools/photopickerV2/res/values-eu/strings.xml
index 57f2ef19a..7f1caa22f 100644
--- a/tools/photopickerV2/res/values-eu/strings.xml
+++ b/tools/photopickerV2/res/values-eu/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker (2. bertsioa)"</string>
<string name="pick_images" msgid="5326258471545526911">"Hautatu irudiak"</string>
<string name="action_get_content" msgid="4319210475508093083">"Edukia eskuratzeko ekintza"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Bistaratu irudiak ordenatuta"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Eman bat baino gehiago hautatzeko baimena"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Multimedia-elementuen gehieneko kopurua"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Idatzi bat baino handiagoa den zenbaki bat"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Hautatu multimedia-elementuak"</string>
<string name="working_on_it" msgid="1373762827081252341">"Lanean ari gara"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-fa/strings.xml b/tools/photopickerV2/res/values-fa/strings.xml
index 8f1ae3090..8d70640f8 100644
--- a/tools/photopickerV2/res/values-fa/strings.xml
+++ b/tools/photopickerV2/res/values-fa/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"انتخابگر عکس نسخه ۲"</string>
<string name="pick_images" msgid="5326258471545526911">"انتخاب تصویر"</string>
<string name="action_get_content" msgid="4319210475508093083">"محتوای مربوط به کنش دریافت"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"نمایش تصاویر به ترتیب"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"مجاز کردن انتخاب چند گزینه"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"حداکثر تعداد فایل رسانه‌ای"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"عدد معتبری که بزرگ‌تر از یک باشد وارد کنید"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"انتخاب رسانه"</string>
<string name="working_on_it" msgid="1373762827081252341">"مشغول تهیه پاسخ"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-fi/strings.xml b/tools/photopickerV2/res/values-fi/strings.xml
index 4de533d37..7d77953ab 100644
--- a/tools/photopickerV2/res/values-fi/strings.xml
+++ b/tools/photopickerV2/res/values-fi/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"Kuvanvalitsin versio 2"</string>
<string name="pick_images" msgid="5326258471545526911">"Valitse kuvia"</string>
<string name="action_get_content" msgid="4319210475508093083">"Sisällön hakutoiminto"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Näytä kuvat järjestyksessä"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Salli usean valinta"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Mediakohteiden enimmäismäärä"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Lisää sallittu määrä, joka on enemmän kuin yksi"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Valitse mediaa"</string>
<string name="working_on_it" msgid="1373762827081252341">"Työn alla"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-fr-rCA/strings.xml b/tools/photopickerV2/res/values-fr-rCA/strings.xml
index 0b2b3d7d4..e0d14e91a 100644
--- a/tools/photopickerV2/res/values-fr-rCA/strings.xml
+++ b/tools/photopickerV2/res/values-fr-rCA/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Choisir des images"</string>
<string name="action_get_content" msgid="4319210475508093083">"Action Obtenir du contenu"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Afficher les images en ordre"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Permettre la sélection multiple"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Nombre maximum d\'éléments multimédias"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Saisir un compte valide supérieur au nombre un"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Choisir un média"</string>
<string name="working_on_it" msgid="1373762827081252341">"Traitement en cours…"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-fr/strings.xml b/tools/photopickerV2/res/values-fr/strings.xml
index 285813546..18087b459 100644
--- a/tools/photopickerV2/res/values-fr/strings.xml
+++ b/tools/photopickerV2/res/values-fr/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Sélectionnez des images"</string>
<string name="action_get_content" msgid="4319210475508093083">"Action \"Obtenir du contenu\""</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Afficher les images dans l\'ordre"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Autoriser la sélection multiple"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Nombre maximal d\'éléments multimédias"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Saisissez un nombre valide supérieur à un"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Sélectionnez des éléments multimédias"</string>
<string name="working_on_it" msgid="1373762827081252341">"Opération en cours"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-gl/strings.xml b/tools/photopickerV2/res/values-gl/strings.xml
index d7a3abc07..0663f5ed0 100644
--- a/tools/photopickerV2/res/values-gl/strings.xml
+++ b/tools/photopickerV2/res/values-gl/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Escoller imaxes"</string>
<string name="action_get_content" msgid="4319210475508093083">"Acción de obter contido"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Mostrar imaxes por orde"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Permitir selección múltiple"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Número máximo de elementos multimedia"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Pon un número válido superior a un"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Escoller elementos multimedia"</string>
<string name="working_on_it" msgid="1373762827081252341">"Estamos niso"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-gu/strings.xml b/tools/photopickerV2/res/values-gu/strings.xml
index c3130ab85..10d427198 100644
--- a/tools/photopickerV2/res/values-gu/strings.xml
+++ b/tools/photopickerV2/res/values-gu/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"છબીઓ પસંદ કરો"</string>
<string name="action_get_content" msgid="4319210475508093083">"ઍક્શન કન્ટેન્ટ મેળવો"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"ક્રમમાં છબીઓ બતાવો"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"એકથી વધુ પસંદગીની મંજૂરી આપો"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"મીડિયા આઇટમની મહત્તમ સંખ્યા"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"એકથી મોટી માન્ય ગણતરી દાખલ કરો"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"મીડિયા પસંદ કરો"</string>
<string name="working_on_it" msgid="1373762827081252341">"તેના પર જ કામ કરી રહ્યાં છીએ"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-hi/strings.xml b/tools/photopickerV2/res/values-hi/strings.xml
index 5a062d535..32219e811 100644
--- a/tools/photopickerV2/res/values-hi/strings.xml
+++ b/tools/photopickerV2/res/values-hi/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"इमेज चुनें"</string>
<string name="action_get_content" msgid="4319210475508093083">"ऐक्शन गेट कॉन्टेंट"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"इमेज क्रम से दिखाएं"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"एक से ज़्यादा विकल्प चुनने की अनुमति दें"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"मीडिया आइटम की ज़्यादा से ज़्यादा संख्या"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"एक से बड़ी ऐसी संख्या डालें जो मान्य हो"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"मीडिया चुनें"</string>
<string name="working_on_it" msgid="1373762827081252341">"प्रोसेस जारी है"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-hr/strings.xml b/tools/photopickerV2/res/values-hr/strings.xml
index 1e4ce3088..70decfe13 100644
--- a/tools/photopickerV2/res/values-hr/strings.xml
+++ b/tools/photopickerV2/res/values-hr/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Odaberite slike"</string>
<string name="action_get_content" msgid="4319210475508093083">"Radnja dohvaćanja sadržaja"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Prikaži slike prema redoslijedu"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Dopusti višestruki izbor"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maksimalan broj medijskih datoteka"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Unesite važeći broj koji je veći od jedan"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Odaberite medije"</string>
<string name="working_on_it" msgid="1373762827081252341">"Radimo na tome"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-hu/strings.xml b/tools/photopickerV2/res/values-hu/strings.xml
index 0d1381486..670b8e73c 100644
--- a/tools/photopickerV2/res/values-hu/strings.xml
+++ b/tools/photopickerV2/res/values-hu/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Képek kiválasztása"</string>
<string name="action_get_content" msgid="4319210475508093083">"Tartalom kérése művelet"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Képek megjelenítése sorrendben"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Többszörös kiválasztás engedélyezése"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Médiaelemek maximális száma"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Egynél nagyobb érvényes számot adjon meg"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Média kiválasztása"</string>
<string name="working_on_it" msgid="1373762827081252341">"Dolgozunk rajta"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-hy/strings.xml b/tools/photopickerV2/res/values-hy/strings.xml
index ffc716ae4..5e0adb167 100644
--- a/tools/photopickerV2/res/values-hy/strings.xml
+++ b/tools/photopickerV2/res/values-hy/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Ընտրեք պատկերներ"</string>
<string name="action_get_content" msgid="4319210475508093083">"«Ստանալ բովանդակություն» գործողություն"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Ցուցադրել պատկերները հերթականությամբ"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Թույլատրել բազմակի ընտրությունը"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Մուլտիմեդիա ֆայլերի առավելագույն քանակը"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Մուտքագրեք մեկից մեծ վավեր թիվ"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Ընտրեք մուլտիմեդիա ֆայլեր"</string>
<string name="working_on_it" msgid="1373762827081252341">"Մշակվում է"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-in/strings.xml b/tools/photopickerV2/res/values-in/strings.xml
index 79958fd05..ab31cc41c 100644
--- a/tools/photopickerV2/res/values-in/strings.xml
+++ b/tools/photopickerV2/res/values-in/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Pilih Gambar"</string>
<string name="action_get_content" msgid="4319210475508093083">"Konten Mendapatkan Tindakan"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Tampilkan Gambar secara Berurutan"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Izinkan Lebih dari Satu Pilihan"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Jumlah maksimal item media"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Masukkan jumlah valid yang lebih besar dari satu"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Pilih Media"</string>
<string name="working_on_it" msgid="1373762827081252341">"Sedang dalam proses"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-is/strings.xml b/tools/photopickerV2/res/values-is/strings.xml
index 2b073a968..bb93f0ed0 100644
--- a/tools/photopickerV2/res/values-is/strings.xml
+++ b/tools/photopickerV2/res/values-is/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"Myndaval, 2. útg."</string>
<string name="pick_images" msgid="5326258471545526911">"Velja myndir"</string>
<string name="action_get_content" msgid="4319210475508093083">"Aðgerðin „Sækja efni“"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Birta myndir í röð"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Leyfa val á mörgum atriðum"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Hámarksfjöldi margmiðlunarskráa"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Sláðu inn gildan fjölda sem er meiri en eitt atriði"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Velja margmiðlunarefni"</string>
<string name="working_on_it" msgid="1373762827081252341">"Við erum að vinna í þessu"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-it/strings.xml b/tools/photopickerV2/res/values-it/strings.xml
index 3e14b7111..2f5d72ad2 100644
--- a/tools/photopickerV2/res/values-it/strings.xml
+++ b/tools/photopickerV2/res/values-it/strings.xml
@@ -8,10 +8,18 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Scegli le immagini"</string>
<string name="action_get_content" msgid="4319210475508093083">"Azione Ricevi contenuti"</string>
+ <string name="open_document" msgid="8593796561386540777">"Apri documento"</string>
+ <string name="open_document_tree" msgid="8979404185180480396">"Apri documento con struttura ad albero"</string>
<string name="display_images_in_order" msgid="7880116254468536174">"Mostra le immagini in ordine"</string>
+ <string name="show_images_only" msgid="6365019348435132030">"Mostra solo immagini"</string>
+ <string name="show_videos_only" msgid="7302756380142587762">"Mostra solo video"</string>
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <string name="select_launch_tab" msgid="1219436289162294907">"Seleziona scheda di lancio"</string>
<string name="allow_multiple_selection" msgid="3485101220559262266">"Consenti la selezione multipla"</string>
+ <string name="allow_custom_mime_type" msgid="770032039896650761">"Consenti tipo MIME personalizzato"</string>
<string name="max_number_of_media_items" msgid="2736386927806685966">"Numero massimo di elementi multimediali"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Inserisci un numero valido maggiore di uno"</string>
+ <string name="enter_valid_number" msgid="650407643348891234">"Inserisci un numero valido maggiore di uno"</string>
<string name="pick_media" msgid="5269447618857205416">"Scegli i contenuti multimediali"</string>
<string name="working_on_it" msgid="1373762827081252341">"Operazione in corso…"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-iw/strings.xml b/tools/photopickerV2/res/values-iw/strings.xml
index f50f32dea..031b0121d 100644
--- a/tools/photopickerV2/res/values-iw/strings.xml
+++ b/tools/photopickerV2/res/values-iw/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"בחירת תמונות"</string>
<string name="action_get_content" msgid="4319210475508093083">"תוכן להשגת פעולה"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"הצגת התמונות לפי סדר"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"הפעלת האפשרות לבחירה מרובה"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"מספר מקסימלי של קובצי מדיה"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"צריך להזין מספר תקין שגדול מאחד"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"בחירת מדיה"</string>
<string name="working_on_it" msgid="1373762827081252341">"בטיפול"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ja/strings.xml b/tools/photopickerV2/res/values-ja/strings.xml
index d829279b1..d34cc1986 100644
--- a/tools/photopickerV2/res/values-ja/strings.xml
+++ b/tools/photopickerV2/res/values-ja/strings.xml
@@ -8,10 +8,18 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"画像の選択"</string>
<string name="action_get_content" msgid="4319210475508093083">"アクション - コンテンツの取得"</string>
+ <string name="open_document" msgid="8593796561386540777">"オープン ドキュメント"</string>
+ <string name="open_document_tree" msgid="8979404185180480396">"オープン ドキュメント ツリー"</string>
<string name="display_images_in_order" msgid="7880116254468536174">"一連の画像の表示"</string>
+ <string name="show_images_only" msgid="6365019348435132030">"画像のみを表示"</string>
+ <string name="show_videos_only" msgid="7302756380142587762">"動画のみを表示"</string>
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <string name="select_launch_tab" msgid="1219436289162294907">"起動タブを選択"</string>
<string name="allow_multiple_selection" msgid="3485101220559262266">"複数の選択を許可する"</string>
+ <string name="allow_custom_mime_type" msgid="770032039896650761">"カスタムの MIME タイプを許可する"</string>
<string name="max_number_of_media_items" msgid="2736386927806685966">"メディア項目数の上限"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"1 より大きい有効な数値を入力してください"</string>
+ <string name="enter_valid_number" msgid="650407643348891234">"1 より大きい有効な数値を入力してください"</string>
<string name="pick_media" msgid="5269447618857205416">"メディアの選択"</string>
<string name="working_on_it" msgid="1373762827081252341">"処理しています"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ka/strings.xml b/tools/photopickerV2/res/values-ka/strings.xml
index cf1f976d2..dfe5e143e 100644
--- a/tools/photopickerV2/res/values-ka/strings.xml
+++ b/tools/photopickerV2/res/values-ka/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"სურათების არჩევა"</string>
<string name="action_get_content" msgid="4319210475508093083">"მოქმედება კონტენტის მისაღებად"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"სურათების ჩვენება თანმიმდევრობით"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"რამდენიმე არჩევის დაშვება"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"მედია ერთეულების მაქსიმალური რაოდენობა"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"შეიყვანეთ ვალიდური ოდენობა, რომელიც აღემატება ერთს"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"მედიის არჩევა"</string>
<string name="working_on_it" msgid="1373762827081252341">"მუშავდება"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-kk/strings.xml b/tools/photopickerV2/res/values-kk/strings.xml
index 4004111de..3dfa78412 100644
--- a/tools/photopickerV2/res/values-kk/strings.xml
+++ b/tools/photopickerV2/res/values-kk/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Суреттерді таңдау"</string>
<string name="action_get_content" msgid="4319210475508093083">"\"Контент алу\" әрекеті"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Суреттерді ретпен көрсету"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Бірнеше элемент таңдауға рұқсат ету"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Максималды медиафайлдар саны"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Бірден үлкен жарамды сан енгізіңіз."</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Медиафайл таңдау"</string>
<string name="working_on_it" msgid="1373762827081252341">"Орындалып жатыр"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-km/strings.xml b/tools/photopickerV2/res/values-km/strings.xml
index 00ee9153b..cd4bf257a 100644
--- a/tools/photopickerV2/res/values-km/strings.xml
+++ b/tools/photopickerV2/res/values-km/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"ជ្រើសរើសរូបភាព"</string>
<string name="action_get_content" msgid="4319210475508093083">"សកម្មភាពយកខ្លឹមសារ"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"បង្ហាញរូបភាពតាមលំដាប់"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"អនុញ្ញាតការជ្រើសរើសច្រើន"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"ចំនួនធាតុមេឌៀអតិបរមា"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"បញ្ចូលចំនួនធំជាងមួយដែលមានសុពលភាព"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"ជ្រើសរើសមេឌៀ"</string>
<string name="working_on_it" msgid="1373762827081252341">"កំពុងដោះស្រាយបញ្ហានេះ"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-kn/strings.xml b/tools/photopickerV2/res/values-kn/strings.xml
index b79a9e086..020950c83 100644
--- a/tools/photopickerV2/res/values-kn/strings.xml
+++ b/tools/photopickerV2/res/values-kn/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"ಚಿತ್ರಗಳನ್ನು ಆರಿಸಿ"</string>
<string name="action_get_content" msgid="4319210475508093083">"ಆ್ಯಕ್ಷನ್ ಗೆಟ್ ಕಂಟೆಂಟ್"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"ಕ್ರಮವಾಗಿ ಚಿತ್ರಗಳನ್ನು ಪ್ರದರ್ಶಿಸಿ"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"ಬಹು ಆಯ್ಕೆಗಳನ್ನು ಆರಿಸಲು ಅನುಮತಿ ನೀಡಿ"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"ಗರಿಷ್ಠ ಸಂಖ್ಯೆಯ ಮಾಧ್ಯಮ ಐಟಂಗಳು"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"ಒಂದಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಮೌಲ್ಯದ ಮಾನ್ಯವಾದ ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"ಮಾಧ್ಯಮವನ್ನು ಆರಿಸಿ"</string>
<string name="working_on_it" msgid="1373762827081252341">"ಪ್ರಯತ್ನಿಸಲಾಗುತ್ತಿದೆ"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ko/strings.xml b/tools/photopickerV2/res/values-ko/strings.xml
index e88448327..1c475a839 100644
--- a/tools/photopickerV2/res/values-ko/strings.xml
+++ b/tools/photopickerV2/res/values-ko/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"이미지 선택"</string>
<string name="action_get_content" msgid="4319210475508093083">"콘텐츠 가져오기 작업"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"순서대로 이미지 표시"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"다중 선택 허용"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"최대 미디어 항목 수"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"1보다 큰 유효한 개수 입력"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"미디어 선택"</string>
<string name="working_on_it" msgid="1373762827081252341">"진행 중"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ky/strings.xml b/tools/photopickerV2/res/values-ky/strings.xml
index 582d4ff7c..633404f52 100644
--- a/tools/photopickerV2/res/values-ky/strings.xml
+++ b/tools/photopickerV2/res/values-ky/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Сүрөттөрдү тандоо"</string>
<string name="action_get_content" msgid="4319210475508093083">"\"Контент алуу\" аракети"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Сүрөттөрдү ирети менен көрсөтүү"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Бир нече объектти тандоого уруксат берүү"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Медиа файлдардын макcималдуу саны"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Бирден чоңураак болгон жарамдуу санды киргизиңиз"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Медиа тандоо"</string>
<string name="working_on_it" msgid="1373762827081252341">"Иштетилип жатат"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-lo/strings.xml b/tools/photopickerV2/res/values-lo/strings.xml
index d9a7d854c..445a7327e 100644
--- a/tools/photopickerV2/res/values-lo/strings.xml
+++ b/tools/photopickerV2/res/values-lo/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"ເລືອກຮູບພາບ"</string>
<string name="action_get_content" msgid="4319210475508093083">"ຄຳສັ່ງຮັບເນື້ອຫາ"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"ສະແດງຮູບພາບຕາມລຳດັບ"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"ອະນຸຍາດໃຫ້ເລືອກຫຼາຍລາຍການ"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"ຈຳນວນລາຍການມີເດຍສູງສຸດ"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"ລະບຸຄ່າທີ່ຖືກຕ້ອງທີ່ຫຼາຍກວ່າ 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"ເລືອກມີເດຍ"</string>
<string name="working_on_it" msgid="1373762827081252341">"ກຳລັງດຳເນີນການ"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-lt/strings.xml b/tools/photopickerV2/res/values-lt/strings.xml
index c80797b09..5eb5dce5b 100644
--- a/tools/photopickerV2/res/values-lt/strings.xml
+++ b/tools/photopickerV2/res/values-lt/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Pasirinkite vaizdus"</string>
<string name="action_get_content" msgid="4319210475508093083">"Veiksmas „Gauti turinį“"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Rodyti vaizdus eilės tvarka"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Leisti kelis pasirinkimus"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maks. medijos elementų skaičius"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Įveskite tinkamą skaičių, didesnį nei 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Pasirinkite mediją"</string>
<string name="working_on_it" msgid="1373762827081252341">"Vykdoma"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-lv/strings.xml b/tools/photopickerV2/res/values-lv/strings.xml
index 3f4ab8a0b..575d43e3e 100644
--- a/tools/photopickerV2/res/values-lv/strings.xml
+++ b/tools/photopickerV2/res/values-lv/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Atlasīt attēlus"</string>
<string name="action_get_content" msgid="4319210475508093083">"Satura iegūšanas darbība"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Rādīt attēlus norādītajā secībā"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Atļaut vairāku vienumu atlasi"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maksimālais mediju skaits"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Ievadiet derīgu skaitu, kas ir lielāks par 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Atlasīt multivides saturu"</string>
<string name="working_on_it" msgid="1373762827081252341">"Notiek darbība…"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-mk/strings.xml b/tools/photopickerV2/res/values-mk/strings.xml
index f93c5b0e4..9d93015aa 100644
--- a/tools/photopickerV2/res/values-mk/strings.xml
+++ b/tools/photopickerV2/res/values-mk/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Изберете слики"</string>
<string name="action_get_content" msgid="4319210475508093083">"Дејство за добивање содржини"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Прикажувајте слики по редослед"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Дозволете повеќекратен избор"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Максимален број аудиовизуелни ставки"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Внесете важечки број поголем од еден"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Изберете аудиовизуелни содржини"</string>
<string name="working_on_it" msgid="1373762827081252341">"Се работи на тоа"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ml/strings.xml b/tools/photopickerV2/res/values-ml/strings.xml
index 737d1efb2..b56121f07 100644
--- a/tools/photopickerV2/res/values-ml/strings.xml
+++ b/tools/photopickerV2/res/values-ml/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"ചിത്രങ്ങൾ തിരഞ്ഞെടുക്കുക"</string>
<string name="action_get_content" msgid="4319210475508093083">"ആക്ഷൻ ഉള്ളടക്കം നേടുക"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"ചിത്രങ്ങൾ ക്രമത്തിൽ പ്രദർശിപ്പിക്കുക"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"ഒന്നിലധികം സെലക്ഷൻ അനുവദിക്കുക"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"മീഡിയ ഇനങ്ങളുടെ പരമാവധി എണ്ണം"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"ഒന്നിനേക്കാൾ വലുതും സാധുതയുള്ളതുമായ എണ്ണം നൽകുക"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"മീഡിയ തിരഞ്ഞെടുക്കുക"</string>
<string name="working_on_it" msgid="1373762827081252341">"ശ്രമിച്ചുകൊണ്ടിരിക്കുകയാണ്"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-mn/strings.xml b/tools/photopickerV2/res/values-mn/strings.xml
index 387330a0d..41aa8ea8f 100644
--- a/tools/photopickerV2/res/values-mn/strings.xml
+++ b/tools/photopickerV2/res/values-mn/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Зураг сонгох"</string>
<string name="action_get_content" msgid="4319210475508093083">"Контент авах үйлдэл"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Зургийг дарааллаар нь үзүүлэх"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Олон сонголтыг зөвшөөрөх"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Медиа зүйлийн дээд тоо"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Нэгээс дээш хүчинтэй тоо оруулна уу"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Медиа сонгох"</string>
<string name="working_on_it" msgid="1373762827081252341">"Үүн дээр ажиллаж байна"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-mr/strings.xml b/tools/photopickerV2/res/values-mr/strings.xml
index 6043f0f3d..5f3829807 100644
--- a/tools/photopickerV2/res/values-mr/strings.xml
+++ b/tools/photopickerV2/res/values-mr/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"इमेज निवडा"</string>
<string name="action_get_content" msgid="4319210475508093083">"आशय मिळवण्याची कृती"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"इमेजना क्रम यानुसार प्रदर्शित करा"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"एकाहून अधिक निवड करण्यास अनुमती द्या"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"मीडिया आयटमची कमाल संख्या"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"एकाहून मोठी असलेली वैध संख्या एंटर करा"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"मीडिया निवडा"</string>
<string name="working_on_it" msgid="1373762827081252341">"त्यावर प्रक्रिया सुरू आहे"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ms/strings.xml b/tools/photopickerV2/res/values-ms/strings.xml
index 4f7f228de..aa7dbf2f7 100644
--- a/tools/photopickerV2/res/values-ms/strings.xml
+++ b/tools/photopickerV2/res/values-ms/strings.xml
@@ -8,10 +8,18 @@
<string name="title_photopicker" msgid="3154038243490840415">"PemilihFoto V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Pilih Imej"</string>
<string name="action_get_content" msgid="4319210475508093083">"Tindakan Mendapatkan Kandungan"</string>
+ <string name="open_document" msgid="8593796561386540777">"Buka Dokumen"</string>
+ <string name="open_document_tree" msgid="8979404185180480396">"Buka Pohon Dokumen"</string>
<string name="display_images_in_order" msgid="7880116254468536174">"Paparkan Imej mengikut Urutan"</string>
+ <string name="show_images_only" msgid="6365019348435132030">"Tunjukkan Imej Sahaja"</string>
+ <string name="show_videos_only" msgid="7302756380142587762">"Tunjukkan Video Sahaja"</string>
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <string name="select_launch_tab" msgid="1219436289162294907">"Pilih Tab Pelancaran"</string>
<string name="allow_multiple_selection" msgid="3485101220559262266">"Benarkan Berbilang Pilihan"</string>
+ <string name="allow_custom_mime_type" msgid="770032039896650761">"Benarkan Jenis MIME Tersuai"</string>
<string name="max_number_of_media_items" msgid="2736386927806685966">"Bilangan maksimum item media"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Masukkan kiraan sah yang lebih banyak daripada satu"</string>
+ <string name="enter_valid_number" msgid="650407643348891234">"Masukkan nombor sah yang lebih banyak daripada satu"</string>
<string name="pick_media" msgid="5269447618857205416">"Pilih Media"</string>
<string name="working_on_it" msgid="1373762827081252341">"Sedang diusahakan"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-my/strings.xml b/tools/photopickerV2/res/values-my/strings.xml
index 545c5d1cf..53ea38b03 100644
--- a/tools/photopickerV2/res/values-my/strings.xml
+++ b/tools/photopickerV2/res/values-my/strings.xml
@@ -8,10 +8,18 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"ကြိုက်ရာ ပုံများ"</string>
<string name="action_get_content" msgid="4319210475508093083">"လုပ်ဆောင်ချက်ရယူခြင်း အကြောင်းအရာ"</string>
+ <string name="open_document" msgid="8593796561386540777">"မှတ်တမ်း ဖွင့်ရန်"</string>
+ <string name="open_document_tree" msgid="8979404185180480396">"မှတ်တမ်း ဆက်နွယ်စနစ် ဖွင့်ရန်"</string>
<string name="display_images_in_order" msgid="7880116254468536174">"ပုံများကို အစဉ်လိုက် ပြရန်"</string>
+ <string name="show_images_only" msgid="6365019348435132030">"ပုံများသာ ပြပါ"</string>
+ <string name="show_videos_only" msgid="7302756380142587762">"ဗီဒီယိုများသာ ပြပါ"</string>
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <string name="select_launch_tab" msgid="1219436289162294907">"စတင်ရန်တဘ် ရွေးရန်"</string>
<string name="allow_multiple_selection" msgid="3485101220559262266">"ရွေးချယ်မှုများစွာ ခွင့်ပြုရန်"</string>
+ <string name="allow_custom_mime_type" msgid="770032039896650761">"စိတ်ကြိုက် MIME အမျိုးအစားကို ခွင့်ပြုရန်"</string>
<string name="max_number_of_media_items" msgid="2736386927806685966">"အများဆုံး မီဒီယာဖိုင်အရေအတွက်"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"တစ်ခုထက်ပိုများသော မှန်ကန်သည့်အရေအတွက် ထည့်ရန်"</string>
+ <string name="enter_valid_number" msgid="650407643348891234">"တစ်ထက်ကြီးသော မှန်ကန်သည့်နံပါတ် ထည့်ရန်"</string>
<string name="pick_media" msgid="5269447618857205416">"ကြိုက်ရာ မီဒီယာ"</string>
<string name="working_on_it" msgid="1373762827081252341">"လုပ်ဆောင်နေသည်"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-nb/strings.xml b/tools/photopickerV2/res/values-nb/strings.xml
index f3f5008b3..bbfb221b1 100644
--- a/tools/photopickerV2/res/values-nb/strings.xml
+++ b/tools/photopickerV2/res/values-nb/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Velg bilder"</string>
<string name="action_get_content" msgid="4319210475508093083">"Handling, hent innhold"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Vis bilder i rekkefølge"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Tillat flere valg"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maksimalt antall medieelementer"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Legg inn et gyldig tall på mer enn én"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Velg medieinnhold"</string>
<string name="working_on_it" msgid="1373762827081252341">"Vi jobber med saken"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ne/strings.xml b/tools/photopickerV2/res/values-ne/strings.xml
index 8eccf250e..b7507f67f 100644
--- a/tools/photopickerV2/res/values-ne/strings.xml
+++ b/tools/photopickerV2/res/values-ne/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"फोटोहरू चयन गर्नुहोस्"</string>
<string name="action_get_content" msgid="4319210475508093083">"\"सामग्री प्राप्त गर्नुहोस्\" नामक कारबाही"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"फोटोहरू क्रमबद्ध रूपमा देखाउनुहोस्"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"एकभन्दा बढी मिडिया चयन गर्ने अनुमति दिनुहोस्"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"मिडिया सामग्रीहरूको अधिकतम सङ्ख्या"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"एकभन्दा ठुलो वैध अङ्क हाल्नुहोस्"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"मिडिया चयन गर्नुहोस्"</string>
<string name="working_on_it" msgid="1373762827081252341">"हामी यो कार्य गरिरहेका छौँ"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-nl/strings.xml b/tools/photopickerV2/res/values-nl/strings.xml
index e46074f63..c535c155b 100644
--- a/tools/photopickerV2/res/values-nl/strings.xml
+++ b/tools/photopickerV2/res/values-nl/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Afbeeldingen kiezen"</string>
<string name="action_get_content" msgid="4319210475508093083">"Actie voor content ophalen"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Afbeeldingen op volgorde tonen"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Meerdere selecties toestaan"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maximumaantal media-items"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Voer een geldig aantal in groter dan één"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Media kiezen"</string>
<string name="working_on_it" msgid="1373762827081252341">"Ik ben ermee bezig"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-or/strings.xml b/tools/photopickerV2/res/values-or/strings.xml
index 2d1c8dc64..e6d71919f 100644
--- a/tools/photopickerV2/res/values-or/strings.xml
+++ b/tools/photopickerV2/res/values-or/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"ଇମେଜ ବାଛନ୍ତୁ"</string>
<string name="action_get_content" msgid="4319210475508093083">"ବିଷୟବସ୍ତୁ ପାଇବା ପାଇଁ କାର୍ଯ୍ୟ"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"ଇମେଜଗୁଡ଼ିକୁ କ୍ରମରେ ଡିସପ୍ଲେ କରନ୍ତୁ"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"ଏକାଧିକ ଚୟନକୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"ସର୍ବାଧିକ ସଂଖ୍ୟକ ମିଡିଆ ଆଇଟମ"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"ଏକରୁ ବଡ଼ ଏକ ବୈଧ ଗଣନା ଲେଖନ୍ତୁ"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"ମିଡିଆ ବାଛନ୍ତୁ"</string>
<string name="working_on_it" msgid="1373762827081252341">"ଏହା ଉପରେ କାମ କରୁଛି"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-pa/strings.xml b/tools/photopickerV2/res/values-pa/strings.xml
index 1534b8014..4d8fd418e 100644
--- a/tools/photopickerV2/res/values-pa/strings.xml
+++ b/tools/photopickerV2/res/values-pa/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"ਚਿੱਤਰ ਚੁਣੋ"</string>
<string name="action_get_content" msgid="4319210475508093083">"ਕਾਰਵਾਈ \'ਸਮੱਗਰੀ ਪ੍ਰਾਪਤ ਕਰੋ\'"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"ਚਿੱਤਰਾਂ ਨੂੰ ਕ੍ਰਮ ਵਿੱਚ ਦਿਖਾਓ"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"ਇੱਕ ਤੋਂ ਵੱਧ ਚੋਣ ਨੂੰ ਆਗਿਆ ਦਿਓ"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"ਵੱਧੋ-ਵੱਧ ਮੀਡੀਆ ਆਈਟਮਾਂ"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"ਇੱਕ ਤੋਂ ਵੱਧ ਮੁੱਲ ਵਾਲੀ ਵੈਧ ਗਿਣਤੀ ਦਾਖਲ ਕਰੋ"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"ਮੀਡੀਆ ਚੁਣੋ"</string>
<string name="working_on_it" msgid="1373762827081252341">"ਇਸ \'ਤੇ ਕੰਮ ਚੱਲ ਰਿਹਾ ਹੈ"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-pl/strings.xml b/tools/photopickerV2/res/values-pl/strings.xml
index 0818f1828..da9ceb43a 100644
--- a/tools/photopickerV2/res/values-pl/strings.xml
+++ b/tools/photopickerV2/res/values-pl/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Wybierz obrazy"</string>
<string name="action_get_content" msgid="4319210475508093083">"Działanie Pobierz treści"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Wyświetlaj obrazy w kolejności"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Zezwalaj na wielokrotny wybór"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maksymalna liczba elementów multimedialnych"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Podaj prawidłową liczbę większą niż 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Wybierz multimedia"</string>
<string name="working_on_it" msgid="1373762827081252341">"Pracujemy nad tym"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-pt-rBR/strings.xml b/tools/photopickerV2/res/values-pt-rBR/strings.xml
index 742ca9c6a..dbfe902a8 100644
--- a/tools/photopickerV2/res/values-pt-rBR/strings.xml
+++ b/tools/photopickerV2/res/values-pt-rBR/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Escolher imagens"</string>
<string name="action_get_content" msgid="4319210475508093083">"Ação \"Receber conteúdo\""</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Mostrar imagens em ordem"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Permitir várias seleções"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Número máximo de itens de mídia"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Informe uma contagem válida maior que 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Escolher mídia"</string>
<string name="working_on_it" msgid="1373762827081252341">"Estamos trabalhando nisso"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-pt-rPT/strings.xml b/tools/photopickerV2/res/values-pt-rPT/strings.xml
index 509c3e74b..4ca39c34d 100644
--- a/tools/photopickerV2/res/values-pt-rPT/strings.xml
+++ b/tools/photopickerV2/res/values-pt-rPT/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Escolher imagens"</string>
<string name="action_get_content" msgid="4319210475508093083">"Ação Obter conteúdo"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Apresentar imagens por ordem"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Permitir seleção múltipla"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Número máx. de itens multimédia"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Introduza um número válido superior a 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Escolher multimédia"</string>
<string name="working_on_it" msgid="1373762827081252341">"A tratar disso"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-pt/strings.xml b/tools/photopickerV2/res/values-pt/strings.xml
index 742ca9c6a..dbfe902a8 100644
--- a/tools/photopickerV2/res/values-pt/strings.xml
+++ b/tools/photopickerV2/res/values-pt/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Escolher imagens"</string>
<string name="action_get_content" msgid="4319210475508093083">"Ação \"Receber conteúdo\""</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Mostrar imagens em ordem"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Permitir várias seleções"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Número máximo de itens de mídia"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Informe uma contagem válida maior que 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Escolher mídia"</string>
<string name="working_on_it" msgid="1373762827081252341">"Estamos trabalhando nisso"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ro/strings.xml b/tools/photopickerV2/res/values-ro/strings.xml
index 88b392cd8..6346fa6e3 100644
--- a/tools/photopickerV2/res/values-ro/strings.xml
+++ b/tools/photopickerV2/res/values-ro/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Alege imagini"</string>
<string name="action_get_content" msgid="4319210475508093083">"Acțiune de obținere conținut"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Afișează imaginile în ordine"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Permite selecțiile multiple"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Numărul maxim de articole media"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Introdu un număr valid, mai mare ca 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Alege articole media"</string>
<string name="working_on_it" msgid="1373762827081252341">"Se procesează"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ru/strings.xml b/tools/photopickerV2/res/values-ru/strings.xml
index 8fa354199..c17dcf4f2 100644
--- a/tools/photopickerV2/res/values-ru/strings.xml
+++ b/tools/photopickerV2/res/values-ru/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"Выбор фотографий (версия 2)"</string>
<string name="pick_images" msgid="5326258471545526911">"Выбрать изображения"</string>
<string name="action_get_content" msgid="4319210475508093083">"ACTION_GET_CONTENT"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Показать изображения по порядку"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Разрешить выбор нескольких объектов"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Максимальное число мультимедийных объектов"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Введите допустимое число больше единицы"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Выбрать мультимедиа"</string>
<string name="working_on_it" msgid="1373762827081252341">"Обработка…"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-si/strings.xml b/tools/photopickerV2/res/values-si/strings.xml
index f76391a56..384e18703 100644
--- a/tools/photopickerV2/res/values-si/strings.xml
+++ b/tools/photopickerV2/res/values-si/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"රූප තෝරන්න"</string>
<string name="action_get_content" msgid="4319210475508093083">"අන්තර්ගතය ලබා ගන්න ක්‍රියාව"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"රූප පිළිවෙලට පෙන්වන්න"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"බහු තේරීමට ඉඩ දෙන්න"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"මාධ්‍ය අයිතම උපරිම සංඛ්‍යාව"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"එකකට වඩා වලංගු සංඛ්‍යාවක් ඇතුළු කරන්න"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"මාධ්‍ය තෝරන්න"</string>
<string name="working_on_it" msgid="1373762827081252341">"ඒ පිළිබඳ කටයුතු සිදු කෙරෙයි"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-sk/strings.xml b/tools/photopickerV2/res/values-sk/strings.xml
index ce3e92c2c..f1f193cdc 100644
--- a/tools/photopickerV2/res/values-sk/strings.xml
+++ b/tools/photopickerV2/res/values-sk/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Vybrať obrázky"</string>
<string name="action_get_content" msgid="4319210475508093083">"Akcia Získať obsah"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Zobraziť obrázky v poradí"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Povoliť výber viacerých položiek"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maximálny počet mediálnych položiek"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Zadajte platný počet položiek (viac ako jednu)"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Vybrať médiá"</string>
<string name="working_on_it" msgid="1373762827081252341">"Pracujeme na tom"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-sl/strings.xml b/tools/photopickerV2/res/values-sl/strings.xml
index 0a34aedc5..02d86a731 100644
--- a/tools/photopickerV2/res/values-sl/strings.xml
+++ b/tools/photopickerV2/res/values-sl/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Izbira slik"</string>
<string name="action_get_content" msgid="4319210475508093083">"Dejanje »Pridobivanje vsebine«"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Prikaz slik v vrstnem redu"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Dovoli izbiro več možnosti"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Največje dovoljeno število predstavnostnih elementov"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Vnesite veljavno številko, večjo od ena"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Izbira predstavnosti"</string>
<string name="working_on_it" msgid="1373762827081252341">"Ukvarjamo se s tem."</string>
</resources>
diff --git a/tools/photopickerV2/res/values-sq/strings.xml b/tools/photopickerV2/res/values-sq/strings.xml
index d0a8f8aa6..073484783 100644
--- a/tools/photopickerV2/res/values-sq/strings.xml
+++ b/tools/photopickerV2/res/values-sq/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Zgjidh imazhet"</string>
<string name="action_get_content" msgid="4319210475508093083">"Veprimi: Merr përmbajtjen"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Shfaq imazhet sipas rendit"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Lejo disa zgjedhje"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Numri maksimal i artikujve të medias"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Fut një numër të vlefshëm më të madh se një"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Zgjidh median"</string>
<string name="working_on_it" msgid="1373762827081252341">"Po punohet për të"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-sr/strings.xml b/tools/photopickerV2/res/values-sr/strings.xml
index 7256a4c7b..403fb8134 100644
--- a/tools/photopickerV2/res/values-sr/strings.xml
+++ b/tools/photopickerV2/res/values-sr/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker верзије 2"</string>
<string name="pick_images" msgid="5326258471545526911">"Изаберите слике"</string>
<string name="action_get_content" msgid="4319210475508093083">"Радња преузимања садржаја"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Приказујте слике у наведеном редоследу"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Дозволите избор више ставки"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Максималан број медијских елемената"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Унесите важећи број већи од један"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Изаберите медије"</string>
<string name="working_on_it" msgid="1373762827081252341">"Радимо на томе"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-sv/strings.xml b/tools/photopickerV2/res/values-sv/strings.xml
index b8d208c57..de5504193 100644
--- a/tools/photopickerV2/res/values-sv/strings.xml
+++ b/tools/photopickerV2/res/values-sv/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"Fotoväljare v2"</string>
<string name="pick_images" msgid="5326258471545526911">"Välj bilder"</string>
<string name="action_get_content" msgid="4319210475508093083">"Åtgärden hämta innehåll"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Visa bilder i rätt ordning"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Tillåt flera val"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maximalt antal medieobjekt"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Ange ett giltigt antal som är större än detta"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Välj media"</string>
<string name="working_on_it" msgid="1373762827081252341">"Jobbar på det"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-sw/strings.xml b/tools/photopickerV2/res/values-sw/strings.xml
index 63fb50e39..62c0eb606 100644
--- a/tools/photopickerV2/res/values-sw/strings.xml
+++ b/tools/photopickerV2/res/values-sw/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Teua Picha"</string>
<string name="action_get_content" msgid="4319210475508093083">"Kitendo cha Kupata Maudhui"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Onyesha Picha katika Mpangilio"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Ruhusu Uteuzi Mwingi"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Idadi ya juu zaidi ya vipengee vya maudhui"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Weka idadi sahihi inayozidi moja"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Teua Maudhui"</string>
<string name="working_on_it" msgid="1373762827081252341">"Inashughulikiwa"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ta/strings.xml b/tools/photopickerV2/res/values-ta/strings.xml
index c91120d72..dc8b36633 100644
--- a/tools/photopickerV2/res/values-ta/strings.xml
+++ b/tools/photopickerV2/res/values-ta/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"ஃபோட்டோ தேர்வுக் கருவி V2"</string>
<string name="pick_images" msgid="5326258471545526911">"படங்களைத் தேர்ந்தெடுங்கள்"</string>
<string name="action_get_content" msgid="4319210475508093083">"உள்ளடக்கத்தைப் பெறுவதற்கான செயல்"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"படங்களை வரிசையாகக் காட்டு"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"பல தேர்வுகளை அனுமதி"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"மீடியா ஃபைல்களுக்கான அதிகபட்ச எண்ணிக்கை"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"ஒன்றை விட அதிகமான சரியான எண்ணை டைப் செய்க"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"மீடியாவைத் தேர்ந்தெடுங்கள்"</string>
<string name="working_on_it" msgid="1373762827081252341">"சிறிது நேரம் காத்திருங்கள்"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-te/strings.xml b/tools/photopickerV2/res/values-te/strings.xml
index a1e38c622..234a1ac1b 100644
--- a/tools/photopickerV2/res/values-te/strings.xml
+++ b/tools/photopickerV2/res/values-te/strings.xml
@@ -8,10 +8,18 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"ఇమేజ్‌లను ఎంచుకోండి"</string>
<string name="action_get_content" msgid="4319210475508093083">"కంటెంట్ పొందడానికి సంబంధించిన చర్య"</string>
+ <string name="open_document" msgid="8593796561386540777">"డాక్యుమెంట్‌ను తెరవండి"</string>
+ <string name="open_document_tree" msgid="8979404185180480396">"డాక్యుమెంట్ ట్రీని తెరవండి"</string>
<string name="display_images_in_order" msgid="7880116254468536174">"ఇమేజ్‌లను ఆర్డర్‌లో డిస్‌ప్లే చేయండి"</string>
+ <string name="show_images_only" msgid="6365019348435132030">"ఇమేజ్‌లను మాత్రమే చూపండి"</string>
+ <string name="show_videos_only" msgid="7302756380142587762">"వీడియోలను మాత్రమే చూడండి"</string>
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <string name="select_launch_tab" msgid="1219436289162294907">"లాంచ్ ట్యాబ్‌ను ఎంచుకోండి"</string>
<string name="allow_multiple_selection" msgid="3485101220559262266">"పలు ఎంపికలను అనుమతించండి"</string>
+ <string name="allow_custom_mime_type" msgid="770032039896650761">"అనుకూల MIME రకాన్ని అనుమతించండి"</string>
<string name="max_number_of_media_items" msgid="2736386927806685966">"మీడియా ఫైల్స్ గరిష్ఠ సంఖ్య"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"ఒకటి కంటే ఎక్కువ చెల్లుబాటు అయ్యే సంఖ్యను ఎంటర్ చేయండి"</string>
+ <string name="enter_valid_number" msgid="650407643348891234">"ఒకటి కంటే ఎక్కువ చెల్లుబాటు అయ్యే నంబర్‌ను ఎంటర్ చేయండి"</string>
<string name="pick_media" msgid="5269447618857205416">"మీడియాను ఎంచుకోండి"</string>
<string name="working_on_it" msgid="1373762827081252341">"ఆ పనిలోనే ఉన్నాను"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-th/strings.xml b/tools/photopickerV2/res/values-th/strings.xml
index 976d83441..cdfd3cf2e 100644
--- a/tools/photopickerV2/res/values-th/strings.xml
+++ b/tools/photopickerV2/res/values-th/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"เลือกรูปภาพ"</string>
<string name="action_get_content" msgid="4319210475508093083">"การดำเนินการรับเนื้อหา"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"แสดงรูปภาพตามลำดับ"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"อนุญาตให้เลือกหลายรายการ"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"จำนวนรายการสื่อสูงสุด"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"ป้อนค่าที่ถูกต้องที่มากกว่า 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"เลือกสื่อ"</string>
<string name="working_on_it" msgid="1373762827081252341">"กำลังดำเนินการ"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-tl/strings.xml b/tools/photopickerV2/res/values-tl/strings.xml
index 7b83b8b5c..5124a3187 100644
--- a/tools/photopickerV2/res/values-tl/strings.xml
+++ b/tools/photopickerV2/res/values-tl/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Pumili ng Mga Larawan"</string>
<string name="action_get_content" msgid="4319210475508093083">"Aksyong Pagkuha ng Content"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Ipakita ang Mga Larawan ayon sa Pagkakasunod-sunod"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Payagan ang Pagpili ng Marami"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Max na bilang ng media item"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Maglagay ng valid na bilang na mas malaki sa isa"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Pumili ng Media"</string>
<string name="working_on_it" msgid="1373762827081252341">"Pinoproseso na"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-tr/strings.xml b/tools/photopickerV2/res/values-tr/strings.xml
index 60efde612..541992054 100644
--- a/tools/photopickerV2/res/values-tr/strings.xml
+++ b/tools/photopickerV2/res/values-tr/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Resim Seçin"</string>
<string name="action_get_content" msgid="4319210475508093083">"İçeriği Alma İşlemi"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Resimleri Sırayla Göster"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Birden Fazla Seçime İzin Verin"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Maksimum medya öğesi sayısı"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Birden büyük geçerli bir sayı girin"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Medya Seçin"</string>
<string name="working_on_it" msgid="1373762827081252341">"Üzerinde çalışıyorum"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-uk/strings.xml b/tools/photopickerV2/res/values-uk/strings.xml
index 99479aa1a..dc3eed505 100644
--- a/tools/photopickerV2/res/values-uk/strings.xml
+++ b/tools/photopickerV2/res/values-uk/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Вибір зображень"</string>
<string name="action_get_content" msgid="4319210475508093083">"Дія для отримання контенту"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Показувати зображення по порядку"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Дозволити множинний вибір"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Максимальна кількість медіафайлів"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Введіть дійсну кількість, що перевищує одиницю"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Вибір медіафайлів"</string>
<string name="working_on_it" msgid="1373762827081252341">"Триває обробка"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-ur/strings.xml b/tools/photopickerV2/res/values-ur/strings.xml
index acdd08bcc..79750e03c 100644
--- a/tools/photopickerV2/res/values-ur/strings.xml
+++ b/tools/photopickerV2/res/values-ur/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"تصاویر منتخب کریں"</string>
<string name="action_get_content" msgid="4319210475508093083">"مواد حاصل کرنے کے لیے کارروائی"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"ترتیب میں تصاویر ڈسپلے کریں"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"متعدد انتخاب کی اجازت دیں"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"میڈیا آئٹمز کی زیادہ سے زیادہ تعداد"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"ایک سے زیادہ کی درست گنتی درج کریں"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"میڈیا منتخب کریں"</string>
<string name="working_on_it" msgid="1373762827081252341">"اس پر کام ہو رہا ہے"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-uz/strings.xml b/tools/photopickerV2/res/values-uz/strings.xml
index a3f6d6a78..6e6175e04 100644
--- a/tools/photopickerV2/res/values-uz/strings.xml
+++ b/tools/photopickerV2/res/values-uz/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Rasm tanlash"</string>
<string name="action_get_content" msgid="4319210475508093083">"Amal kontent olish"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Rasmlarni tartiblash"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Multi-tanlashga ruxsat"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Media elementlar maksimal soni"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Birdan katta yaroqli sonni kiriting"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Media tanlash"</string>
<string name="working_on_it" msgid="1373762827081252341">"Buning ustida ishlayapmiz"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-vi/strings.xml b/tools/photopickerV2/res/values-vi/strings.xml
index 054a198d3..2374d8d38 100644
--- a/tools/photopickerV2/res/values-vi/strings.xml
+++ b/tools/photopickerV2/res/values-vi/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Chọn hình ảnh"</string>
<string name="action_get_content" msgid="4319210475508093083">"ACTION_GET_CONTENT"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Hiển thị hình ảnh theo thứ tự"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Cho phép chọn nhiều mục"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Số mục nội dung nghe nhìn tối đa"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Nhập một số lượng hợp lệ, lớn hơn 1"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Chọn nội dung nghe nhìn"</string>
<string name="working_on_it" msgid="1373762827081252341">"Đang xử lý"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-zh-rCN/strings.xml b/tools/photopickerV2/res/values-zh-rCN/strings.xml
index 22c7b7091..bbb0aac40 100644
--- a/tools/photopickerV2/res/values-zh-rCN/strings.xml
+++ b/tools/photopickerV2/res/values-zh-rCN/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"选择图片"</string>
<string name="action_get_content" msgid="4319210475508093083">"操作获取内容"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"按顺序显示图片"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"允许多选"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"媒体文件数上限"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"输入大于 1 的有效数字"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"选择媒体"</string>
<string name="working_on_it" msgid="1373762827081252341">"正在处理中"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-zh-rHK/strings.xml b/tools/photopickerV2/res/values-zh-rHK/strings.xml
index b3f934f7d..362012004 100644
--- a/tools/photopickerV2/res/values-zh-rHK/strings.xml
+++ b/tools/photopickerV2/res/values-zh-rHK/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"點選圖片"</string>
<string name="action_get_content" msgid="4319210475508093083">"取得內容的動作"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"依序顯示圖片"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"允許選取多項"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"最大媒體項目數量"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"輸入大於一的有效計數"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"點選媒體"</string>
<string name="working_on_it" msgid="1373762827081252341">"正在處理"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-zh-rTW/strings.xml b/tools/photopickerV2/res/values-zh-rTW/strings.xml
index 6fa8ead20..20d5b98cc 100644
--- a/tools/photopickerV2/res/values-zh-rTW/strings.xml
+++ b/tools/photopickerV2/res/values-zh-rTW/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"相片挑選工具 V2"</string>
<string name="pick_images" msgid="5326258471545526911">"選擇圖片"</string>
<string name="action_get_content" msgid="4319210475508093083">"操作取得內容"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"依序顯示圖片"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"允許多選"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"媒體數量上限"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"輸入大於一的有效數字"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"選擇媒體"</string>
<string name="working_on_it" msgid="1373762827081252341">"處理中"</string>
</resources>
diff --git a/tools/photopickerV2/res/values-zu/strings.xml b/tools/photopickerV2/res/values-zu/strings.xml
index ecff25132..dcd226374 100644
--- a/tools/photopickerV2/res/values-zu/strings.xml
+++ b/tools/photopickerV2/res/values-zu/strings.xml
@@ -8,10 +8,25 @@
<string name="title_photopicker" msgid="3154038243490840415">"PhotoPicker V2"</string>
<string name="pick_images" msgid="5326258471545526911">"Khetha Izithombe"</string>
<string name="action_get_content" msgid="4319210475508093083">"Isenzo Sokuthola Okuqukethwe"</string>
+ <!-- no translation found for open_document (8593796561386540777) -->
+ <skip />
+ <!-- no translation found for open_document_tree (8979404185180480396) -->
+ <skip />
<string name="display_images_in_order" msgid="7880116254468536174">"Veza Izithombe Ngokulandelana"</string>
+ <!-- no translation found for show_images_only (6365019348435132030) -->
+ <skip />
+ <!-- no translation found for show_videos_only (7302756380142587762) -->
+ <skip />
+ <!-- no translation found for enter_mime_type (6599304148898478294) -->
+ <skip />
+ <!-- no translation found for select_launch_tab (1219436289162294907) -->
+ <skip />
<string name="allow_multiple_selection" msgid="3485101220559262266">"Vumela Ukukhetha Okuningi"</string>
+ <!-- no translation found for allow_custom_mime_type (770032039896650761) -->
+ <skip />
<string name="max_number_of_media_items" msgid="2736386927806685966">"Umkhawulo wesibalo wezinto zemidiya"</string>
- <string name="enter_valid_number" msgid="5077320506805084248">"Faka isibalo esivumelekile esingaphezu kokukodwa"</string>
+ <!-- no translation found for enter_valid_number (650407643348891234) -->
+ <skip />
<string name="pick_media" msgid="5269447618857205416">"Khetha Imidiya"</string>
<string name="working_on_it" msgid="1373762827081252341">"Izama ukukwenza"</string>
</resources>
diff --git a/tools/photopickerV2/res/values/strings.xml b/tools/photopickerV2/res/values/strings.xml
index d3ed2da7d..57e7d4827 100644
--- a/tools/photopickerV2/res/values/strings.xml
+++ b/tools/photopickerV2/res/values/strings.xml
@@ -9,7 +9,9 @@
<string name="action_get_content">Action Get Content</string>
<string name="open_document">Open Document</string>
<string name="open_document_tree">Open Document Tree</string>
- <string name="display_images_in_order">Display Images in Order</string>
+ <string name="create_document">Create Document</string>
+ <string name="create_file">Create File</string>
+ <string name="display_order_of_selection">Display Order of Selection</string>
<string name="show_images_only"> Show Images Only</string>
<string name="show_videos_only"> Show Videos Only</string>
<string name="enter_mime_type">Enter Mime Type</string>
@@ -20,4 +22,16 @@
<string name="enter_valid_number">Enter a valid number greater than one</string>
<string name="pick_media">Pick Media</string>
<string name="working_on_it">Working on it</string>
+ <string name="show_metadata">Show Meta Data for the media selected</string>
+ <string name="enable_preselection">Enable Pre-selection</string>
+ <string name="request_permissions">Request Permissions</string>
+ <string name="request_permissions_for">Request Permissions for:</string>
+ <string name="picker_choice_unsupported">
+ Picker Choice feature is only available for devices with Android U and above.
+ \n\nPlease upgrade your device to use this feature.</string>
+ <string name="images">Images</string>
+ <string name="videos">Videos</string>
+ <string name="both_images_and_videos">Both Images and Videos</string>
+ <string name="show_latest_selection_only">Show Latest Selection Only</string>
+
</resources> \ No newline at end of file
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIScreen.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIScreen.kt
index 407677953..1e5ab8f3b 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIScreen.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIScreen.kt
@@ -17,10 +17,11 @@ package com.android.providers.media.tools.photopickerv2.docsui
import android.app.Activity
import android.net.Uri
-import android.widget.Toast
+import android.os.Build
import android.widget.VideoView
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -53,6 +54,7 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.providers.media.tools.photopickerv2.R
import com.android.providers.media.tools.photopickerv2.utils.ButtonComponent
+import com.android.providers.media.tools.photopickerv2.utils.MetaDataDetails
import com.android.providers.media.tools.photopickerv2.utils.SwitchComponent
import com.android.providers.media.tools.photopickerv2.utils.TextFieldComponent
import com.android.providers.media.tools.photopickerv2.utils.isImage
@@ -63,6 +65,7 @@ import com.bumptech.glide.integration.compose.GlideImage
/**
* This is the screen for the DocsUI tab.
*/
+@RequiresApi(Build.VERSION_CODES.O)
@OptIn(ExperimentalGlideComposeApi::class)
@Composable
fun DocsUIScreen(docsUIViewModel: DocsUIViewModel = viewModel()) {
@@ -75,6 +78,7 @@ fun DocsUIScreen(docsUIViewModel: DocsUIViewModel = viewModel()) {
var isActionGetContentSelected by remember { mutableStateOf(true) }
var isOpenDocumentSelected by remember { mutableStateOf(false) }
+ var isCreateDocumentSelected by remember { mutableStateOf(false) }
var allowCustomMimeType by remember { mutableStateOf(false) }
var selectedMimeType by remember { mutableStateOf("") }
@@ -83,6 +87,9 @@ fun DocsUIScreen(docsUIViewModel: DocsUIViewModel = viewModel()) {
var showImagesOnly by remember { mutableStateOf(false) }
var showVideosOnly by remember { mutableStateOf(false) }
+ // Meta Data Details
+ var showMetaData by remember { mutableStateOf(false) }
+
// Color of ACTION_GET_CONTENT and OPEN_DOCUMENT button
val getContentColor = if (isActionGetContentSelected){
ButtonDefaults.buttonColors()
@@ -92,7 +99,13 @@ fun DocsUIScreen(docsUIViewModel: DocsUIViewModel = viewModel()) {
ButtonDefaults.buttonColors()
} else ButtonDefaults.buttonColors(Color.Gray)
- val openDocumentTreeColor = if (!isActionGetContentSelected && !isOpenDocumentSelected) {
+ val createDocumentColor = if (isCreateDocumentSelected) {
+ ButtonDefaults.buttonColors()
+ } else ButtonDefaults.buttonColors(Color.Gray)
+
+ val openDocumentTreeColor = if (!isActionGetContentSelected &&
+ !isOpenDocumentSelected &&
+ !isCreateDocumentSelected) {
ButtonDefaults.buttonColors()
} else ButtonDefaults.buttonColors(Color.Gray)
@@ -127,10 +140,12 @@ fun DocsUIScreen(docsUIViewModel: DocsUIViewModel = viewModel()) {
fun resetFeatureComponents(
isGetContentSelected: Boolean,
isOpenDocumentIntentSelected: Boolean,
+ isCreateDocumentIntentSelected: Boolean,
selectedButtonType: Int
) {
isActionGetContentSelected = isGetContentSelected
isOpenDocumentSelected = isOpenDocumentIntentSelected
+ isCreateDocumentSelected = isCreateDocumentIntentSelected
selectedButton = selectedButtonType
allowMultiple = false
showImagesOnly = false
@@ -167,6 +182,7 @@ fun DocsUIScreen(docsUIViewModel: DocsUIViewModel = viewModel()) {
resetFeatureComponents(
isGetContentSelected = true,
isOpenDocumentIntentSelected = false,
+ isCreateDocumentIntentSelected = false,
selectedButtonType = R.string.action_get_content
)
},
@@ -180,11 +196,12 @@ fun DocsUIScreen(docsUIViewModel: DocsUIViewModel = viewModel()) {
resetFeatureComponents(
isGetContentSelected = false,
isOpenDocumentIntentSelected = true,
+ isCreateDocumentIntentSelected = false,
selectedButtonType = R.string.open_document
)
},
modifier = Modifier.weight(1f),
- colors = openDocumentColor,
+ colors = openDocumentColor
)
}
@@ -201,12 +218,27 @@ fun DocsUIScreen(docsUIViewModel: DocsUIViewModel = viewModel()) {
resetFeatureComponents(
isGetContentSelected = false,
isOpenDocumentIntentSelected = false,
+ isCreateDocumentIntentSelected = false,
selectedButtonType = R.string.open_document_tree
)
},
modifier = Modifier.weight(1f),
colors = openDocumentTreeColor
)
+
+ ButtonComponent(
+ label = stringResource(R.string.create_document),
+ onClick = {
+ resetFeatureComponents(
+ isGetContentSelected = false,
+ isOpenDocumentIntentSelected = false,
+ isCreateDocumentIntentSelected = true,
+ selectedButtonType = R.string.create_document
+ )
+ },
+ modifier = Modifier.weight(1f),
+ colors = createDocumentColor
+ )
}
if (isActionGetContentSelected || isOpenDocumentSelected){
@@ -286,60 +318,92 @@ fun DocsUIScreen(docsUIViewModel: DocsUIViewModel = viewModel()) {
// Pick Media Button
ButtonComponent(
- label = stringResource(R.string.pick_media),
+ label = if (!isCreateDocumentSelected) {
+ stringResource(R.string.pick_media)
+ } else {
+ stringResource(R.string.create_file)
+ },
onClick = {
+
+
// Resetting the custom Mime Type Box when allowCustomMimeType is unselected
if (!allowCustomMimeType){
customMimeTypeInput = ""
}
+ /* TODO: (@adityasngh) please check the URI below and fix this intent.
+ // For CREATE_DOCUMENT intent
+ val initialUri = Uri.parse("content://some/initial/uri")
+
val errorMessage = docsUIViewModel.validateAndLaunchPicker(
isActionGetContentSelected = isActionGetContentSelected,
isOpenDocumentSelected = isOpenDocumentSelected,
+ isCreateDocumentSelected = isCreateDocumentSelected,
allowMultiple = allowMultiple,
selectedMimeType = selectedMimeType,
allowCustomMimeType = allowCustomMimeType,
customMimeTypeInput = customMimeTypeInput,
+ pickerInitialUri = initialUri,
launcher = launcher::launch
)
if (errorMessage != null) {
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
}
+ */
}
)
Spacer(modifier = Modifier.height(16.dp))
Column {
- resultMedia.forEach { uri ->
- if (isImage(context, uri)) {
- // To display image
- GlideImage(
- model = uri,
- contentDescription = null,
- modifier = Modifier
- .fillMaxWidth()
- .fillMaxSize()
- .padding(top = 8.dp)
- )
- } else {
- AndroidView(
- // To display video
- factory = { ctx ->
- VideoView(ctx).apply {
- setVideoURI(uri)
- start()
- }
- },
- modifier = Modifier
- .fillMaxWidth()
- .height(600.dp)
- .padding(top = 8.dp)
- )
+ if (isActionGetContentSelected || isOpenDocumentSelected){
+ // Switch for showing meta data
+ SwitchComponent(
+ label = stringResource(R.string.show_metadata),
+ checked = showMetaData,
+ onCheckedChange = { showMetaData = it }
+ )
+ }
+
+ if (!isCreateDocumentSelected){
+ resultMedia.forEach { uri ->
+ if (showMetaData) {
+ MetaDataDetails(
+ uri = uri,
+ contentResolver = context.contentResolver,
+ showMetaData = showMetaData,
+ inDocsUITab = true
+ )
+ }
+ if (isImage(context, uri)) {
+ // To display image
+ GlideImage(
+ model = uri,
+ contentDescription = null,
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxSize()
+ .padding(top = 8.dp)
+ )
+ } else {
+ AndroidView(
+ // To display video
+ factory = { ctx ->
+ VideoView(ctx).apply {
+ setVideoURI(uri)
+ start()
+ }
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(600.dp)
+ .padding(top = 8.dp)
+ )
+ }
+ Spacer(modifier = Modifier.height(20.dp))
+ HorizontalDivider(thickness = 6.dp)
+ Spacer(modifier = Modifier.height(17.dp))
}
- Spacer(modifier = Modifier.height(20.dp))
- HorizontalDivider(thickness = 6.dp)
- Spacer(modifier = Modifier.height(17.dp))
}
}
}
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIViewModel.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIViewModel.kt
index adc52e777..c27d08e76 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIViewModel.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/docsui/DocsUIViewModel.kt
@@ -19,6 +19,7 @@ import android.app.Application
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
+import android.provider.DocumentsContract
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,30 +43,37 @@ class DocsUIViewModel(
fun validateAndLaunchPicker(
isActionGetContentSelected: Boolean,
isOpenDocumentSelected: Boolean,
+ isCreateDocumentSelected: Boolean,
allowMultiple: Boolean,
selectedMimeType: String,
allowCustomMimeType: Boolean,
customMimeTypeInput: String,
+ pickerInitialUri: Uri,
launcher: (Intent) -> Unit
): String? {
- var finalMimeType = ""
- if (allowCustomMimeType) finalMimeType = customMimeTypeInput
- else if (selectedMimeType != "") finalMimeType = selectedMimeType
- else finalMimeType = "*/*"
-
val intent = if (isActionGetContentSelected) {
Intent(Intent.ACTION_GET_CONTENT).apply {
- type = finalMimeType
+ if (allowCustomMimeType) type = customMimeTypeInput
+ else if (selectedMimeType != "") type = selectedMimeType
+ else type = "*/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple)
addCategory(Intent.CATEGORY_OPENABLE)
}
} else if (isOpenDocumentSelected) {
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
- type = finalMimeType
+ if (allowCustomMimeType) type = customMimeTypeInput
+ else if (selectedMimeType != "") type = selectedMimeType
+ else type = "*/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple)
addCategory(Intent.CATEGORY_OPENABLE)
}
+ } else if (isCreateDocumentSelected){
+ Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "application/pdf" // TODO: (@adityasngh) please review and make it generic.
+ putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
+ }
} else {
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
}
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/navigation/NavGraph.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/navigation/NavGraph.kt
index d8a87138a..eb423e29c 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/navigation/NavGraph.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/navigation/NavGraph.kt
@@ -15,7 +15,6 @@
*/
package com.android.providers.media.tools.photopickerv2.navigation
-import android.annotation.SuppressLint
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -42,7 +41,6 @@ import com.android.providers.media.tools.photopickerv2.utils.NavigationComponent
* MainScreen sets up the Scaffold with a bottom navigation bar
* and hosts the NavGraph for navigation between the tabs.
*/
-@SuppressLint
@Composable
fun MainScreen() {
val navController = rememberNavController()
@@ -80,7 +78,6 @@ fun MainScreen() {
* DocsUI
* PickerChoice
*/
-@SuppressLint
@Composable
fun NavGraph(navController: NavHostController, modifier: Modifier = Modifier) {
NavHost(
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerScreen.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerScreen.kt
index f72a110c3..d45a9370f 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerScreen.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerScreen.kt
@@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -36,9 +35,11 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -58,6 +59,7 @@ import com.android.providers.media.tools.photopickerv2.utils.ButtonComponent
import com.android.providers.media.tools.photopickerv2.utils.DropdownList
import com.android.providers.media.tools.photopickerv2.utils.ErrorMessage
import com.android.providers.media.tools.photopickerv2.utils.LaunchLocation
+import com.android.providers.media.tools.photopickerv2.utils.MetaDataDetails
import com.android.providers.media.tools.photopickerv2.utils.PhotoPickerTitle
import com.android.providers.media.tools.photopickerv2.utils.SwitchComponent
import com.android.providers.media.tools.photopickerv2.utils.TextFieldComponent
@@ -76,6 +78,9 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
var isOrderSelectionEnabled by remember { mutableStateOf(false) }
var allowMultiple by remember { mutableStateOf(false) }
var isActionGetContentSelected by remember { mutableStateOf(false) }
+ var selectedLaunchTab by remember { mutableStateOf(LaunchLocation.PHOTOS_TAB.name) }
+ var accentColor by remember { mutableStateOf("#FF6200EE") } // default
+
var allowCustomMimeType by remember { mutableStateOf(false) }
var selectedMimeType by remember { mutableStateOf("") }
@@ -83,11 +88,10 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
var showImagesOnly by remember { mutableStateOf(false) }
var showVideosOnly by remember { mutableStateOf(false) }
- var selectedLaunchTab by remember { mutableStateOf(LaunchLocation.PHOTOS_TAB.name) }
// We can only take string as an input, not an int using OutlinedTextField
var maxSelectionInput by remember { mutableStateOf("10") }
- var maxMediaItemsDisplayed by remember { mutableStateOf(10) } // default items
+ var maxMediaItemsDisplayed by remember { mutableIntStateOf(10) } // default items
var selectionErrorMessage by remember { mutableStateOf("") }
var maxSelectionLimitError by remember { mutableStateOf("") }
@@ -95,8 +99,13 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
// The Pick Images intent is selected by default
var selectedButton by remember { mutableStateOf<Int?>(R.string.pick_images) }
+ // Meta Data Details
+ var showMetaData by remember { mutableStateOf(false) }
+
+ var isPreSelectionEnabled by remember { mutableStateOf(false) }
+
// Color of PickImages and ACTION_GET_CONTENT button
- val getContentColor = if (isActionGetContentSelected){
+ val getContentColor = if (isActionGetContentSelected) {
ButtonDefaults.buttonColors()
} else ButtonDefaults.buttonColors(Color.Gray)
@@ -104,7 +113,6 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
ButtonDefaults.buttonColors()
} else ButtonDefaults.buttonColors(Color.Gray)
-
// For handling the result of the photo picking activity
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
@@ -142,7 +150,9 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
allowMultiple = false
showImagesOnly = false
showVideosOnly = false
+ showMetaData = false
selectedMimeType = ""
+ accentColor = "#FF6200EE"
resetMedia(photoPickerViewModel)
isOrderSelectionEnabled = false
maxSelectionInput = "10" // resetting the max Selection limit to default
@@ -150,6 +160,7 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
allowCustomMimeType = false
customMimeTypeInput = ""
selectedLaunchTab = LaunchLocation.PHOTOS_TAB.toString()
+ isPreSelectionEnabled = false
}
Column(
@@ -182,7 +193,7 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
)
// ACTION_GET_CONTENT will only support "images/*" and "videos/*"
- // in the Photo picker tab
+ // in the PhotoPicker tab
ButtonComponent(
label = stringResource(id = R.string.action_get_content),
onClick = {
@@ -197,16 +208,15 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
}
if (!isActionGetContentSelected) {
- // Display Images in Order
+ // Display Order of Selection
SwitchComponent(
- label = stringResource(id = R.string.display_images_in_order),
+ label = stringResource(id = R.string.display_order_of_selection),
checked = isOrderSelectionEnabled,
onCheckedChange = { isOrderSelectionEnabled = it }
)
- Spacer(modifier = Modifier.height(8.dp))
}
- if (!allowCustomMimeType || isActionGetContentSelected){
+ if (!allowCustomMimeType || isActionGetContentSelected) {
// SHOW ONLY IMAGES OR VIDEOS
Row(
modifier = Modifier
@@ -214,7 +224,7 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
.padding(vertical = 5.dp),
verticalAlignment = Alignment.CenterVertically
) {
- Column (modifier = Modifier.weight(1f)){
+ Column(modifier = Modifier.weight(1f)) {
SwitchComponent(
label = stringResource(R.string.show_images_only),
checked = showImagesOnly,
@@ -224,7 +234,7 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
showVideosOnly = false
selectedMimeType = "image/*"
} else if (!showImagesOnly && !showVideosOnly) {
- selectedMimeType = "*/*"
+ selectedMimeType = ""
}
}
)
@@ -232,7 +242,7 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
Spacer(modifier = Modifier.width(6.dp))
- Column (modifier = Modifier.weight(1f)){
+ Column(modifier = Modifier.weight(1f)) {
SwitchComponent(
label = stringResource(R.string.show_videos_only),
checked = showVideosOnly,
@@ -242,7 +252,7 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
showImagesOnly = false
selectedMimeType = "video/*"
} else if (!showImagesOnly && !showVideosOnly) {
- selectedMimeType = "*/*"
+ selectedMimeType = ""
}
}
)
@@ -250,7 +260,7 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
}
}
- if (!isActionGetContentSelected){
+ if (!isActionGetContentSelected) {
// Allow Custom Mime Type
SwitchComponent(
label = stringResource(id = R.string.allow_custom_mime_type),
@@ -260,7 +270,7 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
}
)
- if (allowCustomMimeType){
+ if (allowCustomMimeType) {
TextFieldComponent(
// Custom Mime Type Input
value = customMimeTypeInput,
@@ -276,13 +286,33 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
// Launch Tab
DropdownList(
label = stringResource(id = R.string.select_launch_tab),
- options = LaunchLocation.values().map { it.name },
+ options = LaunchLocation.entries.map { it.name },
selectedOption = selectedLaunchTab,
onOptionSelected = { selectedLaunchTab = it },
enabled = true
)
}
+ if (!isActionGetContentSelected){
+ // Accent Color
+ TextFieldComponent(
+ value = accentColor,
+ onValueChange = { color ->
+ accentColor = color
+ },
+ label = "Accent Color"
+ )
+ }
+
+ if (!isActionGetContentSelected) {
+ // Switch for enabling pre-selection
+ SwitchComponent(
+ label = stringResource(R.string.enable_preselection),
+ checked = isPreSelectionEnabled,
+ onCheckedChange = { isPreSelectionEnabled = it }
+ )
+ }
+
// Multiple Selection
SwitchComponent(
label = stringResource(id = R.string.allow_multiple_selection),
@@ -343,6 +373,8 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
customMimeTypeInput = customMimeTypeInput,
isOrderSelectionEnabled = isOrderSelectionEnabled,
selectedLaunchTab = LaunchLocation.valueOf(selectedLaunchTab),
+ accentColor = accentColor,
+ isPreSelectionEnabled = isPreSelectionEnabled,
launcher = launcher::launch
)
if (errorMessage != null) {
@@ -361,8 +393,23 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
Spacer(modifier = Modifier.height(16.dp))
- Column{
+ Column {
+ // Switch for showing meta data
+ SwitchComponent(
+ label = stringResource(R.string.show_metadata),
+ checked = showMetaData,
+ onCheckedChange = { showMetaData = it }
+ )
+
resultMedia.forEach { uri ->
+ if (showMetaData) {
+ MetaDataDetails(
+ uri = uri,
+ contentResolver = context.contentResolver,
+ showMetaData = showMetaData,
+ inDocsUITab = false
+ )
+ }
if (isImage(context, uri)) {
// To display image
GlideImage(
@@ -370,7 +417,7 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
- .fillMaxSize()
+ .height(600.dp)
.padding(top = 8.dp)
)
} else {
@@ -384,12 +431,14 @@ fun PhotoPickerScreen(photoPickerViewModel: PhotoPickerViewModel = viewModel())
},
modifier = Modifier
.fillMaxWidth()
- .height(200.dp)
+ .height(600.dp)
.padding(top = 8.dp)
)
}
+ Spacer(modifier = Modifier.height(20.dp))
+ HorizontalDivider(thickness = 6.dp)
+ Spacer(modifier = Modifier.height(17.dp))
}
}
}
}
-
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerViewModel.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerViewModel.kt
index e72acb01b..897e14633 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerViewModel.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/photopicker/PhotoPickerViewModel.kt
@@ -37,7 +37,6 @@ class PhotoPickerViewModel(
application: Application,
) : AndroidViewModel(application) {
-
private val _selectedMedia = MutableStateFlow<List<Uri>>(emptyList())
val selectedMedia: StateFlow<List<Uri>> = _selectedMedia
@@ -67,11 +66,13 @@ class PhotoPickerViewModel(
customMimeTypeInput: String,
isOrderSelectionEnabled: Boolean,
selectedLaunchTab: LaunchLocation,
+ accentColor: String,
+ isPreSelectionEnabled: Boolean,
launcher: (Intent) -> Unit
): String? {
if (!isActionGetContentSelected && allowMultiple){
if (maxMediaItemsDisplayed <= 1) {
- return "Enter a valid number greater than one"
+ return "Enter a valid count greater than one"
}
if (maxMediaItemsDisplayed > _pickImagesMaxSelectionLimit) {
@@ -79,6 +80,16 @@ class PhotoPickerViewModel(
}
}
+ if (accentColor == "") {
+ return "Enter an accent color"
+ }
+
+ val accentColorLong: Long = try {
+ android.graphics.Color.parseColor(accentColor).toLong()
+ } catch (e: IllegalArgumentException) {
+ android.graphics.Color.parseColor("#FF6200EE").toLong() // Default color
+ }
+
val intent = if (isActionGetContentSelected) {
// ACTION_GET_CONTENT supports only images and videos in the PhotoPicker tab
Intent(Intent.ACTION_GET_CONTENT).apply {
@@ -105,16 +116,23 @@ class PhotoPickerViewModel(
if (selectedLaunchTab == LaunchLocation.ALBUMS_TAB) 0 else 1
)
putExtra(MediaStore.EXTRA_PICK_IMAGES_IN_ORDER, isOrderSelectionEnabled)
+ putExtra(MediaStore.EXTRA_PICK_IMAGES_ACCENT_COLOR, accentColorLong)
+ if (isPreSelectionEnabled){
+ Intent(putParcelableArrayListExtra(
+ "android.provider.extra.PICKER_PRE_SELECTION_URIS",
+ ArrayList(_selectedMedia.value)
+ ))
+ }
}
}
+
try {
launcher(intent)
} catch (e: ActivityNotFoundException) {
val errorMessage =
- "No Activity found to handle Intent with type \"" + intent.getType() + "\""
+ "No Activity found to handle Intent with type \"" + intent.type + "\""
Toast.makeText(getApplication(), errorMessage, Toast.LENGTH_SHORT).show()
}
return null
}
-}
-
+} \ No newline at end of file
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/pickerchoice/PickerChoiceScreen.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/pickerchoice/PickerChoiceScreen.kt
index 65fe38d2c..9363a5e57 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/pickerchoice/PickerChoiceScreen.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/pickerchoice/PickerChoiceScreen.kt
@@ -15,47 +15,262 @@
*/
package com.android.providers.media.tools.photopickerv2.pickerchoice
+import android.widget.Toast
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+import android.os.Build
+import android.widget.VideoView
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.paddingFromBaseline
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.providers.media.tools.photopickerv2.R
+import com.android.providers.media.tools.photopickerv2.utils.ButtonComponent
+import com.android.providers.media.tools.photopickerv2.utils.MetaDataDetails
+import com.android.providers.media.tools.photopickerv2.utils.SwitchComponent
+import com.android.providers.media.tools.photopickerv2.utils.isImage
+import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
+import com.bumptech.glide.integration.compose.GlideImage
/**
* This is the screen for the PickerChoice tab.
*/
+@OptIn(ExperimentalGlideComposeApi::class)
@Composable
-fun PickerChoiceScreen() {
- Column (
- modifier = Modifier.fillMaxSize()
- ){
+fun PickerChoiceScreen(pickerChoiceViewModel: PickerChoiceViewModel = viewModel()) {
+ // When VERSION.SDK_INT is lower than VERSION U, then PickerChoice will not work on the device
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ // Error message when the device's version is lower than Version U
Text(
- text = stringResource(id = R.string.tab_pickerchoice),
+ text = stringResource(id = R.string.picker_choice_unsupported),
fontWeight = FontWeight.Bold,
- fontSize = 25.sp,
- modifier = Modifier.padding(16.dp)
+ fontSize = 17.sp,
+ modifier = Modifier.padding(20.dp)
+ .paddingFromBaseline(40.dp),
+ color = Color.Red
)
- Row(modifier = Modifier
- .fillMaxWidth()
- .padding(vertical = 100.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center
+ } else {
+ val context = LocalContext.current
+
+ var requestPermissionForImagesOnly by remember { mutableStateOf(false) }
+ var requestPermissionForVideosOnly by remember { mutableStateOf(false) }
+ var requestPermissionForBoth by remember { mutableStateOf(false) }
+
+ var showMetaData by remember { mutableStateOf(false) }
+
+ val showLatestSelectionOnly by pickerChoiceViewModel
+ .latestSelectionOnly.observeAsState(false)
+
+ val permissionLauncher = rememberLauncherForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
+ val allGranted = permissions.values.all { it }
+ val partialGranted = permissions[READ_MEDIA_VISUAL_USER_SELECTED] == true ||
+ requestPermissionForImagesOnly ||
+ requestPermissionForVideosOnly
+ if (allGranted || partialGranted) {
+ pickerChoiceViewModel.checkPermissions(context.contentResolver)
+ } else {
+ Toast.makeText(context, "Permissions not granted", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ fun resetPermissions() {
+ requestPermissionForImagesOnly = false
+ requestPermissionForVideosOnly = false
+ requestPermissionForBoth = false
+ }
+
+ Column(
+ modifier = Modifier.run {
+ padding(16.dp)
+ .verticalScroll(rememberScrollState())
+ .fillMaxWidth()
+ }
){
Text(
- text = stringResource(id = R.string.working_on_it),
- fontWeight = FontWeight.Medium,
- fontSize = 40.sp,
+ text = stringResource(id = R.string.tab_pickerchoice),
+ fontWeight = FontWeight.Bold,
+ fontSize = 25.sp,
+ modifier = Modifier.padding(5.dp)
+ )
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ Text(
+ text = stringResource(R.string.request_permissions_for),
+ fontWeight = FontWeight.Bold,
+ fontSize = 17.sp
)
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 5.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ // Request Permission for Only Images
+ SwitchComponent(
+ label = stringResource(id = R.string.images),
+ checked = requestPermissionForImagesOnly,
+ onCheckedChange = {
+ requestPermissionForImagesOnly = it
+ if (it) {
+ resetPermissions()
+ requestPermissionForImagesOnly = true
+ }
+ }
+ )
+ }
+
+ Spacer(modifier = Modifier.width(6.dp))
+
+ Column(modifier = Modifier.weight(1f)) {
+ // Request Permission for Only Videos
+ SwitchComponent(
+ label = stringResource(id = R.string.videos),
+ checked = requestPermissionForVideosOnly,
+ onCheckedChange = {
+ requestPermissionForVideosOnly = it
+ if (it) {
+ resetPermissions()
+ requestPermissionForVideosOnly = true
+ }
+ }
+ )
+ }
+ }
+
+ // Request Permission for Both Images and Videos
+ SwitchComponent(
+ label = stringResource(id = R.string.both_images_and_videos),
+ checked = requestPermissionForBoth,
+ onCheckedChange = {
+ requestPermissionForBoth = it
+ if (it) {
+ resetPermissions()
+ requestPermissionForBoth = true
+ }
+ }
+ )
+
+ // Switch to enable show latest selection only
+ SwitchComponent(
+ label = stringResource(id = R.string.show_latest_selection_only),
+ checked = showLatestSelectionOnly,
+ onCheckedChange = {
+ pickerChoiceViewModel.setLatestSelectionOnly(it)
+ }
+ )
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 15.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ ButtonComponent(
+ label = stringResource(id = R.string.request_permissions),
+ onClick = {
+ when {
+ requestPermissionForImagesOnly ->
+ pickerChoiceViewModel.requestAppPermissions(imagesOnly = true)
+ requestPermissionForVideosOnly ->
+ pickerChoiceViewModel.requestAppPermissions(videosOnly = true)
+ requestPermissionForBoth ->
+ pickerChoiceViewModel.requestAppPermissions()
+ }
+ permissionLauncher.launch(
+ pickerChoiceViewModel.permissionRequest.value ?: arrayOf())
+ },
+ enabled = requestPermissionForImagesOnly ||
+ requestPermissionForVideosOnly ||
+ requestPermissionForBoth,
+ modifier = Modifier.weight(1f)
+ )
+ }
+
+ // Switch for showing meta data
+ SwitchComponent(
+ label = stringResource(R.string.show_metadata),
+ checked = showMetaData,
+ onCheckedChange = { showMetaData = it }
+ )
+
+ val mediaList by pickerChoiceViewModel.media.observeAsState(emptyList())
+ DisplayMedia(mediaList, showMetaData)
+ }
+ }
+}
+
+@OptIn(ExperimentalGlideComposeApi::class)
+@Composable
+fun DisplayMedia(mediaList: List<PickerChoiceViewModel.Media>, showMetaData: Boolean) {
+ Column {
+ mediaList.forEach { media ->
+ if (showMetaData) {
+ MetaDataDetails(
+ uri = media.uri,
+ contentResolver = LocalContext.current.contentResolver,
+ showMetaData = showMetaData,
+ inDocsUITab = false
+ )
+ }
+ if (isImage(LocalContext.current, media.uri)) {
+ // To display image
+ GlideImage(
+ model = media.uri,
+ contentDescription = null,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(600.dp)
+ .padding(top = 8.dp)
+ )
+ } else {
+ AndroidView(
+ // To display video
+ factory = { ctx ->
+ VideoView(ctx).apply {
+ setVideoURI(media.uri)
+ start()
+ }
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(600.dp)
+ .padding(top = 8.dp)
+ )
+ }
+ Spacer(modifier = Modifier.height(20.dp))
+ HorizontalDivider(thickness = 6.dp)
+ Spacer(modifier = Modifier.height(17.dp))
}
}
-} \ No newline at end of file
+}
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/pickerchoice/PickerChoiceViewModel.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/pickerchoice/PickerChoiceViewModel.kt
index 1bcfe28af..a81f87b66 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/pickerchoice/PickerChoiceViewModel.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/pickerchoice/PickerChoiceViewModel.kt
@@ -15,12 +15,159 @@
*/
package com.android.providers.media.tools.photopickerv2.pickerchoice
-import androidx.lifecycle.ViewModel
+import android.Manifest.permission.READ_MEDIA_IMAGES
+import android.Manifest.permission.READ_MEDIA_VIDEO
+import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+import android.app.Application
+import android.content.ContentResolver
+import android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER
+import android.content.ContentUris
+import android.net.Uri
+import android.os.Build
+import android.provider.MediaStore
+import android.widget.Toast
+import androidx.core.content.ContextCompat
+import androidx.core.content.PermissionChecker.PERMISSION_GRANTED
+import androidx.core.os.bundleOf
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* PickerChoiceViewModel is responsible for managing the state and logic
- * of the PhotoPicker feature.
+ * of the PickerChoice feature.
*/
-class PickerChoiceViewModel() : ViewModel() {
- // Working on it
-} \ No newline at end of file
+class PickerChoiceViewModel(application: Application) : AndroidViewModel(application) {
+
+ private val _permissionRequest = MutableLiveData<Array<String>>()
+ val permissionRequest: LiveData<Array<String>> = _permissionRequest
+
+ private val _media = MutableLiveData<List<Media>>(emptyList())
+ val media: LiveData<List<Media>> get() = _media
+
+ private val _latestSelectionOnly = MutableLiveData(false)
+ val latestSelectionOnly: LiveData<Boolean> get() = _latestSelectionOnly
+
+ fun setLatestSelectionOnly(enabled: Boolean) {
+ _latestSelectionOnly.value = enabled
+ }
+
+ /**
+ * Requests the necessary permissions for accessing media on the device.
+ *
+ * This method sets the appropriate permissions to request based on the
+ * provided parameters and the Android version.
+ *
+ * @param imagesOnly a Boolean flag indicating if only image permissions should be requested.
+ * @param videosOnly a Boolean flag indicating if only video permissions should be requested.
+ */
+ fun requestAppPermissions(imagesOnly: Boolean = false, videosOnly: Boolean = false) {
+ when {
+ imagesOnly -> {
+ _permissionRequest.value = arrayOf(READ_MEDIA_IMAGES)
+ }
+ videosOnly -> {
+ _permissionRequest.value = arrayOf(READ_MEDIA_VIDEO)
+ }
+ else -> {
+ _permissionRequest.value = arrayOf(
+ READ_MEDIA_IMAGES,
+ READ_MEDIA_VIDEO,
+ READ_MEDIA_VISUAL_USER_SELECTED
+ )
+ }
+ }
+ }
+
+ /**
+ * Checks the permissions for accessing media on the device.
+ *
+ * This method checks if the application has been granted the
+ * READ_MEDIA_VISUAL_USER_SELECTED permission. If the device is
+ * running Android 14 (UPSIDE_DOWN_CAKE) or higher and the permission
+ * is granted, it shows a toast indicating partial access. Otherwise,
+ * it shows a toast indicating access denied.
+ */
+ fun checkPermissions(contentResolver: ContentResolver) {
+ val context = getApplication<Application>().applicationContext
+ when {
+ ContextCompat.checkSelfPermission(context, READ_MEDIA_VISUAL_USER_SELECTED) ==
+ PERMISSION_GRANTED -> {
+ Toast.makeText(context, "Partial access on Android 14 or higher",
+ Toast.LENGTH_SHORT).show()
+ fetchMedia(contentResolver)
+ }
+ else -> {
+ Toast.makeText(context, "Access denied", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ private fun fetchMedia(contentResolver: ContentResolver) {
+ viewModelScope.launch {
+ _media.value = getMedia(contentResolver)
+ }
+ }
+
+ data class Media(
+ val uri: Uri,
+ val name: String,
+ val size: Long,
+ val mimeType: String,
+ )
+ private suspend fun getMedia(
+ contentResolver: ContentResolver
+ ): List<Media> = withContext(Dispatchers.IO) {
+ val projection = arrayOf(
+ MediaStore.MediaColumns._ID,
+ MediaStore.MediaColumns.DISPLAY_NAME,
+ MediaStore.MediaColumns.SIZE,
+ MediaStore.MediaColumns.MIME_TYPE,
+ )
+
+ val collectionUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
+
+ val mediaList = mutableListOf<Media>()
+
+ // TODO: BuildCompat.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 12
+ // @riyaghai : Please add this dependency in Android.bp
+ val queryArgs = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
+ latestSelectionOnly.value == true
+ ) {
+ bundleOf(
+ QUERY_ARG_SQL_SORT_ORDER to "${MediaStore.MediaColumns.DATE_ADDED} DESC",
+ "android:query-arg-latest-selection-only" to true
+ )
+ } else {
+ null
+ }
+
+ contentResolver.query(
+ collectionUri,
+ projection,
+ queryArgs,
+ null
+ )?.use { cursor ->
+ val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
+ val displayNameColumn = cursor.getColumnIndexOrThrow(
+ MediaStore.MediaColumns.DISPLAY_NAME)
+ val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
+ val mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)
+
+ while (cursor.moveToNext()) {
+ val uri = ContentUris.withAppendedId(collectionUri, cursor.getLong(idColumn))
+ val name = cursor.getString(displayNameColumn)
+ val size = cursor.getLong(sizeColumn)
+ val mimeType = cursor.getString(mimeTypeColumn)
+
+ val media = Media(uri, name, size, mimeType)
+ mediaList.add(media)
+ }
+ }
+ return@withContext mediaList
+ }
+}
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/UIComponents.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/UIComponents.kt
index 480210fe3..5821629c2 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/UIComponents.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/UIComponents.kt
@@ -15,10 +15,15 @@
*/
package com.android.providers.media.tools.photopickerv2.utils
+import android.content.ContentResolver
+import android.database.Cursor
+import android.net.Uri
+import android.provider.MediaStore
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -58,6 +63,9 @@ import androidx.compose.ui.window.PopupProperties
import androidx.navigation.NavController
import com.android.providers.media.tools.photopickerv2.R
import com.android.providers.media.tools.photopickerv2.navigation.NavigationItem
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
/**
* PhotoPickerTitle is a composable function that displays the title of the PhotoPicker app.
@@ -158,6 +166,7 @@ fun ErrorMessage(
* @param onClick the callback function to be called when the button component is clicked.
* @param modifier the modifier to be applied to the button component.
* @param colors the color of the button.
+ * @param enabled the enabled state of the button component.
*/
@Composable
fun ButtonComponent(
@@ -165,11 +174,13 @@ fun ButtonComponent(
onClick: () -> Unit,
modifier: Modifier = Modifier,
colors: ButtonColors = ButtonDefaults.buttonColors(),
+ enabled: Boolean = true
) {
Button(
onClick = onClick,
colors = colors,
- modifier = modifier.fillMaxWidth()
+ modifier = modifier.fillMaxWidth(),
+ enabled = enabled
) {
Text(label)
}
@@ -300,13 +311,95 @@ fun DropdownList(
}
}
+@Composable
+fun MetaDataDetails(
+ uri: Uri,
+ contentResolver: ContentResolver,
+ showMetaData: Boolean,
+ inDocsUITab: Boolean
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ if (showMetaData) {
+ val cursor: Cursor? = contentResolver.query(
+ uri, null, null, null, null
+ )
+ cursor?.use {
+ // Metadata Details for PhotoPicker Tab and PickerChoice Tab
+ if (!inDocsUITab){
+ if (it.moveToNext()) {
+ val mediaUri = it.getString(it.getColumnIndexOrThrow(
+ MediaStore.Images.Media.DATA))
+ val displayName = it.getString(it.getColumnIndexOrThrow(
+ MediaStore.Images.Media.DISPLAY_NAME))
+ val size = it.getLong(it.getColumnIndexOrThrow(
+ MediaStore.Images.Media.SIZE))
+ val sizeInKB = size / 1000
+ val dateTaken = it.getLong(it.getColumnIndexOrThrow(
+ MediaStore.Images.Media.DATE_TAKEN))
+
+ val duration =
+ it.getLong(it.getColumnIndexOrThrow(MediaStore.Images.Media.DURATION))
+ val durationInSec = duration / 1000
+ val formatter = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
+ val dateString = formatter.format(Date(dateTaken))
+
+ Column {
+ Text(
+ text = "Meta Data Details:",
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp,
+ )
+ Text(text = "URI: $mediaUri")
+ Text(text = "Display Name: $displayName")
+ Text(text = "Size: $sizeInKB KB")
+ Text(text = "Date Taken: $dateString")
+ Text(text = "Duration: $durationInSec s")
+ }
+ }
+ } else {
+ // Metadata Details for DocsUI Tab
+ if (it.moveToNext()){
+ val documentID = it.getLong(it.getColumnIndexOrThrow(
+ MediaStore.Images.Media.DOCUMENT_ID))
+ val mimeType = it.getString(it.getColumnIndexOrThrow(
+ MediaStore.Images.Media.MIME_TYPE))
+ val displayName =
+ it.getString(it.getColumnIndexOrThrow(
+ MediaStore.Images.Media.DISPLAY_NAME))
+ val size = it.getLong(it.getColumnIndexOrThrow(
+ MediaStore.Images.Media.SIZE))
+ val sizeInKB = size / 1000
+ Column {
+ Text(
+ text = "Meta Data Details:",
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp,
+ )
+
+ Text(text = "Document ID: $documentID")
+ Text(text = "Display Name: $displayName")
+ Text(text = "Size: $sizeInKB KB")
+ Text(text = "Mime Type: $mimeType")
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
enum class LaunchLocation {
PHOTOS_TAB,
ALBUMS_TAB;
companion object {
fun getListOfAvailableLocations(): List<String> {
- return values().toList().map { it -> it.name }
+ return entries.map { it -> it.name }
}
}
}
diff --git a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/Utils.kt b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/Utils.kt
index 451ff6189..f2ba32868 100644
--- a/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/Utils.kt
+++ b/tools/photopickerV2/src/com/android/providers/media/tools/photopickerv2/utils/Utils.kt
@@ -21,6 +21,7 @@ import android.net.Uri
import com.android.providers.media.tools.photopickerv2.docsui.DocsUIViewModel
import com.android.providers.media.tools.photopickerv2.photopicker.PhotoPickerViewModel
+// This function is to check if the type of URI is image
/**
* isImage checks if the provided URI points to an image file.
*
@@ -50,7 +51,4 @@ fun resetMedia(photoPickerViewModel: PhotoPickerViewModel) {
*/
fun resetMedia(docsUIViewModel: DocsUIViewModel) {
docsUIViewModel.updateSelectedMediaList(emptyList())
-}
-
-
-
+} \ No newline at end of file