Snap for 9832712 from ab6a6cb04098dc3b8c348a33f98a5f93d5fbc19c to mainline-adservices-release
Change-Id: If7c77024e97637de1a28256b9f2731b8e5eb8c91
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8f6fe73..c917729 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -177,7 +177,8 @@
android:label="@string/picker_app_label"
android:theme="@style/PickerDefaultTheme"
android:exported="true"
- android:excludeFromRecents="true" >
+ android:excludeFromRecents="true"
+ android:colorMode="wideColorGamut">
<intent-filter android:priority="100" >
<action android:name="android.provider.action.PICK_IMAGES" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index 724cfe4..7e4d172 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -745,6 +745,9 @@
* {@link MediaStore#getPickImagesMaxLimit}, otherwise
* {@link Activity#RESULT_CANCELED} is returned.
* <p>
+ * Callers may use {@link Intent#EXTRA_LOCAL_ONLY} to limit content
+ * selection to local data.
+ * <p>
* Output: MediaStore content URI(s) of the item(s) that was picked.
* Unlike other MediaStore URIs, these are referred to as 'picker' URIs and
* expose a limited set of read-only operations. Specifically, picker URIs
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 42fb74f..30e625f 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Kies tot <xliff:g id="COUNT_0">^1</xliff:g> item}other{Kies tot <xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
<string name="recent" msgid="6694613584743207874">"Onlangs"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Geen foto\'s of video\'s nie"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Geen gesteunde foto’s of video’s nie"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Geen albums nie"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Bekyk geselekteerde"</string>
<string name="picker_photos" msgid="7415035516411087392">"Foto\'s"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index f8a06c2..4bfbfc1 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{እስከ <xliff:g id="COUNT_0">^1</xliff:g> ንጥል ድረስ ይምረጡ}one{እስከ <xliff:g id="COUNT_1">^1</xliff:g> ንጥል ድረስ ይምረጡ}other{እስከ <xliff:g id="COUNT_1">^1</xliff:g> ንጥሎች ድረስ ይምረጡ}}"</string>
<string name="recent" msgid="6694613584743207874">"የቅርብ ጊዜ"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"ምንም ፎቶዎች ወይም ቪዲዮዎች የሉም"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"ምንም የሚደገፉ ፎቶዎች ወይም ቪድዮዎች የሉም"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"ምንም አልበሞች የሉም"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"የተመረጡትን አሳይ"</string>
<string name="picker_photos" msgid="7415035516411087392">"ፎቶዎች"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index b4009f9..e62ab7e 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{اختَر ما يصل إلى عنصر واحد (<xliff:g id="COUNT_0">^1</xliff:g>).}zero{اختَر ما يصل إلى <xliff:g id="COUNT_1">^1</xliff:g> عنصر.}two{اختَر ما يصل إلى عنصرين (<xliff:g id="COUNT_1">^1</xliff:g>).}few{اختَر ما يصل إلى <xliff:g id="COUNT_1">^1</xliff:g> عناصر.}many{اختَر ما يصل إلى <xliff:g id="COUNT_1">^1</xliff:g> عنصرًا.}other{اختَر ما يصل إلى <xliff:g id="COUNT_1">^1</xliff:g> عنصر.}}"</string>
<string name="recent" msgid="6694613584743207874">"الأحدث"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"ما مِن صور أو فيديوهات"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"ما مِن صور أو فيديوهات متوافقة."</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"ما مِن ألبومات"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"عرض ما تم اختياره"</string>
<string name="picker_photos" msgid="7415035516411087392">"الصور"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index e2ea999..dfdc455 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> টালৈকে বস্তু বাছনি কৰক}one{<xliff:g id="COUNT_1">^1</xliff:g> টালৈকে বস্তু বাছনি কৰক}other{<xliff:g id="COUNT_1">^1</xliff:g> টালৈকে বস্তু বাছনি কৰক}}"</string>
<string name="recent" msgid="6694613584743207874">"শেহতীয়া"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"কোনো ফট’ অথবা ভিডিঅ’ নাই"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"অসমৰ্থিত ফট\' অথবা ভিডিঅ\'"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"কোনো এলবাম নাই"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"ভিউ বাছনি কৰা হৈছে"</string>
<string name="picker_photos" msgid="7415035516411087392">"ফট’"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 2cd4981..7f90380 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Maksimum <xliff:g id="COUNT_0">^1</xliff:g> element seçin}other{Maksimum <xliff:g id="COUNT_1">^1</xliff:g> element seçin}}"</string>
<string name="recent" msgid="6694613584743207874">"Son"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Foto və ya video yoxdur"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Dəstəklənən foto və ya video yoxdur"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Albom yoxdur"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Seçilənə baxın"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotolar"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 324e8cd..331a79c 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Izaberite najviše <xliff:g id="COUNT_0">^1</xliff:g> stavku}one{Izaberite najviše <xliff:g id="COUNT_1">^1</xliff:g> stavku}few{Izaberite najviše <xliff:g id="COUNT_1">^1</xliff:g> stavke}other{Izaberite najviše <xliff:g id="COUNT_1">^1</xliff:g> stavki}}"</string>
<string name="recent" msgid="6694613584743207874">"Nedavno"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Nema slika niti video snimaka"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Nema podržanih slika niti video snimaka"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Nema albuma"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Prikaži izabrano"</string>
<string name="picker_photos" msgid="7415035516411087392">"Slike"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index bef7616..1188bb9 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Выберыце да <xliff:g id="COUNT_0">^1</xliff:g> элемента}one{Выберыце да <xliff:g id="COUNT_1">^1</xliff:g> элемента}few{Выберыце да <xliff:g id="COUNT_1">^1</xliff:g> элементаў}many{Выберыце да <xliff:g id="COUNT_1">^1</xliff:g> элементаў}other{Выберыце да <xliff:g id="COUNT_1">^1</xliff:g> элемента}}"</string>
<string name="recent" msgid="6694613584743207874">"Нядаўнія"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Няма фота і відэа"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Няма даступных фота і відэа"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Няма альбомаў"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Праглядзець выбранае"</string>
<string name="picker_photos" msgid="7415035516411087392">"Фота"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 88b2826..b949829 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Изберете най-много <xliff:g id="COUNT_0">^1</xliff:g> елемент}other{Изберете най-много <xliff:g id="COUNT_1">^1</xliff:g> елемента}}"</string>
<string name="recent" msgid="6694613584743207874">"Скорошни"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Няма снимки или видеоклипове"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Няма поддържани снимки или видеоклипове"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Няма албуми"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Преглед на избраното"</string>
<string name="picker_photos" msgid="7415035516411087392">"Снимки"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 7895789..7867197 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{সর্বাধিক <xliff:g id="COUNT_0">^1</xliff:g>টি আইটেম বেছে নিন}one{সর্বাধিক <xliff:g id="COUNT_1">^1</xliff:g>টি আইটেম বেছে নিন}other{সর্বাধিক <xliff:g id="COUNT_1">^1</xliff:g>টি আইটেম বেছে নিন}}"</string>
<string name="recent" msgid="6694613584743207874">"সাম্প্রতিক"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"কোনও ফটো বা ভিডিও নেই"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"মানানসই কোনও ফটো বা ভিডিও নেই"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"কোনও অ্যালবাম নেই"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"ভিউ বেছে নেওয়া হয়েছে"</string>
<string name="picker_photos" msgid="7415035516411087392">"ফটো"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 1573f82..816743e 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Odaberite najviše <xliff:g id="COUNT_0">^1</xliff:g> stavku}one{Odaberite najviše <xliff:g id="COUNT_1">^1</xliff:g> stavku}few{Odaberite najviše <xliff:g id="COUNT_1">^1</xliff:g> stavke}other{Odaberite najviše <xliff:g id="COUNT_1">^1</xliff:g> stavki}}"</string>
<string name="recent" msgid="6694613584743207874">"Nedavno"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Nema fotografija ni videozapisa"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Nema podržanih fotografija ili videozapisa"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Nema albuma"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Prikaži odabrano"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotografije"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index ce47c14..95a60ee 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Selecciona fins a <xliff:g id="COUNT_0">^1</xliff:g> element}many{Selecciona fins a <xliff:g id="COUNT_1">^1</xliff:g> elements}other{Selecciona fins a <xliff:g id="COUNT_1">^1</xliff:g> elements}}"</string>
<string name="recent" msgid="6694613584743207874">"Recent"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"No hi ha cap foto ni vídeo"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"No hi ha cap foto ni vídeo compatibles"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"No hi ha cap àlbum"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Mostra la selecció"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotos"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index cbaca18..a6ad535 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Vyberte maximálně <xliff:g id="COUNT_0">^1</xliff:g> položku}few{Vyberte maximálně <xliff:g id="COUNT_1">^1</xliff:g> položky}many{Vyberte maximálně <xliff:g id="COUNT_1">^1</xliff:g> položky}other{Vyberte maximálně <xliff:g id="COUNT_1">^1</xliff:g> položek}}"</string>
<string name="recent" msgid="6694613584743207874">"Nedávné"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Žádné fotky ani videa"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Žádné podporované fotky ani videa"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Žádná alba"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Zobrazit vybrané"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotky"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 37b1500..216febc 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Vælg op til <xliff:g id="COUNT_0">^1</xliff:g> element}one{Vælg op til <xliff:g id="COUNT_1">^1</xliff:g> element}other{Vælg op til <xliff:g id="COUNT_1">^1</xliff:g> elementer}}"</string>
<string name="recent" msgid="6694613584743207874">"Seneste"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Ingen billeder eller videoer"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Der er ingen understøttede billeder eller videoer"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Ingen album"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Se valgte"</string>
<string name="picker_photos" msgid="7415035516411087392">"Billeder"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 7a80208..0e504cf 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Du kannst maximal <xliff:g id="COUNT_0">^1</xliff:g> Element auswählen}other{Du kannst maximal <xliff:g id="COUNT_1">^1</xliff:g> Elemente auswählen}}"</string>
<string name="recent" msgid="6694613584743207874">"Neueste"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Keine Fotos oder Videos"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Keine unterstützten Fotos oder Videos"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Keine Alben"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Auswahl ansehen"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotos"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index b952a75..02c0a0e 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Επιλέξτε έως <xliff:g id="COUNT_0">^1</xliff:g> στοιχείο}other{Επιλέξτε έως <xliff:g id="COUNT_1">^1</xliff:g> στοιχεία}}"</string>
<string name="recent" msgid="6694613584743207874">"Πρόσφατα"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Δεν υπάρχουν φωτογραφίες ή βίντεο"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Δεν υπάρχουν υποστηριζόμενες φωτογραφίες ή βίντεο"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Δεν υπάρχουν λευκώματα"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Προβολή επιλεγμένων"</string>
<string name="picker_photos" msgid="7415035516411087392">"Φωτογραφίες"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index a0618ae..b6ab4f1 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Select up to <xliff:g id="COUNT_0">^1</xliff:g> item}other{Select up to <xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
<string name="recent" msgid="6694613584743207874">"Recent"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"No photos or videos"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"No supported photos or videos"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"No albums"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"View selected"</string>
<string name="picker_photos" msgid="7415035516411087392">"Photos"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index a0618ae..b6ab4f1 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Select up to <xliff:g id="COUNT_0">^1</xliff:g> item}other{Select up to <xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
<string name="recent" msgid="6694613584743207874">"Recent"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"No photos or videos"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"No supported photos or videos"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"No albums"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"View selected"</string>
<string name="picker_photos" msgid="7415035516411087392">"Photos"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index a0618ae..b6ab4f1 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Select up to <xliff:g id="COUNT_0">^1</xliff:g> item}other{Select up to <xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
<string name="recent" msgid="6694613584743207874">"Recent"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"No photos or videos"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"No supported photos or videos"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"No albums"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"View selected"</string>
<string name="picker_photos" msgid="7415035516411087392">"Photos"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index ad48b6f..d740bbc 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Elige <xliff:g id="COUNT_0">^1</xliff:g> elemento como máximo}many{Elige <xliff:g id="COUNT_1">^1</xliff:g> elementos como máximo}other{Elige <xliff:g id="COUNT_1">^1</xliff:g> elementos como máximo}}"</string>
<string name="recent" msgid="6694613584743207874">"Recientes"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"No hay fotos ni videos"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"No hay fotos ni videos compatibles"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"No hay álbumes"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Ver seleccionados"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotos"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 405ea2e..156db06 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Selecciona hasta <xliff:g id="COUNT_0">^1</xliff:g> elemento}many{Selecciona hasta <xliff:g id="COUNT_1">^1</xliff:g> elementos}other{Selecciona hasta <xliff:g id="COUNT_1">^1</xliff:g> elementos}}"</string>
<string name="recent" msgid="6694613584743207874">"Reciente"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"No hay fotos ni vídeos"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"No hay fotos ni vídeos compatibles"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"No hay ningún álbum"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Ver seleccionado"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotos"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 58f7aa1..a9c85db 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Valige kuni <xliff:g id="COUNT_0">^1</xliff:g> üksus}other{Valige kuni <xliff:g id="COUNT_1">^1</xliff:g> üksust}}"</string>
<string name="recent" msgid="6694613584743207874">"Hiljutised"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Fotosid ega videoid pole"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Toetatud fotosid ega videoid pole"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Albumeid pole"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Kuva valitud"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotod"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 4534d3b..e62facb 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Hautatu <xliff:g id="COUNT_0">^1</xliff:g> elementu, gehienez}other{Hautatu <xliff:g id="COUNT_1">^1</xliff:g> elementu, gehienez}}"</string>
<string name="recent" msgid="6694613584743207874">"Azkenak"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Ez dago argazki edo bideorik"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Ez dago onartutako argazkirik edo bideorik"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Ez dago albumik"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Ikusi hautatutakoak"</string>
<string name="picker_photos" msgid="7415035516411087392">"Argazkiak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 8f1cabe..17bdb46 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{حداکثر <xliff:g id="COUNT_0">^1</xliff:g> مورد را انتخاب کنید}one{حداکثر <xliff:g id="COUNT_1">^1</xliff:g> مورد را انتخاب کنید}other{حداکثر <xliff:g id="COUNT_1">^1</xliff:g> مورد را انتخاب کنید}}"</string>
<string name="recent" msgid="6694613584743207874">"اخیر"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"عکس یا ویدیویی موجود نیست"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"عکس یا ویدیو پشتیبانیشدهای وجود ندارد"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"آلبومی موجود نیست"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"مشاهده موارد انتخابشده"</string>
<string name="picker_photos" msgid="7415035516411087392">"عکسها"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 26b7983..98ca72f 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Valitse enintään <xliff:g id="COUNT_0">^1</xliff:g> kohde}other{Valitse enintään <xliff:g id="COUNT_1">^1</xliff:g> kohdetta}}"</string>
<string name="recent" msgid="6694613584743207874">"Viimeisimmät"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Ei kuvia tai videoita"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Ei tuettuja kuvia tai videoita"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Ei albumeita"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Katso valitut"</string>
<string name="picker_photos" msgid="7415035516411087392">"Kuvat"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index dc0c07d..cbe6a29 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Sélectionnez jusqu\'à <xliff:g id="COUNT_0">^1</xliff:g> élément}one{Sélectionnez jusqu\'à <xliff:g id="COUNT_1">^1</xliff:g> élément}many{Sélectionnez jusqu\'à <xliff:g id="COUNT_1">^1</xliff:g> éléments}other{Sélectionnez jusqu\'à <xliff:g id="COUNT_1">^1</xliff:g> éléments}}"</string>
<string name="recent" msgid="6694613584743207874">"Récentes"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Aucune photo ni aucune vidéo"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Aucune photo ou vidéo compatible"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Aucun album"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Afficher la sélection"</string>
<string name="picker_photos" msgid="7415035516411087392">"Photos"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 3c9ea60..24e8b03 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Sélectionnez <xliff:g id="COUNT_0">^1</xliff:g> élément maximum}one{Sélectionnez <xliff:g id="COUNT_1">^1</xliff:g> élément maximum}many{Sélectionnez <xliff:g id="COUNT_1">^1</xliff:g> éléments maximum}other{Sélectionnez <xliff:g id="COUNT_1">^1</xliff:g> éléments maximum}}"</string>
<string name="recent" msgid="6694613584743207874">"Récente(s)"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Aucune photo ni vidéo"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Aucune photo ou vidéo compatible"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Aucun album"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Afficher la sélection"</string>
<string name="picker_photos" msgid="7415035516411087392">"Photos"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 1380495..efee80a 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Selecciona ata <xliff:g id="COUNT_0">^1</xliff:g> elemento}other{Selecciona ata <xliff:g id="COUNT_1">^1</xliff:g> elementos}}"</string>
<string name="recent" msgid="6694613584743207874">"Recentes"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Non hai fotos nin vídeos"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Non hai fotos nin vídeos compatibles"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Non hai álbums"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Ver elemento seleccionado"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotos"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index df94f0f..4047902 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> જેટલી આઇટમ પસંદ કરો}one{<xliff:g id="COUNT_1">^1</xliff:g> જેટલી આઇટમ પસંદ કરો}other{<xliff:g id="COUNT_1">^1</xliff:g> જેટલી આઇટમ પસંદ કરો}}"</string>
<string name="recent" msgid="6694613584743207874">"તાજેતરના"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"કોઈ ફોટો અથવા વીડિયો નથી"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"કોઈ સપોર્ટેડ ફોટો અથવા વીડિયો નથી"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"કોઈ આલ્બમ નથી"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"પસંદ કરેલા ફોટા જુઓ"</string>
<string name="picker_photos" msgid="7415035516411087392">"ફોટા"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 433dfa8..6e53f79 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{ज़्यादा से ज़्यादा <xliff:g id="COUNT_0">^1</xliff:g> आइटम चुनें}one{ज़्यादा से ज़्यादा <xliff:g id="COUNT_1">^1</xliff:g> आइटम चुनें}other{ज़्यादा से ज़्यादा <xliff:g id="COUNT_1">^1</xliff:g> आइटम चुनें}}"</string>
<string name="recent" msgid="6694613584743207874">"हाल ही की फ़ोटो और वीडियो"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"कोई फ़ोटो या वीडियो नहीं है"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"कोई फ़ोटो या वीडियो नहीं मिला"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"कोई एल्बम नहीं है"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"चुनी गई फ़ोटो या वीडियो देखें"</string>
<string name="picker_photos" msgid="7415035516411087392">"फ़ोटो"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index db8679e..82970a6 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Odaberite najviše <xliff:g id="COUNT_0">^1</xliff:g> stavku}one{Odaberite najviše <xliff:g id="COUNT_1">^1</xliff:g> stavku}few{Odaberite najviše <xliff:g id="COUNT_1">^1</xliff:g> stavke}other{Odaberite najviše <xliff:g id="COUNT_1">^1</xliff:g> stavki}}"</string>
<string name="recent" msgid="6694613584743207874">"Nedavno"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Nema fotografija i videozapisa"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Nema podržanih fotografija ili videozapisa"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Nema albuma"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Prikaži odabrano"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotografije"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 7eb0bf7..71d6df3 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Legfeljebb <xliff:g id="COUNT_0">^1</xliff:g> elemet jelölhet ki}other{Legfeljebb <xliff:g id="COUNT_1">^1</xliff:g> elemet jelölhet ki}}"</string>
<string name="recent" msgid="6694613584743207874">"Legújabbak"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Nincsenek képek vagy videók"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Nincsenek támogatott fotók vagy videók"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Nincsenek albumok"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Kijelöltek megnézése"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotók"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 6aaff4f..4435cca 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Ընտրեք մինչև <xliff:g id="COUNT_0">^1</xliff:g> տարր}one{Ընտրեք մինչև <xliff:g id="COUNT_1">^1</xliff:g> տարր}other{Ընտրեք մինչև <xliff:g id="COUNT_1">^1</xliff:g> տարր}}"</string>
<string name="recent" msgid="6694613584743207874">"Վերջինները"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Լուսանկարներ կամ տեսանյութեր չկան"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Աջակցվող լուսանկարներ կամ տեսանյութեր չկան"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Ալբոմներ չկան"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Դիտել ընտրվածը"</string>
<string name="picker_photos" msgid="7415035516411087392">"Լուսանկարներ"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 5dfb10f..a11f232 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Pilih hingga <xliff:g id="COUNT_0">^1</xliff:g> item}other{Pilih hingga <xliff:g id="COUNT_1">^1</xliff:g> item}}"</string>
<string name="recent" msgid="6694613584743207874">"Terbaru"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Tidak ada foto atau video"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Tidak ada foto atau video yang didukung"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Tidak ada album"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Lihat yang dipilih"</string>
<string name="picker_photos" msgid="7415035516411087392">"Foto"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 51389e8..77386ea 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Veldu allt að <xliff:g id="COUNT_0">^1</xliff:g> atriði}one{Veldu allt að <xliff:g id="COUNT_1">^1</xliff:g> atriði}other{Veldu allt að <xliff:g id="COUNT_1">^1</xliff:g> atriði}}"</string>
<string name="recent" msgid="6694613584743207874">"Nýlegt"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Engar myndir eða myndskeið"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Engar studdar myndir eða myndskeið"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Engin albúm"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Skoða valið"</string>
<string name="picker_photos" msgid="7415035516411087392">"Myndir"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 8fb46f7..9059727 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Seleziona massimo <xliff:g id="COUNT_0">^1</xliff:g> elemento}many{Seleziona fino a <xliff:g id="COUNT_1">^1</xliff:g> elementi}other{Seleziona fino a <xliff:g id="COUNT_1">^1</xliff:g> elementi}}"</string>
<string name="recent" msgid="6694613584743207874">"Recenti"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Nessun video o foto"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Non sono presenti foto o video supportati"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Nessun album"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Visualizza selezione"</string>
<string name="picker_photos" msgid="7415035516411087392">"Foto"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 2d56db8..e4227aa 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{ניתן לבחור פריט אחד (<xliff:g id="COUNT_0">^1</xliff:g>) לכל היותר}one{ניתן לבחור <xliff:g id="COUNT_1">^1</xliff:g> פריטים לכל היותר}two{ניתן לבחור <xliff:g id="COUNT_1">^1</xliff:g> פריטים לכל היותר}other{ניתן לבחור <xliff:g id="COUNT_1">^1</xliff:g> פריטים לכל היותר}}"</string>
<string name="recent" msgid="6694613584743207874">"אחרונות"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"אין תמונות או סרטונים"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"אין תמונות או סרטונים נתמכים"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"אין אלבומים"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"הצגת הפריטים שנבחרו"</string>
<string name="picker_photos" msgid="7415035516411087392">"תמונות"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 2dd1bbe..80b839b 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> 件まで選択できます}other{<xliff:g id="COUNT_1">^1</xliff:g> 件まで選択できます}}"</string>
<string name="recent" msgid="6694613584743207874">"最近"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"画像または動画はありません"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"サポートされている写真や動画はありません"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"アルバムはありません"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"選択した写真を見る"</string>
<string name="picker_photos" msgid="7415035516411087392">"写真"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 30e05e1..a1e658e 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{აირჩიეთ <xliff:g id="COUNT_0">^1</xliff:g>-მდე ერთეული}other{აირჩიეთ <xliff:g id="COUNT_1">^1</xliff:g>-მდე ერთეული}}"</string>
<string name="recent" msgid="6694613584743207874">"ბოლოდროინდელი"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"ფოტოები და ვიდეოები არ არის"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"ფოტოები და ვიდეოები არ არის მხარდაჭერილი"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"ალბომები არ არის"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"ხედი არჩეულია"</string>
<string name="picker_photos" msgid="7415035516411087392">"ფოტოები"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 561b680..45e0be9 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> элементке дейін таңдаңыз.}other{<xliff:g id="COUNT_1">^1</xliff:g> элементке дейін таңдаңыз.}}"</string>
<string name="recent" msgid="6694613584743207874">"Соңғы"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Фотосуреттер немесе бейнелер жоқ."</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Қолдау көрсетілетін фотосуреттер не бейнелер жоқ."</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Альбомдар жоқ."</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Таңдалғанды көру"</string>
<string name="picker_photos" msgid="7415035516411087392">"Фотосуреттер"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index f79c61c..328a171 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{ជ្រើសរើសធាតុរហូតដល់ <xliff:g id="COUNT_0">^1</xliff:g>}other{ជ្រើសរើសធាតុរហូតដល់ <xliff:g id="COUNT_1">^1</xliff:g>}}"</string>
<string name="recent" msgid="6694613584743207874">"ថ្មីៗ"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"គ្មានរូបថត ឬវីដេអូទេ"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"គ្មានរូបថត ឬវីដេអូដែលអាចប្រើបាន"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"គ្មានអាល់ប៊ុមទេ"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"មើលអ្វីដែលបានជ្រើសរើស"</string>
<string name="picker_photos" msgid="7415035516411087392">"រូបថត"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 4b0056f..eda5bc6 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ಐಟಂವರೆಗೆ ಆಯ್ಕೆಮಾಡಿ}one{<xliff:g id="COUNT_1">^1</xliff:g> ಐಟಂಗಳವರೆಗೆ ಆಯ್ಕೆಮಾಡಿ}other{<xliff:g id="COUNT_1">^1</xliff:g> ಐಟಂಗಳವರೆಗೆ ಆಯ್ಕೆಮಾಡಿ}}"</string>
<string name="recent" msgid="6694613584743207874">"ಇತ್ತೀಚಿನದು"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"ಯಾವುದೇ ಫೋಟೋಗಳು ಅಥವಾ ವೀಡಿಯೊಗಳಿಲ್ಲ"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"ಯಾವುದೇ ಬೆಂಬಲಿತ ಫೋಟೋಗಳು ಅಥವಾ ವೀಡಿಯೊಗಳಿಲ್ಲ"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"ಯಾವುದೇ ಆಲ್ಬಮ್ಗಳಿಲ್ಲ"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"ಆಯ್ಕೆಮಾಡಿರುವುದನ್ನು ವೀಕ್ಷಿಸಿ"</string>
<string name="picker_photos" msgid="7415035516411087392">"ಫೋಟೋಗಳು"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 26bbadf..a5298fb 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{최대 <xliff:g id="COUNT_0">^1</xliff:g>개 항목을 선택하세요}other{최대 <xliff:g id="COUNT_1">^1</xliff:g>개 항목을 선택하세요}}"</string>
<string name="recent" msgid="6694613584743207874">"최근"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"사진 또는 동영상 없음"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"지원되는 사진 또는 동영상 없음"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"앨범 없음"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"선택 항목 보기"</string>
<string name="picker_photos" msgid="7415035516411087392">"사진"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index defc289..6b83dbb 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> объектке чейин тандаңыз}other{<xliff:g id="COUNT_1">^1</xliff:g> объектке чейин тандаңыз}}"</string>
<string name="recent" msgid="6694613584743207874">"Акыркы"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Сүрөттөр же видеолор жок"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Колдоого алынган сүрөттөр же видеолор жок"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Альбомдор жок"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Тандалганды карап көрүү"</string>
<string name="picker_photos" msgid="7415035516411087392">"Сүрөттөр"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index a4fad73..dac9580 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{ເລືອກບໍ່ເກີນ <xliff:g id="COUNT_0">^1</xliff:g> ລາຍການ}other{ເລືອກບໍ່ເກີນ <xliff:g id="COUNT_1">^1</xliff:g> ລາຍການ}}"</string>
<string name="recent" msgid="6694613584743207874">"ຫຼ້າສຸດ"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"ບໍ່ມີຮູບພາບ ຫຼື ວິດີໂອ"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"ບໍ່ມີຮູບພາບ ຫຼື ວິດີໂອທີ່ຮອງຮັບ"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"ບໍ່ມີອະລະບ້ຳ"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"ເບິ່ງອັນທີ່ເລືອກໄວ້"</string>
<string name="picker_photos" msgid="7415035516411087392">"ຮູບພາບ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 993094e..596deb6 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Pasirinkite iki <xliff:g id="COUNT_0">^1</xliff:g> elemento}one{Pasirinkite iki <xliff:g id="COUNT_1">^1</xliff:g> elemento}few{Pasirinkite iki <xliff:g id="COUNT_1">^1</xliff:g> elementų}many{Pasirinkite iki <xliff:g id="COUNT_1">^1</xliff:g> elemento}other{Pasirinkite iki <xliff:g id="COUNT_1">^1</xliff:g> elementų}}"</string>
<string name="recent" msgid="6694613584743207874">"Naujausios"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Nėra jokių nuotraukų ar vaizdo įrašų"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Nėra palaikomų nuotraukų ar vaizdo įrašų"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Nėra jokių albumų"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Žiūrėti pasirinktus"</string>
<string name="picker_photos" msgid="7415035516411087392">"Nuotraukos"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index e162077..7a83021 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Atlasiet līdz pat <xliff:g id="COUNT_0">^1</xliff:g> vienumam}zero{Atlasiet līdz pat <xliff:g id="COUNT_1">^1</xliff:g> vienumiem}one{Atlasiet līdz pat <xliff:g id="COUNT_1">^1</xliff:g> vienumam}other{Atlasiet līdz pat <xliff:g id="COUNT_1">^1</xliff:g> vienumiem}}"</string>
<string name="recent" msgid="6694613584743207874">"Jaunākie"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Nav fotoattēlu vai videoklipu"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Nav atbalstītu fotoattēlu vai video"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Nav albumu"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Skatīt atlasīto"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotoattēli"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index adb319d..caafc12 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Изберете до <xliff:g id="COUNT_0">^1</xliff:g> ставка}one{Изберете до <xliff:g id="COUNT_1">^1</xliff:g> ставка}other{Изберете до <xliff:g id="COUNT_1">^1</xliff:g> ставки}}"</string>
<string name="recent" msgid="6694613584743207874">"Неодамнешни"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Нема фотографии или видеа"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Нема поддржани фотографии или видеа"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Нема албуми"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Прикажи ги избраните"</string>
<string name="picker_photos" msgid="7415035516411087392">"Фотографии"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 5c271e8..475739e 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ഇനം വരെ തിരഞ്ഞെടുക്കുക}other{<xliff:g id="COUNT_1">^1</xliff:g> ഇനങ്ങൾ വരെ തിരഞ്ഞെടുക്കുക}}"</string>
<string name="recent" msgid="6694613584743207874">"അടുത്തിടെയുള്ളത്"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"ഫോട്ടോകളോ വീഡിയോകളോ ഇല്ല"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"പിന്തുണയ്ക്കുന്ന ഫോട്ടോകളോ വീഡിയോകളോ ഇല്ല"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"ആൽബങ്ങളൊന്നുമില്ല"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"തിരഞ്ഞെടുത്തത് കാണുക"</string>
<string name="picker_photos" msgid="7415035516411087392">"ഫോട്ടോകൾ"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 8dbedee..966dc5b 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> хүртэлх зүйл сонгоно уу}other{<xliff:g id="COUNT_1">^1</xliff:g> хүртэлх зүйл сонгоно уу}}"</string>
<string name="recent" msgid="6694613584743207874">"Саяхны"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Зураг эсвэл видео алга"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Дэмжигдсэн ямар ч зураг эсвэл видео байхгүй"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Цомог алга"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Сонгосныг харах"</string>
<string name="picker_photos" msgid="7415035516411087392">"Зураг"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index b7fabda..8084e10 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{कमाल <xliff:g id="COUNT_0">^1</xliff:g> आयटम निवडा}other{कमाल <xliff:g id="COUNT_1">^1</xliff:g> आयटम निवडा}}"</string>
<string name="recent" msgid="6694613584743207874">"अलीकडील"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"कोणतेही फोटो किंवा व्हिडिओ नाहीत"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"कोणतेही सपोर्ट असलेले फोटो किंवा व्हिडिओ नाहीत"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"कोणतेही अल्बम नाहीत"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"निवडलेले पहा"</string>
<string name="picker_photos" msgid="7415035516411087392">"फोटो"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 33a40b4..93a9385 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Pilih hingga <xliff:g id="COUNT_0">^1</xliff:g> item}other{Pilih hingga <xliff:g id="COUNT_1">^1</xliff:g> item}}"</string>
<string name="recent" msgid="6694613584743207874">"Terkini"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Tiada foto atau video"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Tiada foto atau video yang disokong"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Tiada album"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Lihat terpilih"</string>
<string name="picker_photos" msgid="7415035516411087392">"Foto"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 0d9dd7c..26ca830 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{ဖိုင် <xliff:g id="COUNT_0">^1</xliff:g> ခုအထိ ရွေးနိုင်သည်}other{ဖိုင် <xliff:g id="COUNT_1">^1</xliff:g> ခုအထိ ရွေးနိုင်သည်}}"</string>
<string name="recent" msgid="6694613584743207874">"မကြာသေးမီက"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"ဓာတ်ပုံ (သို့) ဗီဒီယိုများ မရှိပါ"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"ပံ့ပိုးထားသော ဓာတ်ပုံ (သို့) ဗီဒီယို မရှိပါ"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"အယ်လ်ဘမ်များ မရှိပါ"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"ပြသမှုကို ရွေးချယ်ထားသည်"</string>
<string name="picker_photos" msgid="7415035516411087392">"ဓာတ်ပုံများ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 7e774b2..0270fc9 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Velg opptil <xliff:g id="COUNT_0">^1</xliff:g> element}other{Velg opptil <xliff:g id="COUNT_1">^1</xliff:g> elementer}}"</string>
<string name="recent" msgid="6694613584743207874">"Nylige"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Ingen bilder eller videoer"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Ingen støttede bilder eller videoer"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Ingen album"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Vis valgte"</string>
<string name="picker_photos" msgid="7415035516411087392">"Bilder"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index f3a582b..bd152fb 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{बढीमा <xliff:g id="COUNT_0">^1</xliff:g> वटा वस्तु चयन गर्नुहोस्}other{बढीमा <xliff:g id="COUNT_1">^1</xliff:g> वटा वस्तु चयन गर्नुहोस्}}"</string>
<string name="recent" msgid="6694613584743207874">"हालसालैका"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"कुनै पनि फोटो वा भिडियो छैन"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"प्रयोग गर्न मिल्ने कुनै पनि फोटो वा भिडियो छैन"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"कुनै पनि एल्बम छैन"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"चयन गरिएका फोटो तथा भिडियोहरू हेर्नुहोस्"</string>
<string name="picker_photos" msgid="7415035516411087392">"फोटोहरू"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 4aa56b3..7fdf2a2 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Selecteer maximaal <xliff:g id="COUNT_0">^1</xliff:g> item}other{Selecteer maximaal <xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
<string name="recent" msgid="6694613584743207874">"Recent"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Geen foto\'s of video\'s"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Geen ondersteunde foto\'s of video\'s"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Geen albums"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Selectie bekijken"</string>
<string name="picker_photos" msgid="7415035516411087392">"Foto\'s"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 0348f93..e4f320b 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g>ଟି ପର୍ଯ୍ୟନ୍ତ ଆଇଟମ ଚୟନ କରନ୍ତୁ}other{<xliff:g id="COUNT_1">^1</xliff:g>ଟି ପର୍ଯ୍ୟନ୍ତ ଆଇଟମ ଚୟନ କରନ୍ତୁ}}"</string>
<string name="recent" msgid="6694613584743207874">"ବର୍ତ୍ତମାନର"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"କୌଣସି ଫଟୋ କିମ୍ବା ଭିଡିଓ ନାହିଁ"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"କୌଣସି ସମର୍ଥିତ ଫଟୋ କିମ୍ବା ଭିଡିଓ ନାହିଁ"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"କୌଣସି ଆଲବମ ନାହିଁ"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"ଚୟନିତଗୁଡ଼ିକୁ ଦେଖନ୍ତୁ"</string>
<string name="picker_photos" msgid="7415035516411087392">"ଫଟୋ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 59d507a..951198e 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ਤੱਕ ਆਈਟਮ ਚੁਣੋ}one{<xliff:g id="COUNT_1">^1</xliff:g> ਤੱਕ ਆਈਟਮ ਚੁਣੋ}other{<xliff:g id="COUNT_1">^1</xliff:g> ਤੱਕ ਆਈਟਮਾਂ ਚੁਣੋ}}"</string>
<string name="recent" msgid="6694613584743207874">"ਹਾਲੀਆ"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"ਕੋਈ ਫ਼ੋਟੋ ਜਾਂ ਵੀਡੀਓ ਨਹੀਂ"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"ਕੋਈ ਸਮਰਥਿਤ ਫ਼ੋਟੋ ਜਾਂ ਵੀਡੀਓ ਨਹੀਂ ਹੈ"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"ਕੋਈ ਐਲਬਮ ਨਹੀਂ"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"ਚੁਣੀਆਂ ਗਈਆਂ ਆਈਟਮਾਂ ਦੇਖੋ"</string>
<string name="picker_photos" msgid="7415035516411087392">"ਫ਼ੋਟੋਆਂ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 3fafa10..3898580 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Wybierz maksymalnie <xliff:g id="COUNT_0">^1</xliff:g> element}few{Wybierz maksymalnie <xliff:g id="COUNT_1">^1</xliff:g> elementy}many{Wybierz maksymalnie <xliff:g id="COUNT_1">^1</xliff:g> elementów}other{Wybierz maksymalnie <xliff:g id="COUNT_1">^1</xliff:g> elementu}}"</string>
<string name="recent" msgid="6694613584743207874">"Ostatnie"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Brak zdjęć i filmów"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Brak obsługiwanych zdjęć i filmów"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Brak albumów"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Wyświetl wybrane"</string>
<string name="picker_photos" msgid="7415035516411087392">"Zdjęcia"</string>
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
index 0ced274..0672bdc 100644
--- a/res/values-pt-rBR/strings.xml
+++ b/res/values-pt-rBR/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Selecione até <xliff:g id="COUNT_0">^1</xliff:g> item}one{Selecione até <xliff:g id="COUNT_1">^1</xliff:g> item}many{Selecione até <xliff:g id="COUNT_1">^1</xliff:g> itens}other{Selecione até <xliff:g id="COUNT_1">^1</xliff:g> itens}}"</string>
<string name="recent" msgid="6694613584743207874">"Recentes"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Sem fotos ou vídeos"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Nenhum vídeo ou foto com suporte"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Sem álbuns"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Ver selecionados"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotos"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 9c14290..e4ee77d 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Selecione até <xliff:g id="COUNT_0">^1</xliff:g> item}many{Selecione até <xliff:g id="COUNT_1">^1</xliff:g> itens}other{Selecione até <xliff:g id="COUNT_1">^1</xliff:g> itens}}"</string>
<string name="recent" msgid="6694613584743207874">"Recentes"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Nenhum vídeo ou foto"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Não existem fotos nem vídeos suportados"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Nenhum álbum"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Ver selecionado(s)"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotos"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 0ced274..0672bdc 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Selecione até <xliff:g id="COUNT_0">^1</xliff:g> item}one{Selecione até <xliff:g id="COUNT_1">^1</xliff:g> item}many{Selecione até <xliff:g id="COUNT_1">^1</xliff:g> itens}other{Selecione até <xliff:g id="COUNT_1">^1</xliff:g> itens}}"</string>
<string name="recent" msgid="6694613584743207874">"Recentes"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Sem fotos ou vídeos"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Nenhum vídeo ou foto com suporte"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Sem álbuns"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Ver selecionados"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotos"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index adc9812..28d401a 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Selectează maximum <xliff:g id="COUNT_0">^1</xliff:g> element}few{Selectează maximum <xliff:g id="COUNT_1">^1</xliff:g> elemente}other{Selectează maximum <xliff:g id="COUNT_1">^1</xliff:g> de elemente}}"</string>
<string name="recent" msgid="6694613584743207874">"Recente"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Nu există fotografii sau videoclipuri"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Fără fotografii sau videoclipuri acceptate"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Niciun album"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Vezi elementele selectate"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotografii"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index e179a86..fd0ad6e 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Выберите не более <xliff:g id="COUNT_0">^1</xliff:g> объекта.}one{Выберите не более <xliff:g id="COUNT_1">^1</xliff:g> объекта.}few{Выберите не более <xliff:g id="COUNT_1">^1</xliff:g> объектов.}many{Выберите не более <xliff:g id="COUNT_1">^1</xliff:g> объектов.}other{Выберите не более <xliff:g id="COUNT_1">^1</xliff:g> объекта.}}"</string>
<string name="recent" msgid="6694613584743207874">"Недавние"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Фото или видео нет."</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Нет поддерживаемых фото или видео"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Альбомов нет."</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Посмотреть выбранное"</string>
<string name="picker_photos" msgid="7415035516411087392">"Фотографии"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index c53f366..602f97d 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{අයිතම <xliff:g id="COUNT_0">^1</xliff:g>ක් දක්වා තෝරන්න}one{අයිතම <xliff:g id="COUNT_1">^1</xliff:g>ක් දක්වා තෝරන්න}other{අයිතම <xliff:g id="COUNT_1">^1</xliff:g>ක් දක්වා තෝරන්න}}"</string>
<string name="recent" msgid="6694613584743207874">"මෑත"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"ඡායාරූප හෝ වීඩියෝ නැත"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"සහාය දක්වන ඡායාරූප හෝ වීඩියෝ නැත"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"ඇල්බම නැත"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"තෝරා ගත් දේවල් බලන්න"</string>
<string name="picker_photos" msgid="7415035516411087392">"ඡායාරූප"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 6953e61..2d085ee 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Vyberte maximálne <xliff:g id="COUNT_0">^1</xliff:g> položku}few{Vyberte maximálne <xliff:g id="COUNT_1">^1</xliff:g> položky}many{Select up to <xliff:g id="COUNT_1">^1</xliff:g> items}other{Vyberte maximálne <xliff:g id="COUNT_1">^1</xliff:g> položiek}}"</string>
<string name="recent" msgid="6694613584743207874">"Nedávne"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Žiadne fotky ani videá"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Žiadne podporované fotky ani videá"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Žiadne albumy"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Zobraziť vybrané"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotky"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 33111b5..3eabbbb 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Izberite največ <xliff:g id="COUNT_0">^1</xliff:g> element}one{Izberite največ <xliff:g id="COUNT_1">^1</xliff:g> element}two{Izberite največ <xliff:g id="COUNT_1">^1</xliff:g> elementa}few{Izberite največ <xliff:g id="COUNT_1">^1</xliff:g> elemente}other{Izberite največ <xliff:g id="COUNT_1">^1</xliff:g> elementov}}"</string>
<string name="recent" msgid="6694613584743207874">"Nedavno"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Ni fotografij ali videoposnetkov."</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Ni podprtih fotografij ali videoposnetkov."</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Ni albumov."</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Prikaži izbrano"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotografije"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 949fc51..afaabdd 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Zgjidh deri në <xliff:g id="COUNT_0">^1</xliff:g> artikull}other{Zgjidh deri në <xliff:g id="COUNT_1">^1</xliff:g> artikuj}}"</string>
<string name="recent" msgid="6694613584743207874">"Të fundit"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Nuk ka fotografi apo video"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Nuk ka fotografi ose video të mbështetura"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Nuk ka albume"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Shiko të zgjedhurat"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotografitë"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 550356a..d502a61 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Изаберите највише <xliff:g id="COUNT_0">^1</xliff:g> ставку}one{Изаберите највише <xliff:g id="COUNT_1">^1</xliff:g> ставку}few{Изаберите највише <xliff:g id="COUNT_1">^1</xliff:g> ставке}other{Изаберите највише <xliff:g id="COUNT_1">^1</xliff:g> ставки}}"</string>
<string name="recent" msgid="6694613584743207874">"Недавно"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Нема слика нити видео снимака"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Нема подржаних слика нити видео снимака"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Нема албума"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Прикажи изабранo"</string>
<string name="picker_photos" msgid="7415035516411087392">"Слике"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index acd275e..3770468 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Välj upp till <xliff:g id="COUNT_0">^1</xliff:g> objekt}other{Välj upp till <xliff:g id="COUNT_1">^1</xliff:g> objekt}}"</string>
<string name="recent" msgid="6694613584743207874">"Senaste"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Inga foton eller videor"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Det finns inga foton eller videor som stöds"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Inga album"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Visa valda"</string>
<string name="picker_photos" msgid="7415035516411087392">"Foton"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 56a9e97..99afdb4 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Chagua hadi kipengee <xliff:g id="COUNT_0">^1</xliff:g>}other{Chagua hadi vipengee <xliff:g id="COUNT_1">^1</xliff:g>}}"</string>
<string name="recent" msgid="6694613584743207874">"Za hivi majuzi"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Hakuna picha au video"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Hakuna picha au video zinazoweza kutumika"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Hakuna albamu"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Angalia ulizochagua"</string>
<string name="picker_photos" msgid="7415035516411087392">"Picha"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index a99e423..6992ec0 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> படத்தைத் தேர்ந்தெடுங்கள்}other{<xliff:g id="COUNT_1">^1</xliff:g> படங்களைத் தேர்ந்தெடுங்கள்}}"</string>
<string name="recent" msgid="6694613584743207874">"சமீபத்தியவை"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"படங்களோ வீடியோக்களோ இல்லை"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"ஆதரிக்கப்படும் படங்களோ வீடியோக்களோ இல்லை"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"ஆல்பங்கள் இல்லை"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"தேர்ந்தெடுத்ததைக் காட்டு"</string>
<string name="picker_photos" msgid="7415035516411087392">"படங்கள்"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index b2098f9..0c9f837 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{గరిష్ఠంగా <xliff:g id="COUNT_0">^1</xliff:g> ఐటెమ్ను ఎంచుకోండి}other{గరిష్ఠంగా <xliff:g id="COUNT_1">^1</xliff:g> ఐటెమ్లను ఎంచుకోండి}}"</string>
<string name="recent" msgid="6694613584743207874">"ఇటీవలివి"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"ఫోటోలు లేదా వీడియోలు లేవు"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"సపోర్ట్ గల ఫోటోలు లేదా వీడియోలు లేవు"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"ఆల్బమ్లు ఏవీ లేవు"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"ఎంచుకున్న వాటిని చూడండి"</string>
<string name="picker_photos" msgid="7415035516411087392">"ఫోటోలు"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index d65dc7d..b5f53f6 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{เลือกได้สูงสุด <xliff:g id="COUNT_0">^1</xliff:g> รายการ}other{เลือกได้สูงสุด <xliff:g id="COUNT_1">^1</xliff:g> รายการ}}"</string>
<string name="recent" msgid="6694613584743207874">"ล่าสุด"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"ไม่มีรูปภาพหรือวิดีโอ"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"ไม่มีรูปภาพหรือวิดีโอที่รองรับ"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"ไม่มีอัลบั้ม"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"ดูรายการที่เลือก"</string>
<string name="picker_photos" msgid="7415035516411087392">"รูปภาพ"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index bac69b1..7b49abd 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Pumili ng hanggang <xliff:g id="COUNT_0">^1</xliff:g> item}one{Pumili ng hanggang <xliff:g id="COUNT_1">^1</xliff:g> item}other{Pumili ng hanggang <xliff:g id="COUNT_1">^1</xliff:g> na item}}"</string>
<string name="recent" msgid="6694613584743207874">"Kamakailan"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Walang larawan o video"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Walang sinusuportahang larawan o video"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Walang album"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Tingnan ang napili"</string>
<string name="picker_photos" msgid="7415035516411087392">"Photos"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index ae011fa..89e74bd 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{En fazla <xliff:g id="COUNT_0">^1</xliff:g> öğe seçin}other{En fazla <xliff:g id="COUNT_1">^1</xliff:g> öğe seçin}}"</string>
<string name="recent" msgid="6694613584743207874">"En son"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Fotoğraf veya video yok"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Desteklenen fotoğraf veya video yok"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Albüm yok"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Seçilenleri görüntüle"</string>
<string name="picker_photos" msgid="7415035516411087392">"Fotoğraflar"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 0f00e36..9025535 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Виберіть не більше ніж <xliff:g id="COUNT_0">^1</xliff:g> об’єкт}one{Виберіть не більше ніж <xliff:g id="COUNT_1">^1</xliff:g> об’єкт}few{Виберіть не більше ніж <xliff:g id="COUNT_1">^1</xliff:g> об’єкти}many{Виберіть не більше ніж <xliff:g id="COUNT_1">^1</xliff:g> об’єктів}other{Виберіть не більше ніж <xliff:g id="COUNT_1">^1</xliff:g> об’єкта}}"</string>
<string name="recent" msgid="6694613584743207874">"Нещодавні"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Немає фото чи відео"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Немає підтримуваних фото чи відео"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Немає альбомів"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Переглянути вибране"</string>
<string name="picker_photos" msgid="7415035516411087392">"Фото"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index b385285..b4983f7 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> آئٹم تک منتخب کریں}other{<xliff:g id="COUNT_1">^1</xliff:g> آئٹمز تک منتخب کریں}}"</string>
<string name="recent" msgid="6694613584743207874">"حالیہ"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"کوئی تصویر یا ویڈیو نہیں"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"کوئی تعاون یافتہ تصاویر یا ویڈیوز نہیں ہیں"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"کوئی البم نہیں"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"منتخب کردہ دیکھیں"</string>
<string name="picker_photos" msgid="7415035516411087392">"تصاویر"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 519bb66..d5ed046 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> tagcha elementni tanlang}other{<xliff:g id="COUNT_1">^1</xliff:g> tagcha elementni tanlang}}"</string>
<string name="recent" msgid="6694613584743207874">"Oxirgi"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Surat yoki video kiritilmagan"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Qabul qilinmaydigan rasm va videolar"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Albom kiritilmagan"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Tanlanganni koʻrish"</string>
<string name="picker_photos" msgid="7415035516411087392">"Suratlar"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index cc1cf8f..f752205 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Chọn tối đa <xliff:g id="COUNT_0">^1</xliff:g> mục}other{Chọn tối đa <xliff:g id="COUNT_1">^1</xliff:g> mục}}"</string>
<string name="recent" msgid="6694613584743207874">"Gần đây"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Không có ảnh hoặc video nào"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Không có ảnh hoặc video nào được hỗ trợ"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Không có đĩa nhạc nào"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Xem các mục được chọn"</string>
<string name="picker_photos" msgid="7415035516411087392">"Ảnh"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index f2e4890..ded4c6e 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{最多可选择 <xliff:g id="COUNT_0">^1</xliff:g> 项}other{最多可选择 <xliff:g id="COUNT_1">^1</xliff:g> 项}}"</string>
<string name="recent" msgid="6694613584743207874">"最近"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"无照片或视频"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"没有受支持的照片或视频"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"无影集"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"查看所选内容"</string>
<string name="picker_photos" msgid="7415035516411087392">"照片"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 8f51495..2ff6ae9 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{選取最多 <xliff:g id="COUNT_0">^1</xliff:g> 個項目}other{選取最多 <xliff:g id="COUNT_1">^1</xliff:g> 個項目}}"</string>
<string name="recent" msgid="6694613584743207874">"最近"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"沒有相片或影片"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"沒有支援的相片或影片"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"沒有相簿"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"查看所選項目"</string>
<string name="picker_photos" msgid="7415035516411087392">"相片"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index d099cbe..d7cb6ba 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{最多可選取 <xliff:g id="COUNT_0">^1</xliff:g> 個項目}other{最多可選取 <xliff:g id="COUNT_1">^1</xliff:g> 個項目}}"</string>
<string name="recent" msgid="6694613584743207874">"最近"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"沒有相片或影片"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"沒有支援的相片或影片"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"沒有相簿"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"查看所選項目"</string>
<string name="picker_photos" msgid="7415035516411087392">"相片"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index b3cf6f2..1965666 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -54,8 +54,7 @@
<string name="select_up_to" msgid="6994294169508439957">"{count,plural, =1{Khetha into efika kwengu-<xliff:g id="COUNT_0">^1</xliff:g>}one{Khetha izinto ezifika kwezingu-<xliff:g id="COUNT_1">^1</xliff:g>}other{Khetha izinto ezifika kwezingu-<xliff:g id="COUNT_1">^1</xliff:g>}}"</string>
<string name="recent" msgid="6694613584743207874">"Okwakamuva"</string>
<string name="picker_photos_empty_message" msgid="5980619500554575558">"Azikho izithombe noma amavidiyo"</string>
- <!-- no translation found for picker_album_media_empty_message (7061850698189881671) -->
- <skip />
+ <string name="picker_album_media_empty_message" msgid="7061850698189881671">"Azikho izithombe namavidiyo asekelwayo"</string>
<string name="picker_albums_empty_message" msgid="8341079772950966815">"Awekho ama-albhamu"</string>
<string name="picker_view_selected" msgid="2266031384396143883">"Ukubuka kukhethiwe"</string>
<string name="picker_photos" msgid="7415035516411087392">"Izithombe"</string>
diff --git a/src/com/android/providers/media/DatabaseBackupAndRecovery.java b/src/com/android/providers/media/DatabaseBackupAndRecovery.java
index b7afca8..e8027db 100644
--- a/src/com/android/providers/media/DatabaseBackupAndRecovery.java
+++ b/src/com/android/providers/media/DatabaseBackupAndRecovery.java
@@ -16,7 +16,10 @@
package com.android.providers.media;
-import static com.android.providers.media.DatabaseHelper.INTERNAL_DATABASE_NAME;
+import static com.android.providers.media.MediaProvider.getFuseDaemonForFileWithWait;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__EXTERNAL_PRIMARY;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__INTERNAL;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__PUBLIC;
import static com.android.providers.media.util.Logging.TAG;
import android.content.ContentValues;
@@ -24,6 +27,7 @@
import android.database.sqlite.SQLiteDatabase;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.MediaStore;
@@ -88,18 +92,29 @@
"/storage/emulated/" + UserHandle.myUserId();
/**
+ * Wait time of 5 seconds in millis.
+ */
+ private static final long WAIT_TIME_5_SECONDS_IN_MILLIS = 5000;
+
+ /**
+ * Wait time of 10 seconds in millis.
+ */
+ private static final long WAIT_TIME_10_SECONDS_IN_MILLIS = 10000;
+
+ /**
+ * Number of records to read from leveldb in a JNI call.
+ */
+ protected static final int LEVEL_DB_READ_LIMIT = 1000;
+
+ /**
* Stores cached value of next owner id. This helps in improving performance by backing up next
* row id less frequently in the external storage.
*/
private AtomicInteger mNextOwnerId;
-
- private final MediaProvider mMediaProvider;
private final ConfigStore mConfigStore;
private final VolumeCache mVolumeCache;
- protected DatabaseBackupAndRecovery(MediaProvider mediaProvider, ConfigStore configStore,
- VolumeCache volumeCache) {
- mMediaProvider = mediaProvider;
+ protected DatabaseBackupAndRecovery(ConfigStore configStore, VolumeCache volumeCache) {
mConfigStore = configStore;
mVolumeCache = volumeCache;
}
@@ -158,8 +173,9 @@
if (!new File(sRecoveryDirectoryPath).exists()) {
new File(sRecoveryDirectoryPath).mkdirs();
}
- MediaProvider.getFuseDaemonForFile(volume.getPath(), mVolumeCache)
- .setupVolumeDbBackup();
+ FuseDaemon fuseDaemon = getFuseDaemonForFileWithWait(volume.getPath(),
+ WAIT_TIME_5_SECONDS_IN_MILLIS);
+ fuseDaemon.setupVolumeDbBackup();
} catch (IOException e) {
Log.e(TAG, "Failure in setting up backup and recovery for volume: " + volume.getName(),
e);
@@ -169,9 +185,9 @@
/**
* Backs up databases to external storage to ensure stable URIs.
*/
- public void backupDatabases(CancellationSignal signal) {
+ public void backupDatabases(DatabaseHelper internalDatabaseHelper, CancellationSignal signal) {
Log.i(TAG, "Triggering database backup");
- backupInternalDatabase(signal);
+ backupInternalDatabase(internalDatabaseHelper, signal);
}
protected Optional<BackupIdRow> readDataFromBackup(String volumeName, String filePath) {
@@ -189,21 +205,13 @@
}
}
- protected void backupInternalDatabase(CancellationSignal signal) {
- final Optional<DatabaseHelper> dbHelper =
- mMediaProvider.getDatabaseHelper(INTERNAL_DATABASE_NAME);
-
- if (!dbHelper.isPresent()) {
- Log.e(TAG, "Unable to backup internal db");
- return;
- }
-
- final DatabaseHelper internalDbHelper = dbHelper.get();
-
+ protected void backupInternalDatabase(DatabaseHelper internalDbHelper,
+ CancellationSignal signal) {
if (!isStableUrisEnabled(MediaStore.VOLUME_INTERNAL)
|| internalDbHelper.isDatabaseRecovering()) {
return;
}
+
setupVolumeDbBackupForInternalIfMissing();
FuseDaemon fuseDaemon;
try {
@@ -605,6 +613,83 @@
}
}
+ protected void recoverData(SQLiteDatabase db, String volumeName) {
+ final long startTime = SystemClock.elapsedRealtime();
+ int i = 0;
+ final String fuseFilePath = getFuseFilePathFromVolumeName(volumeName);
+ // Wait for external primary to be attached as we use same thread for internal volume.
+ // Maximum wait for 10s
+ try {
+ getFuseDaemonForFileWithWait(new File(fuseFilePath), WAIT_TIME_10_SECONDS_IN_MILLIS);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Could not recover data as fuse daemon could not serve requests.", e);
+ return;
+ }
+
+ long rowsRecovered = 0;
+ long dirtyRowsCount = 0;
+ String[] backedUpFilePaths;
+ String lastReadValue = "";
+
+ while (true) {
+ backedUpFilePaths = readBackedUpFilePaths(volumeName, lastReadValue,
+ LEVEL_DB_READ_LIMIT);
+ if (backedUpFilePaths.length <= 0) {
+ break;
+ }
+
+ for (String filePath : backedUpFilePaths) {
+ Optional<BackupIdRow> fileRow = readDataFromBackup(volumeName, filePath);
+ if (fileRow.isPresent()) {
+ if (fileRow.get().getIsDirty()) {
+ dirtyRowsCount++;
+ continue;
+ }
+
+ insertDataInDatabase(db, fileRow.get(), filePath, volumeName);
+ rowsRecovered++;
+ }
+ }
+
+ // Read less rows than expected
+ if (backedUpFilePaths.length < LEVEL_DB_READ_LIMIT) {
+ break;
+ }
+ lastReadValue = backedUpFilePaths[backedUpFilePaths.length - 1];
+ }
+ long recoveryTime = SystemClock.elapsedRealtime() - startTime;
+ MediaProviderStatsLog.write(MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED,
+ getVolumeNameForStatsLog(volumeName), recoveryTime, rowsRecovered, dirtyRowsCount);
+ Log.i(TAG, String.format(Locale.ROOT, "%d rows recovered for volume:%s.", rowsRecovered,
+ volumeName));
+ Log.i(TAG, String.format(Locale.ROOT, "Recovery time: %d ms", recoveryTime));
+ }
+
+ protected FuseDaemon getFuseDaemonForFileWithWait(File fuseFilePath, long waitTime)
+ throws FileNotFoundException {
+ return MediaProvider.getFuseDaemonForFileWithWait(fuseFilePath, mVolumeCache, waitTime);
+ }
+
+ private int getVolumeNameForStatsLog(String volumeName) {
+ if (volumeName.equalsIgnoreCase(MediaStore.VOLUME_INTERNAL)) {
+ return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__INTERNAL;
+ } else if (volumeName.equalsIgnoreCase(MediaStore.VOLUME_EXTERNAL_PRIMARY)) {
+ return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__EXTERNAL_PRIMARY;
+ }
+
+ return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__PUBLIC;
+ }
+
+ private static String getFuseFilePathFromVolumeName(String volumeName) {
+ switch (volumeName) {
+ case MediaStore.VOLUME_INTERNAL:
+ case MediaStore.VOLUME_EXTERNAL_PRIMARY:
+ return EXTERNAL_PRIMARY_ROOT_PATH;
+ default:
+ return "/storage/" + volumeName;
+ }
+ }
+
/**
* Returns list of backed up files from external storage.
*/
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index c8990ed..30a0bf5 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -18,9 +18,6 @@
import static com.android.providers.media.DatabaseBackupAndRecovery.getXattr;
import static com.android.providers.media.DatabaseBackupAndRecovery.setXattr;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__EXTERNAL_PRIMARY;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__INTERNAL;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__PUBLIC;
import static com.android.providers.media.util.DatabaseUtils.bindList;
import static com.android.providers.media.util.Logging.LOGV;
import static com.android.providers.media.util.Logging.TAG;
@@ -71,7 +68,6 @@
import com.android.modules.utils.BackgroundThread;
import com.android.providers.media.dao.FileRow;
import com.android.providers.media.playlist.Playlist;
-import com.android.providers.media.stableuris.dao.BackupIdRow;
import com.android.providers.media.util.DatabaseUtils;
import com.android.providers.media.util.FileUtils;
import com.android.providers.media.util.ForegroundThread;
@@ -154,8 +150,6 @@
*/
private static final String DATA_MEDIA_XATTR_DIRECTORY_PATH = "/data/media/0";
- protected static final int LEVEL_DB_READ_LIMIT = 1000;
-
static final String INTERNAL_DATABASE_NAME = "internal.db";
static final String EXTERNAL_DATABASE_NAME = "external.db";
@@ -184,6 +178,7 @@
long mScanStartTime;
long mScanStopTime;
private boolean mEnableNextRowIdRecovery;
+ private final DatabaseBackupAndRecovery mDatabaseBackupAndRecovery;
/**
* Unfortunately we can have multiple instances of DatabaseHelper, causing
@@ -250,10 +245,11 @@
@Nullable OnSchemaChangeListener schemaListener,
@Nullable OnFilesChangeListener filesListener,
@NonNull OnLegacyMigrationListener migrationListener,
- @Nullable UnaryOperator<String> idGenerator, boolean enableNextRowIdRecovery) {
+ @Nullable UnaryOperator<String> idGenerator, boolean enableNextRowIdRecovery,
+ DatabaseBackupAndRecovery databaseBackupAndRecovery) {
this(context, name, getDatabaseVersion(context), earlyUpgrade, legacyProvider,
- projectionHelper, schemaListener, filesListener,
- migrationListener, idGenerator, enableNextRowIdRecovery);
+ projectionHelper, schemaListener, filesListener,
+ migrationListener, idGenerator, enableNextRowIdRecovery, databaseBackupAndRecovery);
}
public DatabaseHelper(Context context, String name, int version,
@@ -262,7 +258,8 @@
@Nullable OnSchemaChangeListener schemaListener,
@Nullable OnFilesChangeListener filesListener,
@NonNull OnLegacyMigrationListener migrationListener,
- @Nullable UnaryOperator<String> idGenerator, boolean enableNextRowIdRecovery) {
+ @Nullable UnaryOperator<String> idGenerator, boolean enableNextRowIdRecovery,
+ DatabaseBackupAndRecovery databaseBackupAndRecovery) {
super(context, name, null, version);
mContext = context;
mName = name;
@@ -283,6 +280,7 @@
mIdGenerator = idGenerator;
mMigrationFileName = "." + mVolumeName;
this.mEnableNextRowIdRecovery = enableNextRowIdRecovery;
+ this.mDatabaseBackupAndRecovery = databaseBackupAndRecovery;
// Configure default filters until we hear differently
if (isInternal()) {
@@ -550,17 +548,9 @@
}
private void tryRecoverDatabase(SQLiteDatabase db) {
- MediaProvider mediaProvider;
- try (ContentProviderClient cpc = mContext.getContentResolver()
- .acquireContentProviderClient(MediaStore.AUTHORITY)) {
- mediaProvider = ((MediaProvider) cpc.getLocalContentProvider());
- } catch (Exception e) {
- throw new RuntimeException("Could not retrieve local content provider", e);
- }
String volumeName =
isInternal() ? MediaStore.VOLUME_INTERNAL : MediaStore.VOLUME_EXTERNAL;
- if (!isInternal()
- || !mediaProvider.getDatabaseBackupAndRecovery().isStableUrisEnabled(volumeName)) {
+ if (!isInternal() || !mDatabaseBackupAndRecovery.isStableUrisEnabled(volumeName)) {
return;
}
@@ -574,8 +564,7 @@
// StableUrisIdleMaintenanceService will be attempted to run only once in 7days.
// Any rollback before that will not recover DB rows.
BackgroundThread.getExecutor().execute(
- () -> mediaProvider.getDatabaseBackupAndRecovery()
- .backupInternalDatabase(null));
+ () -> mDatabaseBackupAndRecovery.backupInternalDatabase(this, null));
// Set next row id in External Storage to handle rollback in future.
backupNextRowId(NEXT_ROW_ID_DEFAULT_BILLION_VALUE);
updateSessionIdInDatabaseAndExternalStorage(db);
@@ -599,7 +588,7 @@
// Recover data from backup
// Ensure we do not back up in case of recovery.
mIsRecovering.set(true);
- recoverData(mediaProvider, db, volumeName);
+ mDatabaseBackupAndRecovery.recoverData(db, volumeName);
updateNextRowIdInDatabaseAndExternalStorage(db);
mIsRecovering.set(false);
updateSessionIdInDatabaseAndExternalStorage(db);
@@ -621,88 +610,6 @@
}
}
- @GuardedBy("sRecoveryLock")
- private void recoverData(MediaProvider mediaProvider, SQLiteDatabase db, String volumeName) {
- final long startTime = SystemClock.elapsedRealtime();
- int i = 0;
- final String fuseFilePath = getFuseFilePathFromVolumeName(volumeName);
- // Wait for external primary to be attached as we use same thread for internal volume.
- // Maximum wait for 10s
- DatabaseBackupAndRecovery dbBackupAndRecovery =
- mediaProvider.getDatabaseBackupAndRecovery();
- while (!dbBackupAndRecovery.isFuseDaemonReadyForFilePath(fuseFilePath)
- && i < 1000) {
- Log.d(TAG, "Waiting for fuse daemon to be ready.");
- // Poll after every 10ms
- SystemClock.sleep(10);
- i++;
- }
- if (!dbBackupAndRecovery.isFuseDaemonReadyForFilePath(fuseFilePath)) {
- Log.e(TAG, "Could not recover data as fuse daemon could not serve requests.");
- return;
- }
-
- long rowsRecovered = 0;
- long dirtyRowsCount = 0;
- String[] backedUpFilePaths;
- String lastReadValue = "";
-
- while (true) {
- backedUpFilePaths = mediaProvider.getDatabaseBackupAndRecovery()
- .readBackedUpFilePaths(volumeName, lastReadValue, LEVEL_DB_READ_LIMIT);
- if (backedUpFilePaths.length <= 0) {
- break;
- }
-
- for (String filePath : backedUpFilePaths) {
- Optional<BackupIdRow> fileRow = mediaProvider.getDatabaseBackupAndRecovery()
- .readDataFromBackup(volumeName, filePath);
- if (fileRow.isPresent()) {
- if (fileRow.get().getIsDirty()) {
- dirtyRowsCount++;
- continue;
- }
-
- dbBackupAndRecovery.insertDataInDatabase(db, fileRow.get(), filePath,
- volumeName);
- rowsRecovered++;
- }
- }
-
- // Read less rows than expected
- if (backedUpFilePaths.length < LEVEL_DB_READ_LIMIT) {
- break;
- }
- lastReadValue = backedUpFilePaths[backedUpFilePaths.length - 1];
- }
- long recoveryTime = SystemClock.elapsedRealtime() - startTime;
- MediaProviderStatsLog.write(MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED,
- getVolumeName(volumeName), recoveryTime, rowsRecovered, dirtyRowsCount);
- Log.i(TAG, String.format(Locale.ROOT, "%d rows recovered for volume:%s.", rowsRecovered,
- volumeName));
- Log.i(TAG, String.format(Locale.ROOT, "Recovery time: %d ms", recoveryTime));
- }
-
- private int getVolumeName(String volumeName) {
- if (volumeName.equalsIgnoreCase(MediaStore.VOLUME_INTERNAL)) {
- return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__INTERNAL;
- } else if (volumeName.equalsIgnoreCase(MediaStore.VOLUME_EXTERNAL_PRIMARY)) {
- return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__EXTERNAL_PRIMARY;
- }
-
- return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__PUBLIC;
- }
-
- private static String getFuseFilePathFromVolumeName(String volumeName) {
- switch (volumeName) {
- case MediaStore.VOLUME_INTERNAL:
- case MediaStore.VOLUME_EXTERNAL_PRIMARY:
- return "/storage/emulated/" + UserHandle.myUserId();
- default:
- return "/storage/" + volumeName;
- }
- }
-
private void tryRecoverRowIdSequence(SQLiteDatabase db) {
if (isInternal()) {
// Database row id recovery for internal is handled in tryRecoverDatabase()
diff --git a/src/com/android/providers/media/LocalUriMatcher.java b/src/com/android/providers/media/LocalUriMatcher.java
index 755c4df..6a9174f 100644
--- a/src/com/android/providers/media/LocalUriMatcher.java
+++ b/src/com/android/providers/media/LocalUriMatcher.java
@@ -77,7 +77,6 @@
static final int PICKER_INTERNAL_MEDIA_LOCAL = 903;
static final int PICKER_INTERNAL_ALBUMS_ALL = 904;
static final int PICKER_INTERNAL_ALBUMS_LOCAL = 905;
- static final int PICKER_UNRELIABLE_VOLUME = 906;
// MediaProvider Command Line Interface
static final int CLI = 100_000;
@@ -119,8 +118,6 @@
mPublic.addURI(auth, "picker/#/#", PICKER_ID);
// content://media/picker/<user-id>/<authority>/media/<media-id>
mPublic.addURI(auth, "picker/#/*/media/*", PICKER_ID);
- // content://media/picker/unreliable/<media_id>
- mPublic.addURI(auth, "picker/unreliable/#", PICKER_UNRELIABLE_VOLUME);
mPublic.addURI(auth, "cli", CLI);
diff --git a/src/com/android/providers/media/MediaGrants.java b/src/com/android/providers/media/MediaGrants.java
index 1aaa60d..b08bb63 100644
--- a/src/com/android/providers/media/MediaGrants.java
+++ b/src/com/android/providers/media/MediaGrants.java
@@ -26,6 +26,8 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.providers.media.photopicker.PickerSyncController;
import java.util.List;
@@ -107,32 +109,35 @@
* database entry in files table. Any deletion in files table will automatically delete
* corresponding media_grants.
*
+ * <p>The action is performed for only specific {@code user}.</p>
+ *
* @param packageName the package name to clear media grants for.
* @param reason a logged reason why the grants are being cleared.
+ * @param user the user for which the grants need to be modified.
*
* @return the number of grants removed.
*/
- int removeAllMediaGrantsForPackage(String packageName, String reason)
+ int removeAllMediaGrantsForPackage(String packageName, String reason,
+ @NonNull Integer user)
throws IllegalArgumentException {
Objects.requireNonNull(packageName);
if (TextUtils.isEmpty(packageName)) {
throw new IllegalArgumentException(
"Removing grants requires a non empty package name.");
}
-
return mExternalDatabase.runWithTransaction(
(db) -> {
int grantsRemoved =
mQueryBuilder.delete(
- db,
- /* selection= */ String.format(
- "%s = ?", OWNER_PACKAGE_NAME_COLUMN),
- /* selectionArgs= */ new String[] {packageName});
- Log.d(
- TAG,
- String.format(
- "Removed %s media_grants for %s. Reason: %s",
- grantsRemoved, packageName, reason));
+ db, String.format(
+ "%s = ? AND %s = ?", OWNER_PACKAGE_NAME_COLUMN,
+ PACKAGE_USER_ID_COLUMN),
+ new String[]{packageName, String.valueOf(user)});
+ Log.d(TAG,
+ String.format("Removed %s media_grants for %s user for %s. Reason: %s",
+ grantsRemoved, String.valueOf(user),
+ packageName,
+ reason));
return grantsRemoved;
});
}
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index d7ecd7a..6677224 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -54,9 +54,9 @@
import static com.android.providers.media.AccessChecker.getWhereForUserSelectedAccess;
import static com.android.providers.media.AccessChecker.hasAccessToCollection;
import static com.android.providers.media.AccessChecker.hasUserSelectedAccess;
+import static com.android.providers.media.DatabaseBackupAndRecovery.LEVEL_DB_READ_LIMIT;
import static com.android.providers.media.DatabaseHelper.EXTERNAL_DATABASE_NAME;
import static com.android.providers.media.DatabaseHelper.INTERNAL_DATABASE_NAME;
-import static com.android.providers.media.DatabaseHelper.LEVEL_DB_READ_LIMIT;
import static com.android.providers.media.LocalCallingIdentity.APPOP_REQUEST_INSTALL_PACKAGES_FOR_SHARED_UID;
import static com.android.providers.media.LocalCallingIdentity.PERMISSION_ACCESS_MTP;
import static com.android.providers.media.LocalCallingIdentity.PERMISSION_INSTALL_PACKAGES;
@@ -107,7 +107,6 @@
import static com.android.providers.media.LocalUriMatcher.PICKER_INTERNAL_ALBUMS_LOCAL;
import static com.android.providers.media.LocalUriMatcher.PICKER_INTERNAL_MEDIA_ALL;
import static com.android.providers.media.LocalUriMatcher.PICKER_INTERNAL_MEDIA_LOCAL;
-import static com.android.providers.media.LocalUriMatcher.PICKER_UNRELIABLE_VOLUME;
import static com.android.providers.media.LocalUriMatcher.VERSION;
import static com.android.providers.media.LocalUriMatcher.VIDEO_MEDIA;
import static com.android.providers.media.LocalUriMatcher.VIDEO_MEDIA_ID;
@@ -281,6 +280,7 @@
import com.android.providers.media.photopicker.data.PickerDbFacade;
import com.android.providers.media.playlist.Playlist;
import com.android.providers.media.scan.MediaScanner;
+import com.android.providers.media.scan.MediaScanner.ScanReason;
import com.android.providers.media.scan.ModernMediaScanner;
import com.android.providers.media.util.CachedSupplier;
import com.android.providers.media.util.DatabaseUtils;
@@ -440,6 +440,12 @@
* kept around for app compatibility in R.
*/
private static final String QUERY_ARG_DO_ASYNC_SCAN = "android:query-arg-do-async-scan";
+
+ /**
+ * Time between two polling attempts for availability of FuseDaemon thread.
+ */
+ private static final long POLLING_TIME_IN_MILLIS = 100;
+
/**
* Enable option to defer the scan triggered as part of MediaProvider#update()
*/
@@ -617,20 +623,24 @@
if (!SdkLevel.isAtLeastU() || !isReadMediaAppOp(op)) {
return;
}
-
- final int uid;
- try {
- uid = getContext().getPackageManager().getPackageUid(packageName, 0);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Unable to resolve uid. Ignoring the AppOp change for " + packageName);
- return;
- }
-
- final LocalCallingIdentity localCallingIdentity =
- LocalCallingIdentity.fromExternal(getContext(), mUserCache, uid);
- if (!localCallingIdentity.checkCallingPermissionUserSelected()) {
- // Revoke media grants if permission state is not "Select flow".
- mMediaGrants.removeAllMediaGrantsForPackage(packageName, "op " + op /* reason */);
+ Context context = getContext();
+ PackageManager packageManager = context.getPackageManager();
+ List<UserHandle> userHandles = mUserCache.getUsersCached();
+ for (UserHandle user : userHandles) {
+ try {
+ int uid = packageManager.getPackageUid(packageName, user.getIdentifier());
+ if (!LocalCallingIdentity.fromExternal(context, mUserCache, uid)
+ .checkCallingPermissionUserSelected()) {
+ // If for any user profile containing the package, the UserSelected permission
+ // has been removed then remove all media grants for that package.
+ // Revoke media grants if permission state is not "Select flow".
+ mMediaGrants.removeAllMediaGrantsForPackage(packageName, "op "
+ + op /* reason */, user.getIdentifier());
+ }
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Unable to resolve uid. Ignoring the AppOp change for "
+ + packageName + ", User : " + user.getIdentifier());
+ }
}
}
@@ -1179,26 +1189,26 @@
final int thumbSize = Math.min(metrics.widthPixels, metrics.heightPixels) / 2;
mThumbSize = new Size(thumbSize, thumbSize);
+ mConfigStore = createConfigStore();
+ mDatabaseBackupAndRecovery = createDatabaseBackupAndRecovery();
+
mMediaScanner = new ModernMediaScanner(context);
mProjectionHelper = new ProjectionHelper(Column.class, ExportedSince.class);
mInternalDatabase = new DatabaseHelper(context, INTERNAL_DATABASE_NAME, false, false,
mProjectionHelper, Metrics::logSchemaChange, mFilesListener,
- MIGRATION_LISTENER, mIdGenerator, true);
+ MIGRATION_LISTENER, mIdGenerator, true, mDatabaseBackupAndRecovery);
mExternalDatabase = new DatabaseHelper(context, EXTERNAL_DATABASE_NAME, false, false,
mProjectionHelper, Metrics::logSchemaChange, mFilesListener,
- MIGRATION_LISTENER, mIdGenerator, true);
+ MIGRATION_LISTENER, mIdGenerator, true, mDatabaseBackupAndRecovery);
mExternalDbFacade = new ExternalDbFacade(getContext(), mExternalDatabase, mVolumeCache);
mPickerDbFacade = new PickerDbFacade(context);
mMediaGrants = new MediaGrants(mExternalDatabase);
- mConfigStore = createConfigStore();
mPickerSyncController = new PickerSyncController(context, mPickerDbFacade, mConfigStore);
mPickerDataLayer = new PickerDataLayer(context, mPickerDbFacade, mPickerSyncController);
mPickerUriResolver = new PickerUriResolver(context, mPickerDbFacade, mProjectionHelper);
- mDatabaseBackupAndRecovery = createDatabaseBackupAndRecovery();
-
if (SdkLevel.isAtLeastS()) {
mTranscodeHelper = new TranscodeHelperImpl(context, this, mConfigStore);
} else {
@@ -1816,7 +1826,8 @@
// Orphan rest of entries.
orphanEntries(db, packageName, userId);
// TODO(b/260685885): Add e2e tests to ensure these are cleared when a package is removed.
- mMediaGrants.removeAllMediaGrantsForPackage(packageName, /* reason */ "Package orphaned");
+ mMediaGrants.removeAllMediaGrantsForPackage(packageName, /* reason */ "Package orphaned",
+ userId);
}
private void deleteAndroidMediaEntries(SQLiteDatabase db, String packageName, int userId) {
@@ -1861,16 +1872,12 @@
}
}
- public void scanDirectory(File file, int reason) {
- mMediaScanner.scanDirectory(file, reason);
+ public void scanDirectory(@NonNull File dir, @ScanReason int reason) {
+ mMediaScanner.scanDirectory(dir, reason);
}
- public Uri scanFile(File file, int reason) {
- return scanFile(file, reason, null);
- }
-
- public Uri scanFile(File file, int reason, String ownerPackage) {
- return mMediaScanner.scanFile(file, reason, ownerPackage);
+ public Uri scanFile(@NonNull File file, @ScanReason int reason) {
+ return mMediaScanner.scanFile(file, reason);
}
private Uri scanFileAsMediaProvider(File file, int reason) {
@@ -6526,7 +6533,7 @@
getContext().enforceCallingPermission(Manifest.permission.WRITE_MEDIA_STORAGE,
"Permission missing to call RUN_IDLE_MAINTENANCE_FOR_STABLE_URIS by "
+ "uid:" + Binder.getCallingUid());
- mDatabaseBackupAndRecovery.backupDatabases(null);
+ backupDatabases(null);
return new Bundle();
}
case MediaStore.READ_BACKED_UP_FILE_PATHS: {
@@ -6580,6 +6587,10 @@
}
}
+ public void backupDatabases(CancellationSignal signal) {
+ mDatabaseBackupAndRecovery.backupDatabases(mInternalDatabase, signal);
+ }
+
private void syncAllMedia() {
// Clear the binder calling identity so that we can sync the unexported
// local_provider while running as MediaProvider
@@ -7989,11 +8000,6 @@
return match == PICKER_ID;
}
- public boolean isPickerUnreliableVolumeUri(Uri uri, boolean allowHidden) {
- final int match = matchUri(uri, allowHidden);
- return match == PICKER_UNRELIABLE_VOLUME;
- }
-
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
return openFileCommon(uri, mode, /*signal*/ null, /*opts*/ null);
@@ -8428,6 +8434,28 @@
}
}
+ @NonNull
+ public static FuseDaemon getFuseDaemonForFileWithWait(@NonNull File file,
+ VolumeCache volumeCache, long waitTimeInMilliseconds) throws FileNotFoundException {
+ FuseDaemon fuseDaemon = null;
+ long time = 0;
+ while (time < waitTimeInMilliseconds) {
+ fuseDaemon = ExternalStorageServiceImpl.getFuseDaemon(
+ volumeCache.getVolumeId(file));
+ if (fuseDaemon != null) {
+ break;
+ }
+ SystemClock.sleep(POLLING_TIME_IN_MILLIS);
+ time += POLLING_TIME_IN_MILLIS;
+ }
+
+ if (fuseDaemon == null) {
+ throw new FileNotFoundException("Missing FUSE daemon for " + file);
+ } else {
+ return fuseDaemon;
+ }
+ }
+
private void invalidateFuseDentry(@NonNull File file) {
invalidateFuseDentry(file.getAbsolutePath());
}
@@ -10743,6 +10771,6 @@
}
protected DatabaseBackupAndRecovery createDatabaseBackupAndRecovery() {
- return new DatabaseBackupAndRecovery(this, mConfigStore, mVolumeCache);
+ return new DatabaseBackupAndRecovery(mConfigStore, mVolumeCache);
}
}
diff --git a/src/com/android/providers/media/VolumeCache.java b/src/com/android/providers/media/VolumeCache.java
index 7087656..dae8031 100644
--- a/src/com/android/providers/media/VolumeCache.java
+++ b/src/com/android/providers/media/VolumeCache.java
@@ -87,24 +87,6 @@
}
}
- /**
- * @return List of paths to unreliable volumes if any, an empty list otherwise
- */
- public @NonNull List<File> getUnreliableVolumePath() throws FileNotFoundException {
- List<File> unreliableVolumes = new ArrayList<>();
- synchronized (mLock) {
- for (MediaVolume volume : mExternalVolumes){
- final File volPath = volume.getPath();
- if (volPath != null && volPath.getPath() != null
- && !volPath.getPath().startsWith("/storage/")){
- unreliableVolumes.add(volPath);
- }
- }
- }
-
- return unreliableVolumes;
- }
-
public @NonNull MediaVolume findVolume(@NonNull String volumeName, @NonNull UserHandle user)
throws FileNotFoundException {
synchronized (mLock) {
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
index bc20b8b..86565c8 100644
--- a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
+++ b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
@@ -267,7 +267,7 @@
// TODO(b/195009187): Settings menu item is hidden by default till Settings page is
// completely developed.
- settingsMenuItem.setVisible(isSettingsScreenEnabled());
+ settingsMenuItem.setVisible(shouldShowSettingsScreen());
// Browse menu item allows users to launch DocumentsUI. This item should only be shown if
// PhotoPicker was opened via {@link #ACTION_GET_CONTENT}.
@@ -310,7 +310,7 @@
// app or account has changed. Currently, we'll reset picker each time it restarts when
// settings page is enabled to avoid the scenario where cloud provider app or account has
// changed but picker continues to show stale data from old provider app and account.
- if (isSettingsScreenEnabled()) {
+ if (shouldShowSettingsScreen()) {
reset(/* switchToPersonalProfile */ false);
}
}
@@ -819,7 +819,16 @@
/**
* Returns {@code true} if settings page is enabled.
*/
- private boolean isSettingsScreenEnabled() {
+ private boolean shouldShowSettingsScreen() {
+ if (mPickerViewModel.isUserSelectForApp() || mPickerViewModel.isLocalOnly()) {
+ // We only show local items in below cases.
+ // 1. Photo Picker is launched by {@link MediaStore#ACTION_USER_SELECT_IMAGES_FOR_APP}
+ // action for permission flow.
+ // 2. Photo Picker is launched with {@link EXTRA_LOCAL_ONLY} as true in
+ // {@link ACTION_GET_CONTENT} or {@link ACTION_PICK_IMAGES}.
+ return false;
+ }
+
final ComponentName componentName = new ComponentName(this,
PhotoPickerSettingsActivity.class);
return getPackageManager().getComponentEnabledSetting(componentName)
diff --git a/src/com/android/providers/media/photopicker/UnreliableVolumeFacade.java b/src/com/android/providers/media/photopicker/UnreliableVolumeFacade.java
deleted file mode 100644
index 5006663..0000000
--- a/src/com/android/providers/media/photopicker/UnreliableVolumeFacade.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2021 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;
-
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteConstraintException;
-import android.database.sqlite.SQLiteDatabase;
-
-import android.net.Uri;
-import android.util.Log;
-
-import com.android.providers.media.photopicker.data.UnreliableVolumeDatabaseHelper;
-import com.android.providers.media.util.SQLiteQueryBuilder;
-
-import java.util.List;
-
-public class UnreliableVolumeFacade {
- private static final String TAG = "UnreliableVolumeFacade";
- private static final String TABLE_NAME = "media";
-
- private static final int FAIL = -1;
- private static final int SUCCESS = 1;
-
- private SQLiteDatabase mDatabase;
- private SQLiteQueryBuilder mQueryBuilder;
-
- private static final String[] mColumns = new String[]{
- UnreliableVolumeDatabaseHelper.MediaColumns._ID,
- UnreliableVolumeDatabaseHelper.MediaColumns._DATA,
- UnreliableVolumeDatabaseHelper.MediaColumns.DATE_MODIFIED,
- UnreliableVolumeDatabaseHelper.MediaColumns.DISPLAY_NAME,
- UnreliableVolumeDatabaseHelper.MediaColumns.SIZE_BYTES,
- UnreliableVolumeDatabaseHelper.MediaColumns.MIME_TYPE
- };
-
- public UnreliableVolumeFacade(Context context) {
- UnreliableVolumeDatabaseHelper dbHelper = new UnreliableVolumeDatabaseHelper(context);
- mDatabase = dbHelper.getWritableDatabase();
- mQueryBuilder = createQueryBuilder();
- }
-
- /**
- * @return the media item from the media table with given {@code uri}
- */
- public Cursor queryMediaId(Uri uri) {
- String id = String.valueOf(ContentUris.parseId(uri));
- final String selection = UnreliableVolumeDatabaseHelper.MediaColumns._ID + " = " + id;
- return mDatabase.query(TABLE_NAME, mColumns, selection, /* selectionArgs */ null,
- /* groupBy */ null, /* having */ null, /* orderBy */ null);
- }
-
- /**
- * @return {@link Cursor} with all the rows in media table
- */
- public Cursor queryMediaAll() {
- return mDatabase.query(TABLE_NAME, mColumns, /* selection */ null, /* selectionArgs */ null,
- /* groupBy */ null, /* having */ null, /* orderBy */ null);
- }
-
- private SQLiteQueryBuilder createQueryBuilder() {
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables(TABLE_NAME);
-
- return qb;
- }
-
- private int insertFile(ContentValues value) {
- try {
- if (mQueryBuilder.insert(mDatabase, value) > 0) {
- return SUCCESS;
- }
- } catch (SQLiteConstraintException e) {
- Log.e(TAG, "Failed to insert picker db media. ContentValues: " + value, e);
- }
- return FAIL;
- }
-
- public int insertMedia(List<ContentValues> values) {
- int numberItemsInserted = 0;
- mDatabase.beginTransaction();
- try {
- for (ContentValues value : values) {
- if (insertFile(value) == SUCCESS) {
- numberItemsInserted++;
- }
- }
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
-
- return numberItemsInserted;
- }
-
- public void deleteMedia() {
- mDatabase.delete(TABLE_NAME, /* whereClause */null, /* whereArgs */null);
- }
-}
\ No newline at end of file
diff --git a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
index 014d5d9..dd77d5d 100644
--- a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
+++ b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
@@ -831,7 +831,7 @@
}
addMimeTypesToQueryBuilderAndSelectionArgs(qb, selectionArgs, query.mMimeTypes);
- Cursor cursor = qb.query(mDatabase, getAlbumProjection(), /* selection */ null,
+ Cursor cursor = qb.query(mDatabase, getMergedAlbumProjection(), /* selection */ null,
selectionArgs.toArray(new String[0]), /* groupBy */ null, /* having */ null,
/* orderBy */ null, /* limit */ null);
@@ -857,17 +857,20 @@
return c;
}
- private String[] getAlbumProjection() {
+ private String[] getMergedAlbumProjection() {
return new String[] {
"COUNT(" + KEY_ID + ") AS " + CloudMediaProviderContract.AlbumColumns.MEDIA_COUNT,
"MAX(" + KEY_DATE_TAKEN_MS + ") AS "
+ CloudMediaProviderContract.AlbumColumns.DATE_TAKEN_MILLIS,
String.format("IFNULL(%s, %s) AS %s", KEY_CLOUD_ID,
KEY_LOCAL_ID, CloudMediaProviderContract.AlbumColumns.MEDIA_COVER_ID),
- // Note that we prefer local provider over cloud provider if a media item is present
- // locally and on cloud.
+ // Note that we prefer cloud_id over local_id here. This logic is for computing the
+ // projection and doesn't affect the filtering of results which has already been
+ // done and ensures that only is_visible=true items are returned.
+ // Here, we need to distinguish between cloud+local and local-only items to
+ // determine the correct authority.
String.format("CASE WHEN %s IS NULL THEN '%s' ELSE '%s' END AS %s",
- KEY_LOCAL_ID, mCloudProvider, mLocalProvider, AlbumColumns.AUTHORITY)
+ KEY_CLOUD_ID, mLocalProvider, mCloudProvider, AlbumColumns.AUTHORITY)
};
}
diff --git a/src/com/android/providers/media/photopicker/data/UnreliableVolumeDatabaseHelper.java b/src/com/android/providers/media/photopicker/data/UnreliableVolumeDatabaseHelper.java
deleted file mode 100644
index 9679954..0000000
--- a/src/com/android/providers/media/photopicker/data/UnreliableVolumeDatabaseHelper.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2021 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.data;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.util.Log;
-
-import static com.android.providers.media.DatabaseHelper.VERSION_LATEST;
-
-import androidx.annotation.VisibleForTesting;
-
-public class UnreliableVolumeDatabaseHelper extends SQLiteOpenHelper implements AutoCloseable {
- final Context mContext;
- final String mName;
- final int mVersion;
-
- private static final String UNRELIABLE_VOLUME_DATABASE_NAME = "pickerUnreliableVolume.db";
- private static final String TAG = "PickerUnreliableVolumeHelper";
-
- public static final class MediaColumns {
- private MediaColumns() {}
- public static final String _ID = "_id";
- public static final String DISPLAY_NAME = "display_name";
- public static final String _DATA = "_data";
- public static final String DATE_MODIFIED = "date_modified";
- public static final String SIZE_BYTES = "size_bytes";
- public static final String MIME_TYPE = "mime_type";
- }
-
- public UnreliableVolumeDatabaseHelper(Context context) {
- this(context, UNRELIABLE_VOLUME_DATABASE_NAME, VERSION_LATEST);
- }
-
- public UnreliableVolumeDatabaseHelper(Context context, String name, int version) {
- super(context, name, null, version);
- mContext = context;
- mName = name;
- mVersion = version;
-
- setWriteAheadLoggingEnabled(true);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- Log.v(TAG, "onCreate() for " + mName);
-
- createSchema(db);
- createIndexes(db);
- }
-
- @Override
- public void onUpgrade(final SQLiteDatabase db, final int oldV, final int newV) {
- Log.v(TAG, "onUpgrade() for " + mName + " from " + oldV + " to " + newV);
- }
-
- @VisibleForTesting
- static void makePristineSchema(SQLiteDatabase db) {
- // drop all tables
- Cursor c = db.query("sqlite_master", new String[] {"name"}, "type is 'table'", null, null,
- null, null);
- while (c.moveToNext()) {
- if (c.getString(0).startsWith("sqlite_")) continue;
- db.execSQL("DROP TABLE IF EXISTS " + c.getString(0));
- }
- c.close();
- }
-
- @VisibleForTesting
- static void makePristineIndexes(SQLiteDatabase db) {
- // drop all indexes
- Cursor c = db.query("sqlite_master", new String[] {"name"}, "type is 'index'", null,
- null, null, null);
- while (c.moveToNext()) {
- if (c.getString(0).startsWith("sqlite_")) continue;
- db.execSQL("DROP INDEX IF EXISTS " + c.getString(0));
- }
- c.close();
- }
-
- private void createSchema(SQLiteDatabase db) {
- makePristineSchema(db);
-
- db.execSQL("CREATE TABLE media (_id INTEGER PRIMARY KEY AUTOINCREMENT,"
- + "date_modified INTEGER NOT NULL CHECK(date_modified >= 0),"
- + "size_bytes INTEGER NOT NULL CHECK(size_bytes > 0),"
- + "display_name TEXT NOT NULL,"
- + "_data TEXT NOT NULL UNIQUE COLLATE NOCASE,"
- + "mime_type TEXT NOT NULL)");
- }
-
- private void createIndexes(SQLiteDatabase db) {
- makePristineIndexes(db);
-
- db.execSQL("CREATE INDEX path_index on media(_data)");
- db.execSQL("CREATE INDEX display_name_index on media(display_name)");
- db.execSQL("CREATE INDEX date_modified_index on media(date_modified)");
- db.execSQL("CREATE INDEX size_index on media(size_bytes)");
- db.execSQL("CREATE INDEX mime_type_index on media(mime_type)");
- }
-}
diff --git a/src/com/android/providers/media/photopicker/ui/AlbumsTabAdapter.java b/src/com/android/providers/media/photopicker/ui/AlbumsTabAdapter.java
index 4da9152..8cae259 100644
--- a/src/com/android/providers/media/photopicker/ui/AlbumsTabAdapter.java
+++ b/src/com/android/providers/media/photopicker/ui/AlbumsTabAdapter.java
@@ -45,11 +45,17 @@
@NonNull LiveData<String> cloudMediaAccountName,
@NonNull LiveData<Boolean> shouldShowChooseAppBanner,
@NonNull LiveData<Boolean> shouldShowCloudMediaAvailableBanner,
- @NonNull OnBannerClickListener onChooseAppBannerClickListener,
- @NonNull OnBannerClickListener onCloudMediaAvailableBannerClickListener) {
+ @NonNull LiveData<Boolean> shouldShowAccountUpdatedBanner,
+ @NonNull LiveData<Boolean> shouldShowChooseAccountBanner,
+ @NonNull OnBannerEventListener onChooseAppBannerEventListener,
+ @NonNull OnBannerEventListener onCloudMediaAvailableBannerEventListener,
+ @NonNull OnBannerEventListener onAccountUpdatedBannerEventListener,
+ @NonNull OnBannerEventListener onChooseAccountBannerEventListener) {
super(imageLoader, lifecycleOwner, cloudMediaProviderAppTitle, cloudMediaAccountName,
shouldShowChooseAppBanner, shouldShowCloudMediaAvailableBanner,
- onChooseAppBannerClickListener, onCloudMediaAvailableBannerClickListener);
+ shouldShowAccountUpdatedBanner, shouldShowChooseAccountBanner,
+ onChooseAppBannerEventListener, onCloudMediaAvailableBannerEventListener,
+ onAccountUpdatedBannerEventListener, onChooseAccountBannerEventListener);
mOnAlbumClickListener = onAlbumClickListener;
mHasMimeTypeFilter = hasMimeTypeFilter;
}
diff --git a/src/com/android/providers/media/photopicker/ui/AlbumsTabFragment.java b/src/com/android/providers/media/photopicker/ui/AlbumsTabFragment.java
index 652c35f..de8bcb8 100644
--- a/src/com/android/providers/media/photopicker/ui/AlbumsTabFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/AlbumsTabFragment.java
@@ -51,7 +51,10 @@
mPickerViewModel.getCloudMediaAccountNameLiveData(),
mPickerViewModel.shouldShowChooseAppBannerLiveData(),
mPickerViewModel.shouldShowCloudMediaAvailableBannerLiveData(),
- mOnChooseAppBannerClickListener, mOnCloudMediaAvailableBannerClickListener);
+ mPickerViewModel.shouldShowAccountUpdatedBannerLiveData(),
+ mPickerViewModel.shouldShowChooseAccountBannerLiveData(),
+ mOnChooseAppBannerEventListener, mOnCloudMediaAvailableBannerEventListener,
+ mOnAccountUpdatedBannerEventListener, mOnChooseAccountBannerEventListener);
mPickerViewModel.getCategories().observe(this, categoryList -> {
adapter.updateCategoryList(categoryList);
// Handle emptyView's visibility
diff --git a/src/com/android/providers/media/photopicker/ui/AlbumsTabItemDecoration.java b/src/com/android/providers/media/photopicker/ui/AlbumsTabItemDecoration.java
index 2db2210..ed15296 100644
--- a/src/com/android/providers/media/photopicker/ui/AlbumsTabItemDecoration.java
+++ b/src/com/android/providers/media/photopicker/ui/AlbumsTabItemDecoration.java
@@ -67,7 +67,17 @@
final int spanCount = layoutManager.getSpanCount();
// the top gap of the album items on the first row is mSpacing
- if (adapterPosition <= spanCount) {
+ /**
+ * {@link adapterPosition} for album items =
+ * {@link TabAdapter#getBannerCount()} + {@link column}
+ * + ((album item row index) * {@link spanCount}).
+ *
+ * Since {@link TabAdapter#getBannerCount()} only returns a 1 or 0,
+ * and {@link spanCount} > 1,
+ * for the first row of album items, {@link adapterPosition} <= {@link column} + 1,
+ * whereas for the further rows, {@link adapterPosition} > {@link column} + 1.
+ */
+ if (adapterPosition <= column + 1) {
outRect.top = mSpacing;
} else {
outRect.top = mTopSpacing;
diff --git a/src/com/android/providers/media/photopicker/ui/ImageLoader.java b/src/com/android/providers/media/photopicker/ui/ImageLoader.java
index 69f5e3d..0d77b82 100644
--- a/src/com/android/providers/media/photopicker/ui/ImageLoader.java
+++ b/src/com/android/providers/media/photopicker/ui/ImageLoader.java
@@ -16,6 +16,8 @@
package com.android.providers.media.photopicker.ui;
+import static com.bumptech.glide.load.resource.bitmap.Downsampler.PREFERRED_COLOR_SPACE;
+
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.ImageDecoder;
@@ -35,6 +37,7 @@
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.Option;
+import com.bumptech.glide.load.PreferredColorSpace;
import com.bumptech.glide.load.resource.gif.GifDrawable;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.signature.ObjectKey;
@@ -51,9 +54,15 @@
private static final RequestOptions THUMBNAIL_OPTION =
RequestOptions.option(THUMBNAIL_REQUEST, /* enableThumbnail */ true);
private final Context mContext;
+ private final PreferredColorSpace mPreferredColorSpace;
public ImageLoader(Context context) {
mContext = context;
+
+ final boolean isScreenWideColorGamut =
+ mContext.getResources().getConfiguration().isScreenWideColorGamut();
+ mPreferredColorSpace =
+ isScreenWideColorGamut ? PreferredColorSpace.DISPLAY_P3 : PreferredColorSpace.SRGB;
}
/**
@@ -162,9 +171,15 @@
ImageView imageView) {
RequestBuilder<T> newRequestBuilder = requestBuilder.clone();
+ final RequestOptions requestOptionsWithPreferredColorSpace;
if (requestOptions != null) {
- newRequestBuilder = newRequestBuilder.apply(requestOptions);
+ requestOptionsWithPreferredColorSpace = requestOptions.clone();
+ } else {
+ requestOptionsWithPreferredColorSpace = new RequestOptions();
}
+ requestOptionsWithPreferredColorSpace.set(PREFERRED_COLOR_SPACE, mPreferredColorSpace);
+
+ newRequestBuilder = newRequestBuilder.apply(requestOptionsWithPreferredColorSpace);
if (signature != null) {
newRequestBuilder = newRequestBuilder.signature(signature);
diff --git a/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java b/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
index 72e891c..d7568f3 100644
--- a/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
+++ b/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
@@ -57,11 +57,17 @@
@NonNull LiveData<String> cloudMediaAccountName,
@NonNull LiveData<Boolean> shouldShowChooseAppBanner,
@NonNull LiveData<Boolean> shouldShowCloudMediaAvailableBanner,
- @NonNull OnBannerClickListener onChooseAppBannerClickListener,
- @NonNull OnBannerClickListener onCloudMediaAvailableBannerClickListener) {
+ @NonNull LiveData<Boolean> shouldShowAccountUpdatedBanner,
+ @NonNull LiveData<Boolean> shouldShowChooseAccountBanner,
+ @NonNull OnBannerEventListener onChooseAppBannerEventListener,
+ @NonNull OnBannerEventListener onCloudMediaAvailableBannerEventListener,
+ @NonNull OnBannerEventListener onAccountUpdatedBannerEventListener,
+ @NonNull OnBannerEventListener onChooseAccountBannerEventListener) {
super(imageLoader, lifecycleOwner, cloudMediaProviderAppTitle, cloudMediaAccountName,
shouldShowChooseAppBanner, shouldShowCloudMediaAvailableBanner,
- onChooseAppBannerClickListener, onCloudMediaAvailableBannerClickListener);
+ shouldShowAccountUpdatedBanner, shouldShowChooseAccountBanner,
+ onChooseAppBannerEventListener, onCloudMediaAvailableBannerEventListener,
+ onAccountUpdatedBannerEventListener, onChooseAccountBannerEventListener);
mShowRecentSection = showRecentSection;
mSelection = selection;
mOnMediaItemClickListener = onMediaItemClickListener;
diff --git a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
index 99cc2d7..8607734 100644
--- a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
@@ -76,19 +76,23 @@
// We only show the Banners on the PhotosTabFragment with CATEGORY_DEFAULT (Main grid).
final boolean shouldShowBanners = mCategory.isDefault();
+ final LiveData<Boolean> doNotShowBanner = new MutableLiveData<>(false);
final LiveData<Boolean> showChooseAppBanner = shouldShowBanners
- ? mPickerViewModel.shouldShowChooseAppBannerLiveData()
- : new MutableLiveData<>(false);
+ ? mPickerViewModel.shouldShowChooseAppBannerLiveData() : doNotShowBanner;
final LiveData<Boolean> showCloudMediaAvailableBanner = shouldShowBanners
- ? mPickerViewModel.shouldShowCloudMediaAvailableBannerLiveData()
- : new MutableLiveData<>(false);
+ ? mPickerViewModel.shouldShowCloudMediaAvailableBannerLiveData() : doNotShowBanner;
+ final LiveData<Boolean> showAccountUpdatedBanner = shouldShowBanners
+ ? mPickerViewModel.shouldShowAccountUpdatedBannerLiveData() : doNotShowBanner;
+ final LiveData<Boolean> showChooseAccountBanner = shouldShowBanners
+ ? mPickerViewModel.shouldShowChooseAccountBannerLiveData() : doNotShowBanner;
final PhotosTabAdapter adapter = new PhotosTabAdapter(showRecentSection, mSelection,
mImageLoader, this::onItemClick, this::onItemLongClick, /* lifecycleOwner */ this,
mPickerViewModel.getCloudMediaProviderAppTitleLiveData(),
mPickerViewModel.getCloudMediaAccountNameLiveData(), showChooseAppBanner,
- showCloudMediaAvailableBanner, mOnChooseAppBannerClickListener,
- mOnCloudMediaAvailableBannerClickListener);
+ showCloudMediaAvailableBanner, showAccountUpdatedBanner, showChooseAccountBanner,
+ mOnChooseAppBannerEventListener, mOnCloudMediaAvailableBannerEventListener,
+ mOnAccountUpdatedBannerEventListener, mOnChooseAccountBannerEventListener);
if (mCategory.isDefault()) {
setEmptyMessage(R.string.picker_photos_empty_message);
diff --git a/src/com/android/providers/media/photopicker/ui/TabAdapter.java b/src/com/android/providers/media/photopicker/ui/TabAdapter.java
index 19e14e0..db88751 100644
--- a/src/com/android/providers/media/photopicker/ui/TabAdapter.java
+++ b/src/com/android/providers/media/photopicker/ui/TabAdapter.java
@@ -51,7 +51,7 @@
@NonNull private final LiveData<String> mCloudMediaAccountName;
@Nullable private Banner mBanner;
- @Nullable private OnBannerClickListener mOnBannerClickListener;
+ @Nullable private OnBannerEventListener mOnBannerEventListener;
/**
* Combined list of Sections and Media Items, ordered based on their position in the view.
*
@@ -69,17 +69,27 @@
@NonNull LiveData<String> cloudMediaAccountName,
@NonNull LiveData<Boolean> shouldShowChooseAppBanner,
@NonNull LiveData<Boolean> shouldShowCloudMediaAvailableBanner,
- @NonNull OnBannerClickListener onChooseAppBannerClickListener,
- @NonNull OnBannerClickListener onCloudMediaAvailableBannerClickListener) {
+ @NonNull LiveData<Boolean> shouldShowAccountUpdatedBanner,
+ @NonNull LiveData<Boolean> shouldShowChooseAccountBanner,
+ @NonNull OnBannerEventListener onChooseAppBannerEventListener,
+ @NonNull OnBannerEventListener onCloudMediaAvailableBannerEventListener,
+ @NonNull OnBannerEventListener onAccountUpdatedBannerEventListener,
+ @NonNull OnBannerEventListener onChooseAccountBannerEventListener) {
mImageLoader = imageLoader;
mCloudMediaProviderAppTitle = cloudMediaProviderAppTitle;
mCloudMediaAccountName = cloudMediaAccountName;
shouldShowChooseAppBanner.observe(lifecycleOwner, isVisible ->
- setBannerVisibility(isVisible, Banner.CHOOSE_APP, onChooseAppBannerClickListener));
+ setBannerVisibility(isVisible, Banner.CHOOSE_APP, onChooseAppBannerEventListener));
shouldShowCloudMediaAvailableBanner.observe(lifecycleOwner, isVisible ->
setBannerVisibility(isVisible, Banner.CLOUD_MEDIA_AVAILABLE,
- onCloudMediaAvailableBannerClickListener));
+ onCloudMediaAvailableBannerEventListener));
+ shouldShowAccountUpdatedBanner.observe(lifecycleOwner, isVisible ->
+ setBannerVisibility(isVisible, Banner.ACCOUNT_UPDATED,
+ onAccountUpdatedBannerEventListener));
+ shouldShowChooseAccountBanner.observe(lifecycleOwner, isVisible ->
+ setBannerVisibility(isVisible, Banner.CHOOSE_ACCOUNT,
+ onChooseAccountBannerEventListener));
}
@NonNull
@@ -157,7 +167,7 @@
private void onBindBannerViewHolder(@NonNull RecyclerView.ViewHolder itemHolder) {
final BannerHolder bannerVH = (BannerHolder) itemHolder;
bannerVH.bind(mBanner, mCloudMediaProviderAppTitle.getValue(),
- mCloudMediaAccountName.getValue(), mOnBannerClickListener);
+ mCloudMediaAccountName.getValue(), mOnBannerEventListener);
}
void onBindSectionViewHolder(@NonNull RecyclerView.ViewHolder itemHolder, int position) {
@@ -187,23 +197,24 @@
abstract boolean isItemTypeMediaItem(int position);
/**
- * Update the 'choose app' banner visibility in tab adapter
+ * Update the banner visibility in tab adapter
*/
private void setBannerVisibility(boolean isVisible, @NonNull Banner banner,
- @NonNull OnBannerClickListener onBannerClickListener) {
+ @NonNull OnBannerEventListener onBannerEventListener) {
if (isVisible) {
if (mBanner == null) {
mBanner = banner;
- mOnBannerClickListener = onBannerClickListener;
+ mOnBannerEventListener = onBannerEventListener;
notifyItemInserted(/* position */ 0);
- } else if (mBanner != banner) {
+ mOnBannerEventListener.onBannerAdded();
+ } else {
mBanner = banner;
- mOnBannerClickListener = onBannerClickListener;
+ mOnBannerEventListener = onBannerEventListener;
notifyItemChanged(/* position */ 0);
}
} else if (mBanner == banner) {
mBanner = null;
- mOnBannerClickListener = null;
+ mOnBannerEventListener = null;
notifyItemRemoved(/* position */ 0);
}
}
@@ -251,21 +262,21 @@
}
void bind(@NonNull Banner banner, String cloudAppName, String cloudUserAccount,
- @NonNull OnBannerClickListener onBannerClickListener) {
+ @NonNull OnBannerEventListener onBannerEventListener) {
final Context context = itemView.getContext();
- itemView.setOnClickListener(v -> onBannerClickListener.onBannerClick());
+ itemView.setOnClickListener(v -> onBannerEventListener.onBannerClick());
mPrimaryText.setText(banner.getPrimaryText(context, cloudAppName));
mSecondaryText.setText(banner.getSecondaryText(context, cloudAppName,
cloudUserAccount));
- mDismissButton.setOnClickListener(v -> onBannerClickListener.onDismissButtonClick());
+ mDismissButton.setOnClickListener(v -> onBannerEventListener.onDismissButtonClick());
if (banner.mActionButtonText != -1) {
mActionButton.setText(banner.mActionButtonText);
mActionButton.setVisibility(View.VISIBLE);
- mActionButton.setOnClickListener(v -> onBannerClickListener.onActionButtonClick());
+ mActionButton.setOnClickListener(v -> onBannerEventListener.onActionButtonClick());
} else {
mActionButton.setVisibility(View.GONE);
}
@@ -273,14 +284,18 @@
}
private enum Banner {
+ // TODO(b/274426228): Replace `CLOUD_MEDIA_AVAILABLE` `mActionButtonText` from `-1` to
+ // `R.string.picker_banner_cloud_change_account_button`, post change cloud account
+ // functionality implementation from the Picker settings (b/261999521).
CLOUD_MEDIA_AVAILABLE(R.string.picker_banner_cloud_first_time_available_title,
- R.string.picker_banner_cloud_first_time_available_desc,
- R.string.picker_banner_cloud_change_account_button),
+ R.string.picker_banner_cloud_first_time_available_desc, /* no action button */ -1),
ACCOUNT_UPDATED(R.string.picker_banner_cloud_account_changed_title,
R.string.picker_banner_cloud_account_changed_desc, /* no action button */ -1),
+ // TODO(b/274426228): Replace `CHOOSE_ACCOUNT` `mActionButtonText` from `-1` to
+ // `R.string.picker_banner_cloud_choose_account_button`, post change cloud account
+ // functionality implementation from the Picker settings (b/261999521).
CHOOSE_ACCOUNT(R.string.picker_banner_cloud_choose_account_title,
- R.string.picker_banner_cloud_choose_account_desc,
- R.string.picker_banner_cloud_choose_account_button),
+ R.string.picker_banner_cloud_choose_account_desc, /* no action button */ -1),
CHOOSE_APP(R.string.picker_banner_cloud_choose_app_title,
R.string.picker_banner_cloud_choose_app_desc,
R.string.picker_banner_cloud_choose_app_button);
@@ -326,7 +341,7 @@
}
}
- interface OnBannerClickListener {
+ interface OnBannerEventListener {
void onActionButtonClick();
void onDismissButtonClick();
@@ -334,5 +349,7 @@
default void onBannerClick() {
onActionButtonClick();
}
+
+ void onBannerAdded();
}
}
diff --git a/src/com/android/providers/media/photopicker/ui/TabFragment.java b/src/com/android/providers/media/photopicker/ui/TabFragment.java
index 9a59cdc..1f6e862 100644
--- a/src/com/android/providers/media/photopicker/ui/TabFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/TabFragment.java
@@ -448,7 +448,7 @@
mRecyclerView.setLayoutManager(layoutManager);
}
- private abstract class OnBannerClickListener implements TabAdapter.OnBannerClickListener {
+ private abstract class OnBannerEventListener implements TabAdapter.OnBannerEventListener {
@Override
public void onActionButtonClick() {
dismissBanner();
@@ -460,22 +460,43 @@
dismissBanner();
}
+ @Override
+ public void onBannerAdded() {
+ mRecyclerView.scrollToPosition(/* position */ 0);
+ }
+
abstract void dismissBanner();
}
- protected final OnBannerClickListener mOnChooseAppBannerClickListener =
- new OnBannerClickListener() {
+ protected final OnBannerEventListener mOnChooseAppBannerEventListener =
+ new OnBannerEventListener() {
@Override
void dismissBanner() {
mPickerViewModel.onUserDismissedChooseAppBanner();
}
};
- protected final OnBannerClickListener mOnCloudMediaAvailableBannerClickListener =
- new OnBannerClickListener() {
+ protected final OnBannerEventListener mOnCloudMediaAvailableBannerEventListener =
+ new OnBannerEventListener() {
@Override
void dismissBanner() {
mPickerViewModel.onUserDismissedCloudMediaAvailableBanner();
}
};
+
+ protected final OnBannerEventListener mOnAccountUpdatedBannerEventListener =
+ new OnBannerEventListener() {
+ @Override
+ void dismissBanner() {
+ mPickerViewModel.onUserDismissedAccountUpdatedBanner();
+ }
+ };
+
+ protected final OnBannerEventListener mOnChooseAccountBannerEventListener =
+ new OnBannerEventListener() {
+ @Override
+ void dismissBanner() {
+ mPickerViewModel.onUserDismissedChooseAccountBanner();
+ }
+ };
}
diff --git a/src/com/android/providers/media/photopicker/viewmodel/BannerController.java b/src/com/android/providers/media/photopicker/viewmodel/BannerController.java
index ff91ff6..c6c0fdc 100644
--- a/src/com/android/providers/media/photopicker/viewmodel/BannerController.java
+++ b/src/com/android/providers/media/photopicker/viewmodel/BannerController.java
@@ -18,85 +18,98 @@
import static android.provider.MediaStore.getCurrentCloudProvider;
+import static com.android.providers.media.MediaApplication.getConfigStore;
import static com.android.providers.media.photopicker.util.CloudProviderUtils.getAvailableCloudProviders;
import static com.android.providers.media.photopicker.util.CloudProviderUtils.getCloudMediaAccountName;
import static com.android.providers.media.photopicker.util.CloudProviderUtils.getProviderLabelForUser;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Looper;
-import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.AtomicFile;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.providers.media.ConfigStore;
import com.android.providers.media.photopicker.data.model.UserId;
+import com.android.providers.media.util.XmlUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.HashMap;
+import java.util.Map;
/**
- * Banner Controller to store and handle the banner data per user for {@link PhotoPickerActivity}.
+ * Banner Controller to store and handle the banner data per user for
+ * {@link com.android.providers.media.photopicker.PhotoPickerActivity}.
*/
class BannerController {
private static final String TAG = "BannerController";
- private static final String UI_PREFS_FILE_NAME = "picker_ui_prefs";
+ private static final String DATA_MEDIA_DIRECTORY_PATH = "/data/media/";
+ private static final String LAST_CLOUD_PROVIDER_DATA_FILE_PATH_IN_USER_MEDIA_DIR =
+ "/.transforms/picker/last_cloud_provider_info";
/**
- * Key to the {@link SharedPreferences} value of the last received
- * {@link android.provider.CloudMediaProvider} authority by the UI (PhotoPicker) process.
+ * {@link #mCloudProviderDataMap} key to the last fetched
+ * {@link android.provider.CloudMediaProvider} authority.
*/
- private static final String PREFS_KEY_CLOUD_PROVIDER_AUTHORITY =
- "last_cloud_provider_authority";
+ private static final String AUTHORITY = "authority";
/**
- * Key to the {@link SharedPreferences} value of the last received
- * {@link android.provider.CloudMediaProvider} account name by the UI (PhotoPicker) process.
+ * {@link #mCloudProviderDataMap} key to the last fetched account name in the then fetched
+ * {@link android.provider.CloudMediaProvider}.
*/
- private static final String PREFS_KEY_CLOUD_PROVIDER_ACCOUNT_NAME =
- "last_cloud_provider_account_name";
+ private static final String ACCOUNT_NAME = "account_name";
- // Authority of the current cloud media provider
- @Nullable
- private String mCmpAuthority;
+ private final Context mContext;
+ private final UserHandle mUserHandle;
+
+ /**
+ * {@link File} for persisting the last fetched {@link android.provider.CloudMediaProvider}
+ * data.
+ */
+ private final File mLastCloudProviderDataFile;
+
+ /**
+ * Last fetched {@link android.provider.CloudMediaProvider} data.
+ */
+ private final Map<String, String> mCloudProviderDataMap = new HashMap<>();
// Label of the current cloud media provider
- @Nullable
private String mCmpLabel;
- // Account name in the current cloud media provider
- @Nullable
- private String mCmpAccountName;
-
- // Boolean Choose App Banner visibility
+ // Boolean 'Choose App' banner visibility
private boolean mShowChooseAppBanner;
- // Boolean Choose App Banner visibility
+ // Boolean 'Cloud Media Available' banner visibility
private boolean mShowCloudMediaAvailableBanner;
- BannerController(@NonNull Context context, @NonNull ConfigStore configStore,
- @NonNull UserHandle userHandle) {
+ // Boolean 'Account Updated' banner visibility
+ private boolean mShowAccountUpdatedBanner;
- // TODO(b/268255830): Show picker banners in the work profile as per the cloud provider
- // state in the work profile when launched from the personal profile and vice-versa.
- if (!isCrossProfile(userHandle)) {
- // Fetch the last cached cloud media info from ui prefs and save.
- mCmpAuthority = getUiPrefs(context)
- .getString(PREFS_KEY_CLOUD_PROVIDER_AUTHORITY, /* defValue */ null);
- mCmpAccountName = getUiPrefs(context)
- .getString(PREFS_KEY_CLOUD_PROVIDER_ACCOUNT_NAME, /* defValue */ null);
- }
+ // Boolean 'Choose Account' banner visibility
+ private boolean mShowChooseAccountBanner;
- initialise(context, configStore, userHandle);
+ BannerController(@NonNull Context context, @NonNull UserHandle userHandle) {
+ mContext = context;
+ mUserHandle = userHandle;
+
+ final String lastCloudProviderDataFilePath = DATA_MEDIA_DIRECTORY_PATH
+ + userHandle.getIdentifier() + LAST_CLOUD_PROVIDER_DATA_FILE_PATH_IN_USER_MEDIA_DIR;
+ mLastCloudProviderDataFile = new File(lastCloudProviderDataFilePath);
+ loadCloudProviderInfo();
+
+ initialise();
}
/**
- * Same as {@link #initialise(Context, ConfigStore, UserHandle)}, renamed for readability.
+ * Same as {@link #initialise()}, renamed for readability.
*/
- void reset(@NonNull Context context, @NonNull ConfigStore configStore,
- @NonNull UserHandle userHandle) {
- initialise(context, configStore, userHandle);
+ void reset() {
+ initialise();
}
/**
@@ -104,17 +117,14 @@
*
* 0. Assert non-main thread.
* 1. Fetch the latest cloud provider info.
- * 2. If the previous & new cloud provider infos are the same, No-op.
- * 3. Reset should show banners.
- * 4. Update the saved and cached cloud provider info with the latest info.
+ * 2. {@link #onChangeCloudMediaInfo(String, String)} with the newly fetched authority and
+ * account name.
*
* Note : This method is expected to be called only in a non-main thread since we shouldn't
* block the UI thread on the heavy Binder calls to fetch the cloud media provider info.
*/
- private void initialise(@NonNull Context context, @NonNull ConfigStore configStore,
- @NonNull UserHandle userHandle) {
- final String lastCmpAuthority = mCmpAuthority, lastCmpAccountName = mCmpAccountName;
-
+ private void initialise() {
+ final String cmpAuthority, cmpAccountName;
// TODO(b/245746037): Remove try-catch for the RuntimeException.
// Under the hood MediaStore.getCurrentCloudProvider() makes an IPC call to the primary
// MediaProvider process, where we currently perform a UID check (making sure that
@@ -130,80 +140,90 @@
// 1. Fetch the latest cloud provider info.
final ContentResolver contentResolver =
- UserId.of(userHandle).getContentResolver(context);
- mCmpAuthority = getCurrentCloudProvider(contentResolver);
- mCmpLabel = getProviderLabelForUser(context, userHandle, mCmpAuthority);
- mCmpAccountName = getCloudMediaAccountName(contentResolver, mCmpAuthority);
+ UserId.of(mUserHandle).getContentResolver(mContext);
+ cmpAuthority = getCurrentCloudProvider(contentResolver);
+ mCmpLabel = getProviderLabelForUser(mContext, mUserHandle, cmpAuthority);
+ cmpAccountName = getCloudMediaAccountName(contentResolver, cmpAuthority);
// Not logging the account name due to privacy concerns
- Log.d(TAG, "Current CloudMediaProvider authority: " + mCmpAuthority + ", label: "
+ Log.d(TAG, "Current CloudMediaProvider authority: " + cmpAuthority + ", label: "
+ mCmpLabel);
} catch (PackageManager.NameNotFoundException | RuntimeException e) {
Log.w(TAG, "Could not fetch the current CloudMediaProvider", e);
- hideBanners();
+ resetToDefault();
return;
}
- // TODO(b/268255830): Show picker banners in the work profile as per the cloud provider
- // state in the work profile when launched from the personal profile and vice-versa.
- // Hide cross profile banners until cross profile shared preferences access is resolved.
- if (isCrossProfile(userHandle)) {
+ onChangeCloudMediaInfo(cmpAuthority, cmpAccountName);
+ }
+
+ /**
+ * On Change Cloud Media Info
+ *
+ * @param cmpAuthority Current {@link android.provider.CloudMediaProvider} authority.
+ * @param cmpAccountName Current {@link android.provider.CloudMediaProvider} account name.
+ *
+ * 1. If the previous & new cloud provider infos are the same, No-op.
+ * 2. Reset should show banners.
+ * 3. Update the saved and cached cloud provider info with the latest info.
+ */
+ private void onChangeCloudMediaInfo(@Nullable String cmpAuthority,
+ @Nullable String cmpAccountName) {
+ // 1. If the previous & new cloud provider infos are the same, No-op.
+ final String lastCmpAuthority = mCloudProviderDataMap.get(AUTHORITY);
+ final String lastCmpAccountName = mCloudProviderDataMap.get(ACCOUNT_NAME);
+
+ if (TextUtils.equals(lastCmpAuthority, cmpAuthority)
+ && TextUtils.equals(lastCmpAccountName, cmpAccountName)) {
// no-op
return;
}
- // 2. If the previous & new cloud provider infos are the same, No-op.
- if (TextUtils.equals(lastCmpAuthority, mCmpAuthority)
- && TextUtils.equals(lastCmpAccountName, mCmpAccountName)) {
- // no-op
- return;
+ // 2. Update banner visibilities.
+ clearBanners();
+
+ if (cmpAuthority == null) {
+ // mShowChooseAppBanner is true iff the new authority is null and the available cloud
+ // providers list is not empty.
+ mShowChooseAppBanner =
+ !getAvailableCloudProviders(mContext, getConfigStore(), mUserHandle).isEmpty();
+ } else if (cmpAccountName == null) {
+ // mShowChooseAccountBanner is true iff the new account name is null while the new
+ // authority is NOT null.
+ mShowChooseAccountBanner = true;
+ } else if (TextUtils.equals(lastCmpAuthority, cmpAuthority)) {
+ // mShowAccountUpdatedBanner is true iff the new authority AND account name are NOT null
+ // AND the authority is unchanged.
+ mShowAccountUpdatedBanner = true;
+ } else {
+ // mShowCloudMediaAvailableBanner is true iff the new authority AND account name are
+ // NOT null AND the authority has changed.
+ mShowCloudMediaAvailableBanner = true;
}
- // 3. Reset should show banners.
- // mShowChooseAppBanner is true iff new authority is null and the available cloud
- // providers list is not empty.
- mShowChooseAppBanner = (mCmpAuthority == null)
- && !getAvailableCloudProviders(context, configStore, userHandle).isEmpty();
- // mShowCloudMediaAvailableBanner is true iff the new authority AND account name are
- // NOT null while the old authority OR account is / are null.
- mShowCloudMediaAvailableBanner = mCmpAuthority != null && mCmpAccountName != null
- && (lastCmpAuthority == null || lastCmpAccountName == null);
-
- // 4. Update the saved and cached cloud provider info with the latest info.
- final SharedPreferences.Editor uiPrefsEditor = getUiPrefs(context).edit();
- if (!TextUtils.equals(mCmpAuthority, lastCmpAuthority)) {
- uiPrefsEditor.putString(PREFS_KEY_CLOUD_PROVIDER_AUTHORITY, mCmpAuthority);
- }
- if (!TextUtils.equals(mCmpAccountName, lastCmpAccountName)) {
- uiPrefsEditor.putString(PREFS_KEY_CLOUD_PROVIDER_ACCOUNT_NAME, mCmpAccountName);
- }
- uiPrefsEditor.apply();
+ // 3. Update the saved and cached cloud provider info with the latest info.
+ persistCloudProviderInfo(cmpAuthority, cmpAccountName);
}
/**
- * @return {@code true} if the given {@link UserHandle} is not the calling user,
- * {@code false} otherwise.
+ * Reset all the controller data to their default values.
*/
- private static boolean isCrossProfile(@NonNull UserHandle userHandle) {
- return !Process.myUserHandle().equals(userHandle);
- }
-
- @NonNull
- // TODO(b/267525755): Migrate the Picker UI Shared preferences actions to a helper class that
- // ensures synchronization.
- private static SharedPreferences getUiPrefs(@NonNull Context context) {
- return context.getSharedPreferences(UI_PREFS_FILE_NAME, Context.MODE_PRIVATE);
- }
-
- /**
- * Hide all banners (Fallback for error scenarios)
- */
- private void hideBanners() {
- mCmpAuthority = null;
+ private void resetToDefault() {
+ mCloudProviderDataMap.clear();
mCmpLabel = null;
- mCmpAccountName = null;
+ clearBanners();
+ }
+
+ /**
+ * Clear all banners
+ *
+ * Reset all should show banner {@code boolean} values to {@code false}.
+ */
+ private void clearBanners() {
mShowChooseAppBanner = false;
mShowCloudMediaAvailableBanner = false;
+ mShowAccountUpdatedBanner = false;
+ mShowChooseAccountBanner = false;
}
/**
@@ -211,7 +231,7 @@
*/
@Nullable
String getCloudMediaProviderAuthority() {
- return mCmpAuthority;
+ return mCloudProviderDataMap.get(AUTHORITY);
}
/**
@@ -227,7 +247,7 @@
*/
@Nullable
String getCloudMediaProviderAccountName() {
- return mCmpAccountName;
+ return mCloudProviderDataMap.get(ACCOUNT_NAME);
}
/**
@@ -246,6 +266,20 @@
}
/**
+ * @return the 'Account Updated' banner visibility {@link #mShowAccountUpdatedBanner}.
+ */
+ boolean shouldShowAccountUpdatedBanner() {
+ return mShowAccountUpdatedBanner;
+ }
+
+ /**
+ * @return the 'Choose Account' banner visibility {@link #mShowChooseAccountBanner}.
+ */
+ boolean shouldShowChooseAccountBanner() {
+ return mShowChooseAccountBanner;
+ }
+
+ /**
* Dismiss (hide) the 'Choose App' banner
*
* Set the 'Choose App' banner visibility {@link #mShowChooseAppBanner} as {@code false}.
@@ -266,12 +300,41 @@
*/
void onUserDismissedCloudMediaAvailableBanner() {
if (!mShowCloudMediaAvailableBanner) {
- Log.wtf(TAG, "Choose app banner visibility for current user is false on dismiss");
+ Log.wtf(TAG, "Cloud media available banner visibility for current user is false on "
+ + "dismiss");
} else {
mShowCloudMediaAvailableBanner = false;
}
}
+ /**
+ * Dismiss (hide) the 'Account Updated' banner
+ *
+ * Set the 'Account Updated' banner visibility {@link #mShowAccountUpdatedBanner} as
+ * {@code false}.
+ */
+ void onUserDismissedAccountUpdatedBanner() {
+ if (!mShowAccountUpdatedBanner) {
+ Log.wtf(TAG, "Account Updated banner visibility for current user is false on dismiss");
+ } else {
+ mShowAccountUpdatedBanner = false;
+ }
+ }
+
+ /**
+ * Dismiss (hide) the 'Choose Account' banner
+ *
+ * Set the 'Choose Account' banner visibility {@link #mShowChooseAccountBanner} as
+ * {@code false}.
+ */
+ void onUserDismissedChooseAccountBanner() {
+ if (!mShowChooseAccountBanner) {
+ Log.wtf(TAG, "Choose Account banner visibility for current user is false on dismiss");
+ } else {
+ mShowChooseAccountBanner = false;
+ }
+ }
+
private static void assertNonMainThread() {
if (!Looper.getMainLooper().isCurrentThread()) {
return;
@@ -280,4 +343,55 @@
throw new IllegalStateException("Expected to NOT be called from the main thread."
+ " Current thread: " + Thread.currentThread());
}
+
+ private void loadCloudProviderInfo() {
+ FileInputStream fis = null;
+ final Map<String, String> lastCloudProviderDataMap = new HashMap<>();
+ try {
+ if (!mLastCloudProviderDataFile.exists()) {
+ return;
+ }
+
+ final AtomicFile atomicLastCloudProviderDataFile = new AtomicFile(
+ mLastCloudProviderDataFile);
+ fis = atomicLastCloudProviderDataFile.openRead();
+ lastCloudProviderDataMap.putAll(XmlUtils.readMapXml(fis));
+ } catch (Exception e) {
+ Log.w(TAG, "Could not load the cloud provider info.", e);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to close the FileInputStream.", e);
+ }
+ }
+ mCloudProviderDataMap.clear();
+ mCloudProviderDataMap.putAll(lastCloudProviderDataMap);
+ }
+ }
+
+ private void persistCloudProviderInfo(@Nullable String cmpAuthority,
+ @Nullable String cmpAccountName) {
+ mCloudProviderDataMap.clear();
+ if (cmpAuthority != null) {
+ mCloudProviderDataMap.put(AUTHORITY, cmpAuthority);
+ }
+ if (cmpAccountName != null) {
+ mCloudProviderDataMap.put(ACCOUNT_NAME, cmpAccountName);
+ }
+
+ FileOutputStream fos = null;
+ final AtomicFile atomicLastCloudProviderDataFile = new AtomicFile(
+ mLastCloudProviderDataFile);
+
+ try {
+ fos = atomicLastCloudProviderDataFile.startWrite();
+ XmlUtils.writeMapXml(mCloudProviderDataMap, fos);
+ atomicLastCloudProviderDataFile.finishWrite(fos);
+ } catch (Exception e) {
+ atomicLastCloudProviderDataFile.failWrite(fos);
+ Log.w(TAG, "Could not persist the cloud provider info.", e);
+ }
+ }
}
diff --git a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
index f57857b..d2b5cb8 100644
--- a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
+++ b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
@@ -17,6 +17,7 @@
package com.android.providers.media.photopicker.viewmodel;
import static android.content.Intent.ACTION_GET_CONTENT;
+import static android.content.Intent.EXTRA_LOCAL_ONLY;
import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
@@ -86,24 +87,30 @@
private MutableLiveData<List<Item>> mCategoryItemList;
// The list of categories.
private MutableLiveData<List<Category>> mCategoryList;
+
// Authority of the current CloudMediaProvider of the current user
private final MutableLiveData<String> mCloudMediaProviderAuthority = new MutableLiveData<>();
// Label of the current CloudMediaProvider of the current user
private final MutableLiveData<String> mCloudMediaProviderLabel = new MutableLiveData<>();
// Account name of the current CloudMediaProvider of the current user
private final MutableLiveData<String> mCloudMediaAccountName = new MutableLiveData<>();
+
// Boolean Choose App Banner visibility
private final MutableLiveData<Boolean> mShowChooseAppBanner = new MutableLiveData<>(false);
// Boolean Cloud Media Available Banner visibility
private final MutableLiveData<Boolean> mShowCloudMediaAvailableBanner =
new MutableLiveData<>(false);
+ // Boolean 'Account Updated' banner visibility
+ private final MutableLiveData<Boolean> mShowAccountUpdatedBanner = new MutableLiveData<>(false);
+ // Boolean 'Choose Account' banner visibility
+ private final MutableLiveData<Boolean> mShowChooseAccountBanner = new MutableLiveData<>(false);
// The banner controllers per user
private final PerUser<BannerController> mBannerControllers = new PerUser<BannerController>() {
@NonNull
@Override
protected BannerController create(@UserIdInt int userId) {
- return new BannerController(mAppContext, mConfigStore, UserHandle.of(userId));
+ return new BannerController(mAppContext, UserHandle.of(userId));
}
};
@@ -120,6 +127,8 @@
private Category mCurrentCategory;
private ConfigStore mConfigStore;
+ private boolean mIsLocalOnly;
+
public PickerViewModel(@NonNull Application application) {
super(application);
mAppContext = application.getApplicationContext();
@@ -131,6 +140,7 @@
mLogger = new PhotoPickerUiEventLogger();
mConfigStore = new ConfigStore.ConfigStoreImpl();
mIsUserSelectForApp = false;
+ mIsLocalOnly = false;
maybeInitialiseAndSetBannersForCurrentUser();
}
@@ -255,9 +265,12 @@
}
private Cursor fetchItems(Category category, UserId userId) {
- if (isUserSelectForApp()) {
- // Photo Picker is launched by {@link MediaStore#ACTION_USER_SELECT_IMAGES_FOR_APP}
- // action for permission flow. We only show local items in this case.
+ if (isUserSelectForApp() || isLocalOnly()) {
+ // We only show local items in below cases.
+ // 1. Photo Picker is launched by {@link MediaStore#ACTION_USER_SELECT_IMAGES_FOR_APP}
+ // action for permission flow.
+ // 2. Photo Picker is launched with {@link EXTRA_LOCAL_ONLY} as true in
+ // {@link ACTION_GET_CONTENT} or {@link ACTION_PICK_IMAGES}.
return mItemsProvider.getLocalItems(category, /* limit */ -1, mMimeTypeFilters, userId);
} else {
return mItemsProvider.getAllItems(category, /* limit */ -1, mMimeTypeFilters, userId);
@@ -355,9 +368,12 @@
}
private Cursor fetchCategories(UserId userId) {
- if (isUserSelectForApp()) {
- // Photo Picker is launched by {@link MediaStore#ACTION_USER_SELECT_IMAGES_FOR_APP}
- // action for permission flow. We only show local items in this case.
+ if (isUserSelectForApp() || isLocalOnly()) {
+ // We only show local items in below cases.
+ // 1. Photo Picker is launched by {@link MediaStore#ACTION_USER_SELECT_IMAGES_FOR_APP}
+ // action for permission flow.
+ // 2. Photo Picker is launched with {@link EXTRA_LOCAL_ONLY} as true in
+ // {@link ACTION_GET_CONTENT} or {@link ACTION_PICK_IMAGES}.
return mItemsProvider.getLocalCategories(mMimeTypeFilters, userId);
} else {
return mItemsProvider.getAllCategories(mMimeTypeFilters, userId);
@@ -408,6 +424,8 @@
mSelection.parseSelectionValuesFromIntent(intent);
+ mIsLocalOnly = intent.getBooleanExtra(EXTRA_LOCAL_ONLY, false);
+
mIsUserSelectForApp =
intent.getAction().equals(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP);
if (!SdkLevel.isAtLeastU() && mIsUserSelectForApp) {
@@ -557,6 +575,13 @@
maybeInitialiseAndSetBannersForCurrentUser();
}
+
+ // Return whether hotopicker's launch intent has extra {@link EXTRA_LOCAL_ONLY} set to true
+ // or not.
+ public boolean isLocalOnly() {
+ return mIsLocalOnly;
+ }
+
/**
* Note - This method is expected to be called only on
* {@link android.provider.CloudMediaProvider} app / account change, i.e. currently on
@@ -576,12 +601,10 @@
*/
if (bannerController != null) {
/**
- * {@link BannerController#reset} cannot be called in the UI thread hence,
+ * {@link BannerController#reset()} cannot be called in the UI thread hence,
* using {@link ForegroundThread} here.
*/
- ForegroundThread.getExecutor().execute(() -> {
- bannerController.reset(mAppContext, mConfigStore, UserHandle.of(userIdInt));
- });
+ ForegroundThread.getExecutor().execute(bannerController::reset);
}
}
@@ -590,8 +613,8 @@
*
* 1. {@link #hideAllBanners()} in the Main thread to ensure consistency with the media items
* displayed for the period when the items and categories have been updated but the
- * {@link BannerController} construction or
- * {@link BannerController#reset(Context, ConfigStore, UserHandle)} is still in progress.
+ * {@link BannerController} construction or {@link BannerController#reset()} is still in
+ * progress.
*
* 2. Get or create the {@link BannerController} for
* {@link UserIdManager#getCurrentUserProfileId()} using {@link PerUser#forUser(int)}.
@@ -620,6 +643,8 @@
mShowChooseAppBanner.postValue(bannerController.shouldShowChooseAppBanner());
mShowCloudMediaAvailableBanner.postValue(
bannerController.shouldShowCloudMediaAvailableBanner());
+ mShowAccountUpdatedBanner.postValue(bannerController.shouldShowAccountUpdatedBanner());
+ mShowChooseAccountBanner.postValue(bannerController.shouldShowChooseAccountBanner());
});
}
@@ -632,11 +657,13 @@
private void hideAllBanners() {
mShowChooseAppBanner.setValue(false);
mShowCloudMediaAvailableBanner.setValue(false);
+ mShowAccountUpdatedBanner.setValue(false);
+ mShowChooseAccountBanner.setValue(false);
}
/**
- * @return the {@link LiveData} of the 'Choose App banner' visibility
- * {@link #mShowChooseAppBanner}.
+ * @return the {@link LiveData} of the 'Choose App' banner visibility
+ * {@link #mShowChooseAppBanner}.
*/
@NonNull
public LiveData<Boolean> shouldShowChooseAppBannerLiveData() {
@@ -653,6 +680,24 @@
}
/**
+ * @return the {@link LiveData} of the 'Account Updated' banner visibility
+ * {@link #mShowAccountUpdatedBanner}.
+ */
+ @NonNull
+ public LiveData<Boolean> shouldShowAccountUpdatedBannerLiveData() {
+ return mShowAccountUpdatedBanner;
+ }
+
+ /**
+ * @return the {@link LiveData} of the 'Choose Account' banner visibility
+ * {@link #mShowChooseAccountBanner}.
+ */
+ @NonNull
+ public LiveData<Boolean> shouldShowChooseAccountBannerLiveData() {
+ return mShowChooseAccountBanner;
+ }
+
+ /**
* Dismiss (hide) the 'Choose App' banner for the current user.
*
* 1. Set the {@link LiveData} value of the 'Choose App' banner visibility
@@ -666,13 +711,13 @@
final BannerController bannerController = getCurrentBannerController();
if (bannerController == null) {
- Log.wtf(TAG, "Banner controller not yet created for the current user on choose app"
- + "banner dismiss");
+ Log.wtf(TAG, "Banner controller not yet created for the current user on Choose App"
+ + " banner dismiss");
return;
}
if (Boolean.FALSE.equals(mShowChooseAppBanner.getValue())) {
- Log.wtf(TAG, "Choose app banner visibility live data value is false on dismiss");
+ Log.wtf(TAG, "Choose App banner visibility live data value is false on dismiss");
} else {
mShowChooseAppBanner.setValue(false);
}
@@ -693,13 +738,13 @@
final BannerController bannerController = getCurrentBannerController();
if (bannerController == null) {
- Log.wtf(TAG, "Banner controller not yet created for the current user on cloud media"
- + "available banner dismiss");
+ Log.wtf(TAG, "Banner controller not yet created for the current user on Cloud Media "
+ + "Available banner dismiss");
return;
}
if (Boolean.FALSE.equals(mShowCloudMediaAvailableBanner.getValue())) {
- Log.wtf(TAG, "Cloud media available banner visibility live data value is false on"
+ Log.wtf(TAG, "Cloud Media Available banner visibility live data value is false on "
+ "dismiss");
} else {
mShowCloudMediaAvailableBanner.setValue(false);
@@ -707,6 +752,60 @@
bannerController.onUserDismissedCloudMediaAvailableBanner();
}
+ /**
+ * Dismiss (hide) the 'Account Updated' banner for the current user.
+ *
+ * 1. Set the {@link LiveData} value of the 'Account Updated' banner visibility
+ * {@link #mShowAccountUpdatedBanner} as {@code false}.
+ *
+ * 2. Update the 'Account Updated' banner visibility of the current user
+ * {@link BannerController} to {@code false}.
+ */
+ @UiThread
+ public void onUserDismissedAccountUpdatedBanner() {
+ final BannerController bannerController = getCurrentBannerController();
+
+ if (bannerController == null) {
+ Log.wtf(TAG, "Banner controller not yet created for the current user on Account Updated"
+ + " banner dismiss");
+ return;
+ }
+
+ if (Boolean.FALSE.equals(mShowAccountUpdatedBanner.getValue())) {
+ Log.wtf(TAG, "Account Updated banner visibility live data value is false on dismiss");
+ } else {
+ mShowAccountUpdatedBanner.setValue(false);
+ }
+ bannerController.onUserDismissedAccountUpdatedBanner();
+ }
+
+ /**
+ * Dismiss (hide) the 'Choose Account' banner for the current user.
+ *
+ * 1. Set the {@link LiveData} value of the 'Choose Account' banner visibility
+ * {@link #mShowChooseAccountBanner} as {@code false}.
+ *
+ * 2. Update the 'Choose Account' banner visibility of the current user
+ * {@link BannerController} to {@code false}.
+ */
+ @UiThread
+ public void onUserDismissedChooseAccountBanner() {
+ final BannerController bannerController = getCurrentBannerController();
+
+ if (bannerController == null) {
+ Log.wtf(TAG, "Banner controller not yet created for the current user on Choose Account"
+ + " banner dismiss");
+ return;
+ }
+
+ if (Boolean.FALSE.equals(mShowChooseAccountBanner.getValue())) {
+ Log.wtf(TAG, "Choose Account banner visibility live data value is false on dismiss");
+ } else {
+ mShowChooseAccountBanner.setValue(false);
+ }
+ bannerController.onUserDismissedChooseAccountBanner();
+ }
+
@Nullable
private BannerController getCurrentBannerController() {
final int currentUserId = mUserIdManager.getCurrentUserProfileId().getIdentifier();
diff --git a/src/com/android/providers/media/scan/LegacyMediaScanner.java b/src/com/android/providers/media/scan/LegacyMediaScanner.java
index d8d3bed..d73dda5 100644
--- a/src/com/android/providers/media/scan/LegacyMediaScanner.java
+++ b/src/com/android/providers/media/scan/LegacyMediaScanner.java
@@ -19,8 +19,6 @@
import android.content.Context;
import android.net.Uri;
-import androidx.annotation.Nullable;
-
import com.android.providers.media.MediaVolume;
import java.io.File;
@@ -49,11 +47,6 @@
}
@Override
- public Uri scanFile(File file, int reason, @Nullable String ownerPackage) {
- throw new UnsupportedOperationException();
- }
-
- @Override
public void onDetachVolume(MediaVolume volume) {
throw new UnsupportedOperationException();
}
diff --git a/src/com/android/providers/media/scan/MediaScanner.java b/src/com/android/providers/media/scan/MediaScanner.java
index 45d2a24..8999542 100644
--- a/src/com/android/providers/media/scan/MediaScanner.java
+++ b/src/com/android/providers/media/scan/MediaScanner.java
@@ -24,23 +24,42 @@
import android.content.Context;
import android.net.Uri;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.providers.media.MediaVolume;
import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
public interface MediaScanner {
- public static final int REASON_UNKNOWN = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__UNKNOWN;
- public static final int REASON_MOUNTED = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__MOUNTED;
- public static final int REASON_DEMAND = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__DEMAND;
- public static final int REASON_IDLE = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__IDLE;
+ int REASON_UNKNOWN = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__UNKNOWN;
+ int REASON_MOUNTED = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__MOUNTED;
+ int REASON_DEMAND = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__DEMAND;
+ int REASON_IDLE = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__IDLE;
- public Context getContext();
- public void scanDirectory(File file, int reason);
- public Uri scanFile(File file, int reason);
- public Uri scanFile(File file, int reason, @Nullable String ownerPackage);
- public void onDetachVolume(MediaVolume volume);
- public void onIdleScanStopped();
- public void onDirectoryDirty(File file);
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ REASON_UNKNOWN,
+ REASON_MOUNTED,
+ REASON_DEMAND,
+ REASON_IDLE
+ })
+ @interface ScanReason {}
+
+ @NonNull
+ Context getContext();
+
+ void scanDirectory(@NonNull File dir, @ScanReason int reason);
+
+ @Nullable
+ Uri scanFile(@NonNull File file, @ScanReason int reason);
+
+ void onDetachVolume(@NonNull MediaVolume volume);
+
+ void onIdleScanStopped();
+
+ void onDirectoryDirty(@NonNull File file);
}
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index 1770136..0a84838 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -50,6 +50,8 @@
import static com.android.providers.media.util.Metrics.translateReason;
+import static java.util.Objects.requireNonNull;
+
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
@@ -58,6 +60,7 @@
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
+import android.database.sqlite.SQLiteBlobTooBigException;
import android.database.sqlite.SQLiteDatabase;
import android.drm.DrmManagerClient;
import android.drm.DrmSupportInfo;
@@ -179,6 +182,7 @@
private static final Pattern PATTERN_ALBUM_ART = Pattern.compile(
"(?i)(?:(?:^folder|(?:^AlbumArt(?:(?:_\\{.*\\}_)?(?:small|large))?))(?:\\.jpg$)|(?:\\._.*))");
+ @NonNull
private final Context mContext;
private final DrmManagerClient mDrmClient;
@GuardedBy("mPendingCleanDirectories")
@@ -214,8 +218,8 @@
*/
private final Set<String> mDrmMimeTypes = new ArraySet<>();
- public ModernMediaScanner(Context context) {
- mContext = context;
+ public ModernMediaScanner(@NonNull Context context) {
+ mContext = requireNonNull(context);
mDrmClient = new DrmManagerClient(context);
// Dynamically collect the set of MIME types that should be considered
@@ -229,40 +233,44 @@
}
@Override
+ @NonNull
public Context getContext() {
return mContext;
}
@Override
- public void scanDirectory(File file, int reason) {
- try (Scan scan = new Scan(file, reason, /*ownerPackage*/ null)) {
+ public void scanDirectory(@NonNull File file, @ScanReason int reason) {
+ requireNonNull(file);
+
+ try (Scan scan = new Scan(file, reason)) {
scan.run();
- } catch (OperationCanceledException ignored) {
} catch (FileNotFoundException e) {
- Log.e(TAG, "Couldn't find directory to scan", e) ;
+ Log.e(TAG, "Couldn't find directory to scan", e);
+ return;
+ } catch (OperationCanceledException ignored) {
+ // No-op.
}
}
@Override
- public Uri scanFile(File file, int reason) {
- return scanFile(file, reason, /*ownerPackage*/ null);
- }
+ @Nullable
+ public Uri scanFile(@NonNull File file, @ScanReason int reason) {
+ requireNonNull(file);
- @Override
- public Uri scanFile(File file, int reason, @Nullable String ownerPackage) {
- try (Scan scan = new Scan(file, reason, ownerPackage)) {
+ try (Scan scan = new Scan(file, reason)) {
scan.run();
return scan.getFirstResult();
- } catch (OperationCanceledException ignored) {
- return null;
} catch (FileNotFoundException e) {
Log.e(TAG, "Couldn't find file to scan", e) ;
return null;
+ } catch (OperationCanceledException ignored) {
+ // No-op.
+ return null;
}
}
@Override
- public void onDetachVolume(MediaVolume volume) {
+ public void onDetachVolume(@NonNull MediaVolume volume) {
synchronized (mActiveScans) {
for (Scan scan : mActiveScans) {
if (volume.equals(scan.mVolume)) {
@@ -318,15 +326,14 @@
private final String mVolumeName;
private final Uri mFilesUri;
private final CancellationSignal mSignal;
- private final String mOwnerPackage;
private final List<String> mExcludeDirs;
private final long mStartGeneration;
private final boolean mSingleFile;
private final Set<Path> mAcquiredDirectoryLocks = new ArraySet<>();
private final ArrayList<ContentProviderOperation> mPending = new ArrayList<>();
- private LongArray mScannedIds = new LongArray();
- private LongArray mUnknownIds = new LongArray();
+ private final LongArray mScannedIds = new LongArray();
+ private final LongArray mUnknownIds = new LongArray();
private long mFirstId = -1;
@@ -347,8 +354,7 @@
*/
private boolean mIsDirectoryTreeDirty;
- public Scan(File root, int reason, @Nullable String ownerPackage)
- throws FileNotFoundException {
+ Scan(File root, int reason) throws FileNotFoundException {
Trace.beginSection("Scanner.ctor");
mClient = mContext.getContentResolver()
@@ -369,7 +375,6 @@
mStartGeneration = MediaStore.getGeneration(mResolver, mVolumeName);
mSingleFile = mRoot.isFile();
- mOwnerPackage = ownerPackage;
mExcludeDirs = new ArrayList<>();
Trace.endSection();
@@ -515,31 +520,21 @@
queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_INCLUDE);
queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_FAVORITE, MediaStore.MATCH_INCLUDE);
- final int[] countPerMediaType = new int[FileColumns.MEDIA_TYPE_COUNT];
- try (Cursor c = mResolver.query(mFilesUri,
- new String[]{FileColumns._ID, FileColumns.MEDIA_TYPE, FileColumns.DATE_EXPIRES,
- FileColumns.IS_PENDING}, queryArgs, mSignal)) {
- while (c.moveToNext()) {
- final long id = c.getLong(0);
- if (Arrays.binarySearch(scannedIds, id) < 0) {
- final long dateExpire = c.getLong(2);
- final boolean isPending = c.getInt(3) == 1;
- // Don't delete the pending item which is not expired.
- // If the scan is triggered between invoking
- // ContentResolver#insert() and ContentResolver#openFileDescriptor(),
- // it raises the FileNotFoundException b/166063754.
- if (isPending && dateExpire > System.currentTimeMillis() / 1000) {
- continue;
- }
- mUnknownIds.add(id);
- final int mediaType = c.getInt(1);
- // Avoid ArrayIndexOutOfBounds if more mediaTypes are added,
- // but mediaTypeSize is not updated
- if (mediaType < countPerMediaType.length) {
- countPerMediaType[mediaType]++;
- }
- }
- }
+ int[] countPerMediaType;
+ try {
+ countPerMediaType = addUnknownIdsAndGetMediaTypeCount(queryArgs, scannedIds);
+ } catch (SQLiteBlobTooBigException e) {
+ // Catching SQLiteBlobTooBigException to avoid MP process crash. There can be two
+ // scenarios where SQLiteBlobTooBigException is thrown.
+ // First, where data read by cursor is more than 2MB size. In this case,
+ // next fill window request might try to read data which may not exist anymore due
+ // to a recent update after the last query.
+ // Second, when columns being read have total size of more than 2MB.
+ // We intend to solve for first scenario by querying MP again. If the initial
+ // failure was because of second scenario, a runtime exception will be thrown.
+ Log.e(TAG, "Encountered exception: ", e);
+ mUnknownIds.clear();
+ countPerMediaType = addUnknownIdsAndGetMediaTypeCount(queryArgs, scannedIds);
} finally {
Trace.endSection();
}
@@ -567,6 +562,37 @@
}
}
+ private int[] addUnknownIdsAndGetMediaTypeCount(Bundle queryArgs, long[] scannedIds) {
+ int[] countPerMediaType = new int[FileColumns.MEDIA_TYPE_COUNT];
+ try (Cursor c = mResolver.query(mFilesUri,
+ new String[]{FileColumns._ID, FileColumns.MEDIA_TYPE, FileColumns.DATE_EXPIRES,
+ FileColumns.IS_PENDING}, queryArgs, mSignal)) {
+ while (c.moveToNext()) {
+ final long id = c.getLong(0);
+ if (Arrays.binarySearch(scannedIds, id) < 0) {
+ final long dateExpire = c.getLong(2);
+ final boolean isPending = c.getInt(3) == 1;
+ // Don't delete the pending item which is not expired.
+ // If the scan is triggered between invoking
+ // ContentResolver#insert() and ContentResolver#openFileDescriptor(),
+ // it raises the FileNotFoundException b/166063754.
+ if (isPending && dateExpire > System.currentTimeMillis() / 1000) {
+ continue;
+ }
+ mUnknownIds.add(id);
+ final int mediaType = c.getInt(1);
+ // Avoid ArrayIndexOutOfBounds if more mediaTypes are added,
+ // but mediaTypeSize is not updated
+ if (mediaType < countPerMediaType.length) {
+ countPerMediaType[mediaType]++;
+ }
+ }
+ }
+ }
+
+ return countPerMediaType;
+ }
+
private void resolvePlaylists() {
mSignal.throwIfCanceled();
@@ -798,10 +824,7 @@
}
if (op != null) {
op.withValue(FileColumns._MODIFIER, FileColumns._MODIFIER_MEDIA_SCAN);
- // Add owner package name to new insertions when package name is provided.
- if (op.build().isInsert() && !attrs.isDirectory() && mOwnerPackage != null) {
- op.withValue(MediaColumns.OWNER_PACKAGE_NAME, mOwnerPackage);
- }
+
// Force DRM files to be marked as DRM, since the lower level
// stack may not set this correctly
if (isDrm) {
@@ -892,7 +915,7 @@
return FileVisitResult.CONTINUE;
}
- private void addPending(ContentProviderOperation op) {
+ private void addPending(@NonNull ContentProviderOperation op) {
mPending.add(op);
if (op.isInsert()) mInsertCount++;
diff --git a/src/com/android/providers/media/scan/NullMediaScanner.java b/src/com/android/providers/media/scan/NullMediaScanner.java
index 7a1a396..fc475c7 100644
--- a/src/com/android/providers/media/scan/NullMediaScanner.java
+++ b/src/com/android/providers/media/scan/NullMediaScanner.java
@@ -21,8 +21,6 @@
import android.provider.MediaStore;
import android.util.Log;
-import androidx.annotation.Nullable;
-
import com.android.providers.media.MediaVolume;
import java.io.File;
@@ -57,12 +55,6 @@
}
@Override
- public Uri scanFile(File file, int reason, @Nullable String ownerPackage) {
- Log.w(TAG, "Ignoring scan request for " + file);
- return null;
- }
-
- @Override
public void onDetachVolume(MediaVolume volume) {
// Ignored
}
diff --git a/src/com/android/providers/media/scan/UnreliableVolumeScanner.java b/src/com/android/providers/media/scan/UnreliableVolumeScanner.java
deleted file mode 100644
index 58715ad..0000000
--- a/src/com/android/providers/media/scan/UnreliableVolumeScanner.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2021 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.scan;
-
-import android.content.ContentValues;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import static com.android.providers.media.photopicker.data.UnreliableVolumeDatabaseHelper.MediaColumns.DATE_MODIFIED;
-import static com.android.providers.media.photopicker.data.UnreliableVolumeDatabaseHelper.MediaColumns.DISPLAY_NAME;
-import static com.android.providers.media.photopicker.data.UnreliableVolumeDatabaseHelper.MediaColumns.MIME_TYPE;
-import static com.android.providers.media.photopicker.data.UnreliableVolumeDatabaseHelper.MediaColumns.SIZE_BYTES;
-import static com.android.providers.media.photopicker.data.UnreliableVolumeDatabaseHelper.MediaColumns._DATA;
-
-import static com.android.providers.media.util.MimeUtils.isImageMimeType;
-import static com.android.providers.media.util.MimeUtils.isVideoMimeType;
-import static com.android.providers.media.util.MimeUtils.resolveMimeType;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.ArrayList;
-import java.util.List;
-
-public class UnreliableVolumeScanner {
- private final String TAG = "UnreliableVolumeScanner";
-
- private boolean isAllowedMimeType(String mimeType) {
- return (isImageMimeType(mimeType) || isVideoMimeType(mimeType));
- }
-
- private @NonNull ContentValues addDataFromFile(File file, String mimeType) {
- ContentValues fileData = new ContentValues();
- fileData.put(SIZE_BYTES, file.length());
- fileData.put(_DATA, file.getPath());
- fileData.put(MIME_TYPE, mimeType);
- fileData.put(DATE_MODIFIED, file.lastModified());
- fileData.put(DISPLAY_NAME, file.getName());
-
- return fileData;
- }
-
- /**
- * @return list of image and video data from the unreliable volume {@code path}
- */
- public @NonNull List<ContentValues> scanAndGetUnreliableVolFileInfo(Path path)
- throws IOException {
- List<ContentValues> unreliableVolumeData = new ArrayList<>();
- Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
- String mimeType = resolveMimeType(file.toFile());
- if (isAllowedMimeType(mimeType)) {
- unreliableVolumeData.add(addDataFromFile(file.toFile(), mimeType));
- }
- return FileVisitResult.CONTINUE;
- }
-
- @Override
- public FileVisitResult visitFileFailed(Path file, IOException exc) {
- Log.w(TAG, "File read failed for: " + file.toString());
- return FileVisitResult.CONTINUE;
- }
- });
- return unreliableVolumeData;
- }
-}
\ No newline at end of file
diff --git a/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceService.java b/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceService.java
index e5874c3..493333f 100644
--- a/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceService.java
+++ b/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceService.java
@@ -48,8 +48,7 @@
new Thread(() -> {
try (ContentProviderClient cpc = getContentResolver()
.acquireContentProviderClient(MediaStore.AUTHORITY)) {
- ((MediaProvider) cpc.getLocalContentProvider()).getDatabaseBackupAndRecovery()
- .backupDatabases(mSignal);
+ ((MediaProvider) cpc.getLocalContentProvider()).backupDatabases(mSignal);
} catch (OperationCanceledException e) {
Log.e(TAG, "StableUriIdleMaintenanceService operation cancelled: ", e);
}
diff --git a/src/com/android/providers/media/util/XmlUtils.java b/src/com/android/providers/media/util/XmlUtils.java
new file mode 100644
index 0000000..d01ea1e
--- /dev/null
+++ b/src/com/android/providers/media/util/XmlUtils.java
@@ -0,0 +1,124 @@
+/*
+ * 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.util;
+
+import android.util.Xml;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.collect.Maps;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * XML utility methods.
+ */
+public class XmlUtils {
+ private static final String FEATURE_NAME =
+ "http://xmlpull.org/v1/doc/features.html#indent-output";
+ private static final String TAG_NAME_MAP_ENTRY = "map_entry";
+ private static final String ATTRIBUTE_NAME_MAP_ENTRY_KEY = "key";
+
+ /**
+ * Read a {@link Map}<{@link String}, {@link String}> from an {@link InputStream} containing
+ * XML. The stream should previously have been written by
+ * {@link #writeMapXml(Map, OutputStream)}, else it may return a {@link Maps#newHashMap()},
+ * or throw a {@link RuntimeException}.
+ *
+ * @param in The {@link InputStream} from which to read.
+ *
+ * @return The resulting {@link Map}<{@link String}, {@link String}>.
+ *
+ * @see #writeMapXml(Map, OutputStream)
+ */
+ @NonNull
+ public static Map<String, String> readMapXml(@NonNull InputStream in)
+ throws IOException, XmlPullParserException {
+ final Map<String, String> map = new HashMap<>();
+
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, StandardCharsets.UTF_8.name());
+
+ final StringBuilder mapEntryKey = new StringBuilder(), mapEntryValue = new StringBuilder();
+
+ for (int eventType = parser.getEventType(); eventType != parser.END_DOCUMENT;
+ eventType = parser.next()) {
+ if (eventType == parser.START_TAG) {
+ final String tagName = parser.getName();
+ if (!Objects.equals(tagName, TAG_NAME_MAP_ENTRY)) {
+ throw new RuntimeException("Unexpected tag name: " + tagName);
+ }
+
+ mapEntryKey.append(parser.getAttributeValue(
+ /* namespace */ null, ATTRIBUTE_NAME_MAP_ENTRY_KEY));
+ } else if (eventType == parser.TEXT) {
+ mapEntryValue.append(parser.getText());
+ } else if (eventType == parser.END_TAG) {
+ if (mapEntryKey.length() > 0 && mapEntryValue.length() > 0) {
+ map.put(mapEntryKey.toString(), mapEntryValue.toString());
+ }
+
+ mapEntryKey.setLength(0);
+ mapEntryValue.setLength(0);
+ }
+ }
+
+ return map;
+ }
+
+ /**
+ * Flatten a {@link Map}<{@link String}, {@link String}> into an {@link OutputStream} as XML.
+ * The map can later be read back with {@link #readMapXml(InputStream)}.
+ *
+ * @param map The {@link Map}<{@link String}, {@link String}> to be flattened.
+ * @param out The {@link OutputStream} where to write the XML data.
+ *
+ * @see #readMapXml(InputStream)
+ */
+ public static void writeMapXml(@NonNull Map<String, String> map, @NonNull OutputStream out)
+ throws IOException {
+ final XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+
+ serializer.startDocument(/* encoding */ null, /* standalone */ true);
+ serializer.setFeature(FEATURE_NAME, /* state */ true);
+
+ for (Map.Entry<String, String> e : map.entrySet()) {
+ serializer.startTag(/* namespace */ null, TAG_NAME_MAP_ENTRY);
+ serializer.attribute(/* namespace */ null, ATTRIBUTE_NAME_MAP_ENTRY_KEY, e.getKey());
+
+ final String val = e.getValue();
+ if (val != null) {
+ serializer.text(val);
+ }
+
+ serializer.endTag(/* namespace */ null, TAG_NAME_MAP_ENTRY);
+ }
+
+ serializer.endDocument();
+ }
+}
diff --git a/tests/client/src/com/android/providers/media/client/PublicVolumeTest.java b/tests/client/src/com/android/providers/media/client/PublicVolumeTest.java
index 988dcf9..e5181d5 100644
--- a/tests/client/src/com/android/providers/media/client/PublicVolumeTest.java
+++ b/tests/client/src/com/android/providers/media/client/PublicVolumeTest.java
@@ -38,6 +38,7 @@
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,6 +59,7 @@
/**
* Test that we can query database rows of recently unmounted volume
*/
+ @Ignore("Re-enable once b/273569662 is fixed")
@Test
public void testIncludeRecentlyUnmountedVolumes() throws Exception {
Context context = InstrumentationRegistry.getTargetContext();
diff --git a/tests/src/com/android/providers/media/DatabaseHelperTest.java b/tests/src/com/android/providers/media/DatabaseHelperTest.java
index 043b61c..8f871f4 100644
--- a/tests/src/com/android/providers/media/DatabaseHelperTest.java
+++ b/tests/src/com/android/providers/media/DatabaseHelperTest.java
@@ -310,12 +310,16 @@
true).build());
sIsolatedContext = new IsolatedContext(
InstrumentationRegistry.getTargetContext(), TAG, /*asFuseThread*/ false);
- ((IsolatedContext) sIsolatedContext).setBackedUpData(backedUpData);
+ IsolatedContext isolatedContext = (IsolatedContext) sIsolatedContext;
+ isolatedContext.setBackedUpData(backedUpData);
+ DatabaseBackupAndRecovery databaseBackupAndRecovery =
+ isolatedContext.getMediaProvider().getDatabaseBackupAndRecovery();
sIsolatedResolver = sIsolatedContext.getContentResolver();
Map<String, Long> pathToIdMap = new HashMap<>();
- try (DatabaseHelper helper = before.getConstructor(Context.class, String.class)
- .newInstance(sIsolatedContext, "internal.db")) {
+ try (DatabaseHelper helper = before.getConstructor(Context.class, String.class,
+ DatabaseBackupAndRecovery.class)
+ .newInstance(sIsolatedContext, "internal.db", databaseBackupAndRecovery)) {
SQLiteDatabase db = helper.getWritableDatabaseForTest();
assertThat(sIsolatedContext.getDatabasePath("internal.db").exists()).isTrue();
@@ -337,8 +341,9 @@
}
// Downgrade will wipe data, and recover non-dirty rows from backup
- try (DatabaseHelper helper = after.getConstructor(Context.class, String.class)
- .newInstance(sIsolatedContext, "internal.db")) {
+ try (DatabaseHelper helper = after.getConstructor(Context.class, String.class,
+ DatabaseBackupAndRecovery.class)
+ .newInstance(sIsolatedContext, "internal.db", databaseBackupAndRecovery)) {
SQLiteDatabase db = helper.getWritableDatabaseForTest();
assertThat(sIsolatedContext.getDatabasePath("internal.db").exists()).isTrue();
try (Cursor c = db.query("files", new String[]{FileColumns._ID, FileColumns.DATA}, null,
@@ -689,7 +694,7 @@
private static class DatabaseHelperO extends DatabaseHelper {
public DatabaseHelperO(Context context, String name) {
super(context, name, DatabaseHelper.VERSION_O, false, false, sProjectionHelper, null,
- null, null, null, false);
+ null, null, null, false, null);
}
@Override
@@ -701,7 +706,7 @@
private static class DatabaseHelperP extends DatabaseHelper {
public DatabaseHelperP(Context context, String name) {
super(context, name, DatabaseHelper.VERSION_P, false, false, sProjectionHelper, null,
- null, null, null, false);
+ null, null, null, false, null);
}
@Override
@@ -713,7 +718,7 @@
private static class DatabaseHelperQ extends DatabaseHelper {
public DatabaseHelperQ(Context context, String name) {
super(context, name, DatabaseHelper.VERSION_Q, false, false, sProjectionHelper, null,
- null, null, null, false);
+ null, null, null, false, null);
}
@Override
@@ -725,7 +730,7 @@
private static class DatabaseHelperR extends DatabaseHelper {
public DatabaseHelperR(Context context, String name) {
super(context, name, DatabaseHelper.VERSION_R, false, false, sProjectionHelper, null,
- null, MediaProvider.MIGRATION_LISTENER, null, false);
+ null, MediaProvider.MIGRATION_LISTENER, null, false, null);
}
@Override
@@ -737,10 +742,9 @@
private static class DatabaseHelperS extends DatabaseHelper {
public DatabaseHelperS(Context context, String name) {
super(context, name, VERSION_S, false, false, sProjectionHelper, null,
- null, MediaProvider.MIGRATION_LISTENER, null, false);
+ null, MediaProvider.MIGRATION_LISTENER, null, false, null);
}
-
@Override
public void onCreate(SQLiteDatabase db) {
createSSchema(db, false);
@@ -750,7 +754,13 @@
private static class DatabaseHelperT extends DatabaseHelper {
public DatabaseHelperT(Context context, String name) {
super(context, name, DatabaseHelper.VERSION_T, false, false, sProjectionHelper, null,
- null, MediaProvider.MIGRATION_LISTENER, null, false);
+ null, MediaProvider.MIGRATION_LISTENER, null, false, null);
+ }
+
+ public DatabaseHelperT(Context context, String name,
+ DatabaseBackupAndRecovery databaseBackupAndRecovery) {
+ super(context, name, DatabaseHelper.VERSION_T, false, false, sProjectionHelper, null,
+ null, MediaProvider.MIGRATION_LISTENER, null, false, databaseBackupAndRecovery);
}
@Override
@@ -767,7 +777,13 @@
private static class DatabaseHelperU extends DatabaseHelper {
public DatabaseHelperU(Context context, String name) {
super(context, name, DatabaseHelper.VERSION_U, false, false, sProjectionHelper, null,
- null, MediaProvider.MIGRATION_LISTENER, null, false);
+ null, MediaProvider.MIGRATION_LISTENER, null, false, null);
+ }
+
+ public DatabaseHelperU(Context context, String name,
+ DatabaseBackupAndRecovery databaseBackupAndRecovery) {
+ super(context, name, DatabaseHelper.VERSION_U, false, false, sProjectionHelper, null,
+ null, MediaProvider.MIGRATION_LISTENER, null, false, databaseBackupAndRecovery);
}
@Override
diff --git a/tests/src/com/android/providers/media/IsolatedContext.java b/tests/src/com/android/providers/media/IsolatedContext.java
index 568e12f..0b65326 100644
--- a/tests/src/com/android/providers/media/IsolatedContext.java
+++ b/tests/src/com/android/providers/media/IsolatedContext.java
@@ -16,7 +16,6 @@
package com.android.providers.media;
-import android.annotation.NonNull;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
@@ -31,6 +30,7 @@
import android.test.mock.MockContentResolver;
import com.android.providers.media.cloudproviders.CloudProviderPrimary;
+import com.android.providers.media.fuse.FuseDaemon;
import com.android.providers.media.photopicker.PhotoPickerProvider;
import com.android.providers.media.photopicker.PickerSyncController;
import com.android.providers.media.stableuris.dao.BackupIdRow;
@@ -110,7 +110,7 @@
@Override
protected DatabaseBackupAndRecovery createDatabaseBackupAndRecovery() {
- return new TestDatabaseBackupAndRecovery(this, configStore, getVolumeCache());
+ return new TestDatabaseBackupAndRecovery(configStore, getVolumeCache());
}
@Override
@@ -122,9 +122,8 @@
private class TestDatabaseBackupAndRecovery extends DatabaseBackupAndRecovery {
- TestDatabaseBackupAndRecovery(MediaProvider mediaProvider, ConfigStore configStore,
- VolumeCache volumeCache) {
- super(mediaProvider, configStore, volumeCache);
+ TestDatabaseBackupAndRecovery(ConfigStore configStore, VolumeCache volumeCache) {
+ super(configStore, volumeCache);
}
@Override
@@ -154,8 +153,13 @@
}
@Override
- protected boolean isFuseDaemonReadyForFilePath(@NonNull String filePath) {
- return true;
+ protected FuseDaemon getFuseDaemonForFileWithWait(File fuseFilePath, long waitTime) {
+ return null;
+ }
+
+ @Override
+ protected void setupVolumeDbBackupAndRecovery(MediaVolume volume) {
+
}
}
@@ -202,4 +206,8 @@
throw new IllegalStateException("Failed to get Database helper");
}
}
+
+ public MediaProvider getMediaProvider() {
+ return mMediaProvider;
+ }
}
diff --git a/tests/src/com/android/providers/media/LocalUriMatcherTest.java b/tests/src/com/android/providers/media/LocalUriMatcherTest.java
index aff5f6b..32721ed 100644
--- a/tests/src/com/android/providers/media/LocalUriMatcherTest.java
+++ b/tests/src/com/android/providers/media/LocalUriMatcherTest.java
@@ -42,9 +42,6 @@
assertMatchesPublic(
LocalUriMatcher.PICKER_ID,
assembleTestUri(new String[] {"picker", "0", "anything", "media", "anything"}));
- assertMatchesPublic(
- LocalUriMatcher.PICKER_UNRELIABLE_VOLUME,
- assembleTestUri(new String[] {"picker", "unreliable", "1"}));
assertMatchesPublic(LocalUriMatcher.CLI, assembleTestUri(new String[] {"cli"}));
@@ -206,9 +203,6 @@
assertMatchesHidden(
LocalUriMatcher.PICKER_ID,
assembleTestUri(new String[] {"picker", "0", "anything", "media", "anything"}));
- assertMatchesHidden(
- LocalUriMatcher.PICKER_UNRELIABLE_VOLUME,
- assembleTestUri(new String[] {"picker", "unreliable", "1"}));
assertMatchesHidden(LocalUriMatcher.CLI, assembleTestUri(new String[] {"cli"}));
diff --git a/tests/src/com/android/providers/media/MediaGrantsTest.java b/tests/src/com/android/providers/media/MediaGrantsTest.java
index f393805..aae4f84 100644
--- a/tests/src/com/android/providers/media/MediaGrantsTest.java
+++ b/tests/src/com/android/providers/media/MediaGrantsTest.java
@@ -139,7 +139,8 @@
assertGrantExistsForPackage(fileId1, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
assertGrantExistsForPackage(fileId2, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
- int removed = mGrants.removeAllMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, "test");
+ int removed = mGrants.removeAllMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, "test",
+ TEST_USER_ID);
assertEquals(2, removed);
try (Cursor c =
@@ -168,7 +169,7 @@
assertThrows(
IllegalArgumentException.class,
() -> {
- mGrants.removeAllMediaGrantsForPackage("", "test");
+ mGrants.removeAllMediaGrantsForPackage("", "test", TEST_USER_ID);
});
}
diff --git a/tests/src/com/android/providers/media/UnreliableVolumeTest.java b/tests/src/com/android/providers/media/UnreliableVolumeTest.java
deleted file mode 100644
index 77fbe55..0000000
--- a/tests/src/com/android/providers/media/UnreliableVolumeTest.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2021 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;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-
-import android.app.UiAutomation;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.Environment;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.providers.media.photopicker.data.UnreliableVolumeDatabaseHelper;
-import com.android.providers.media.util.UserCache;
-
-import com.google.common.io.ByteStreams;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.function.Supplier;
-
-public class UnreliableVolumeTest {
-
- private static String sVolumePath;
- private static String sVolumeName;
- private static VolumeCache sVolumeCache;
- private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(2);
- private static final long POLLING_SLEEP_MILLIS = 100;
- private static final String TAG = "UnreliableVolumeTest";
-
- static final String UNRELIABLE_VOLUME_TABLE = "media";
-
- private static final long SIZE_BYTES = 7000;
- private static final String DISPLAY_NAME = "first day of school";
- private static final long DATE_MODIFIED = 1623852851911L;
- private static final String MIME_TYPE = "image/jpg";
- private static String _DATA = "/foo/bar/internship.jpeg";
-
- private static Context sIsolatedContext;
-
- @BeforeClass
- public static void setUp() throws Exception {
- createRemovableVolume();
- final Context context = getContext();
- UserCache mUserCache = new UserCache(context);
- sIsolatedContext = new IsolatedContext(context, TAG, /*asFuseThread*/ false);
- sVolumeCache = new VolumeCache(context, mUserCache);
- sVolumeCache.update();
- sVolumeName = getCurrentPublicVolumeString();
- sVolumePath = "/mnt/media_rw/" + sVolumeName;
- }
-
- @AfterClass
- public static void tearDown() throws Exception {
- executeShellCommand("sm set-virtual-disk false");
- pollForCondition(() -> !isPublicVolumeMounted(), "Timed out while waiting for"
- + " the public volume to disappear");
- }
-
- @Test
- @Ignore("Enable after b/197816987 is fixed")
- public void testUnreliableVolumeSimple() throws Exception {
- assertEquals(sVolumeName, sVolumeCache.getUnreliableVolumePath().get(0).getName());
- assertEquals(sVolumePath, sVolumeCache.getUnreliableVolumePath().get(0).getPath());
- }
-
- @Test
- @Ignore("Enable after b/197816987 is fixed")
- public void testDBisCreated() throws Exception {
- String[] projection = new String[] {
- UnreliableVolumeDatabaseHelper.MediaColumns.SIZE_BYTES,
- UnreliableVolumeDatabaseHelper.MediaColumns.DISPLAY_NAME,
- UnreliableVolumeDatabaseHelper.MediaColumns.DATE_MODIFIED,
- UnreliableVolumeDatabaseHelper.MediaColumns.MIME_TYPE,
- UnreliableVolumeDatabaseHelper.MediaColumns._DATA
- };
-
- try (UnreliableVolumeDatabaseHelper helper =
- new UnreliableVolumeDatabaseHelper(sIsolatedContext)) {
- SQLiteDatabase db = helper.getWritableDatabase();
- ContentValues values = generateAndGetContentValues();
- assertThat(db.insert(UNRELIABLE_VOLUME_TABLE, null, values)).isNotEqualTo(-1);
-
- try (Cursor cr = db.query(UNRELIABLE_VOLUME_TABLE, projection, null, null, null,
- null, null)) {
- assertThat(cr.getCount()).isEqualTo(1);
- while (cr.moveToNext()) {
- assertThat(cr.getLong(0)).isEqualTo(SIZE_BYTES);
- assertThat(cr.getString(1)).isEqualTo(DISPLAY_NAME);
- assertThat(cr.getLong(2)).isEqualTo(DATE_MODIFIED);
- assertThat(cr.getString(3)).isEqualTo(MIME_TYPE);
- assertThat(cr.getString(4)).isEqualTo(_DATA);
- }
- }
- }
- }
-
- private static ContentValues generateAndGetContentValues() {
- ContentValues values = new ContentValues();
- values.put(UnreliableVolumeDatabaseHelper.MediaColumns.SIZE_BYTES, SIZE_BYTES);
- values.put(UnreliableVolumeDatabaseHelper.MediaColumns.DISPLAY_NAME, DISPLAY_NAME);
- values.put(UnreliableVolumeDatabaseHelper.MediaColumns.DATE_MODIFIED, DATE_MODIFIED);
- values.put(UnreliableVolumeDatabaseHelper.MediaColumns.MIME_TYPE, MIME_TYPE);
- values.put(UnreliableVolumeDatabaseHelper.MediaColumns._DATA, _DATA);
- return values;
- }
-
- /**
- * Executes a shell command.
- */
- public static String executeShellCommand(String pattern, Object...args) throws IOException {
- String command = String.format(pattern, args);
- int attempt = 0;
- while (attempt++ < 5) {
- try {
- return executeShellCommandInternal(command);
- } catch (InterruptedIOException e) {
- Log.v(TAG, "Trouble executing " + command + "; trying again", e);
- }
- }
- throw new IOException("Failed to execute " + command);
- }
-
- private static String executeShellCommandInternal(String cmd) throws IOException {
- UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- try (FileInputStream output = new FileInputStream(
- uiAutomation.executeShellCommand(cmd).getFileDescriptor())) {
- return new String(ByteStreams.toByteArray(output));
- }
- }
-
- private static void pollForCondition(Supplier<Boolean> condition, String errorMessage)
- throws Exception {
- for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) {
- if (condition.get()) {
- return;
- }
- Thread.sleep(POLLING_SLEEP_MILLIS);
- }
- throw new TimeoutException(errorMessage);
- }
-
- private static boolean isPublicVolumeMounted() {
- try {
- final String publicVolume = executeShellCommand("sm list-volumes public").trim();
- return publicVolume != null && publicVolume.contains("mounted");
- } catch (Exception e) {
- return false;
- }
- }
-
- private static boolean partitionDisk() {
- try {
- final String listDisks = executeShellCommand("sm list-disks").trim();
- if (listDisks.length() > 0) {
- executeShellCommand("sm partition " + listDisks + " public");
- return true;
- }
- return false;
- } catch (Exception e) {
- return false;
- }
- }
-
- private static String getCurrentPublicVolumeString() {
- final String[] allPublicVolumeDetails;
- try {
- allPublicVolumeDetails = executeShellCommand("sm list-volumes public")
- .trim().split("\n");
- } catch (Exception e) {
- Log.e(TAG, "Failed to execute shell command", e);
- return null;
- }
- for (String volDetails : allPublicVolumeDetails) {
- if (volDetails.startsWith("public")) {
- final String[] publicVolumeDetails = volDetails.trim().split(" ");
- String res = publicVolumeDetails[publicVolumeDetails.length - 1];
- if ("null".equals(res)) {
- continue;
- }
- return res;
- }
- }
- return null;
- }
-
- private static boolean isExternalStorageStateMounted() {
- final File target = Environment.getExternalStorageDirectory();
- try {
- return (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(target))
- && Os.statvfs(target.getAbsolutePath()).f_blocks > 0);
- } catch (ErrnoException ignored) {
- }
- return false;
- }
-
- /**
- * Creates a new OTG-USB volume
- */
- public static void createRemovableVolume() throws Exception {
- executeShellCommand("sm set-force-adoptable off");
- executeShellCommand("sm set-virtual-disk true");
- pollForCondition(() -> partitionDisk(), "Timed out while waiting for"
- + " disk partitioning");
- // Poll twice to avoid using previous mount status
- pollForCondition(() -> isPublicVolumeMounted(), "Timed out while waiting for"
- + " the public volume to mount");
- pollForCondition(() -> isExternalStorageStateMounted(), "Timed out while"
- + " waiting for ExternalStorageState to be MEDIA_MOUNTED");
- }
-}
\ No newline at end of file
diff --git a/tests/src/com/android/providers/media/photopicker/SafetyProtectionUtilsTest.java b/tests/src/com/android/providers/media/photopicker/SafetyProtectionUtilsTest.java
index 1c15d97..88d3a3d 100644
--- a/tests/src/com/android/providers/media/photopicker/SafetyProtectionUtilsTest.java
+++ b/tests/src/com/android/providers/media/photopicker/SafetyProtectionUtilsTest.java
@@ -34,6 +34,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,6 +74,7 @@
});
}
+ @Ignore("Enable once b/269874157 is fixed")
@Test
public void testWhetherShouldUseSafetyProtectionResourcesWhenTOrAboveAndFeatureFlagOn() {
assumeTrue(SdkLevel.isAtLeastT());
diff --git a/tests/src/com/android/providers/media/photopicker/UnreliableVolumeFacadeTest.java b/tests/src/com/android/providers/media/photopicker/UnreliableVolumeFacadeTest.java
deleted file mode 100644
index 551a141..0000000
--- a/tests/src/com/android/providers/media/photopicker/UnreliableVolumeFacadeTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2021 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;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-
-import static org.junit.Assert.*;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.providers.media.photopicker.data.UnreliableVolumeDatabaseHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class UnreliableVolumeFacadeTest {
- private static final int BATCH_SIZE = 100;
- private static final long SIZE_BYTES = 7000;
- private static final String DISPLAY_NAME = "random test image";
- private static final long DATE_MODIFIED = 1623852851911L;
- private static final String MIME_TYPE = "image/jpg";
- private static final String DATA_PREFIX = "mnt/media_rw/A678954/";
-
- private static UnreliableVolumeFacade mFacade;
-
- @Before
- public void setUp() {
- Context context = InstrumentationRegistry.getTargetContext();
- mFacade = new UnreliableVolumeFacade(context);
- }
-
- @After
- public void tearDown() {
- mFacade.deleteMedia();
- }
-
- @Test
- public void queryAllMedia() {
- int counter = mFacade.insertMedia(generateContentDb());
- try (Cursor cr = mFacade.queryMediaAll()) {
- assertThat(cr.getCount()).isEqualTo(counter);
- cr.moveToFirst();
- assertThat(cr.getString(1)).isEqualTo(DATA_PREFIX + "1");
- cr.moveToNext();
- assertThat(cr.getString(1)).isEqualTo(DATA_PREFIX + "2");
- }
- }
-
- @Test
- public void testUniqueConstraint() {
- List<ContentValues> values = generateContentDb();
- int initialCounter = mFacade.insertMedia(values);
- int sameInsertionAttempt = mFacade.insertMedia(values);
- assertEquals(100, initialCounter);
- assertEquals(0, sameInsertionAttempt);
- try (Cursor cr = mFacade.queryMediaAll()) {
- assertThat(cr.getCount()).isEqualTo(initialCounter);
- }
- }
-
- @Test
- public void deleteAllMedia() {
- mFacade.deleteMedia();
- try (Cursor cr = mFacade.queryMediaAll()) {
- assertThat(cr.getCount()).isEqualTo(0);
- }
- }
-
- @Test
- public void queryMediaId() {
- List<ContentValues> values = generateContentDb();
- int counter = mFacade.insertMedia(values);
- String uriString = "content://media/external/file/10";
- Uri uri = Uri.parse(uriString);
- try (Cursor cr = mFacade.queryMediaId(uri)) {
- assertThat(cr.getCount()).isEqualTo(1);
- cr.moveToFirst();
- String id = "10";
- assertThat(cr.getString(0)).isEqualTo(id);
- }
- }
-
- private static ContentValues generateAndGetContentValues(int index) {
- ContentValues values = new ContentValues();
- values.put(UnreliableVolumeDatabaseHelper.MediaColumns.DATE_MODIFIED, DATE_MODIFIED);
- values.put(UnreliableVolumeDatabaseHelper.MediaColumns.SIZE_BYTES, SIZE_BYTES);
- values.put(UnreliableVolumeDatabaseHelper.MediaColumns.DISPLAY_NAME, DISPLAY_NAME);
- values.put(UnreliableVolumeDatabaseHelper.MediaColumns._DATA, DATA_PREFIX + index);
- values.put(UnreliableVolumeDatabaseHelper.MediaColumns.MIME_TYPE, MIME_TYPE);
- values.put(UnreliableVolumeDatabaseHelper.MediaColumns._ID, index);
-
- return values;
- }
-
- public List<ContentValues> generateContentDb() {
- List<ContentValues> list = new ArrayList<>();
- for (int i = 1; i <= BATCH_SIZE; i++) {
- list.add(generateAndGetContentValues(i));
- }
- return list;
- }
-}
diff --git a/tests/src/com/android/providers/media/photopicker/data/ExternalDbFacadeTest.java b/tests/src/com/android/providers/media/photopicker/data/ExternalDbFacadeTest.java
index 184efca..f25b4ea 100644
--- a/tests/src/com/android/providers/media/photopicker/data/ExternalDbFacadeTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/ExternalDbFacadeTest.java
@@ -930,7 +930,7 @@
private static class TestDatabaseHelper extends DatabaseHelper {
public TestDatabaseHelper(Context context) {
super(context, TEST_CLEAN_DB, 1, false, false, new ProjectionHelper(null, null), null,
- null, null, null, false);
+ null, null, null, false, null);
}
}
}
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerUserSelectActivityTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerUserSelectActivityTest.java
index 17e2acb..072e715 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerUserSelectActivityTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerUserSelectActivityTest.java
@@ -134,6 +134,14 @@
@Ignore
@Test
+ public void testNoCloudSettings() {
+ launchValidActivity();
+
+ OverflowMenuUtils.assertOverflowMenuNotShown();
+ }
+
+ @Ignore
+ @Test
public void testUserSelectCorrectHeaderTextIsShown() {
launchValidActivity();
onView(withText(R.string.picker_header_permissions)).check(matches(isDisplayed()));
diff --git a/tests/src/com/android/providers/media/photopicker/ui/PhotosTabAdapterTest.java b/tests/src/com/android/providers/media/photopicker/ui/PhotosTabAdapterTest.java
index 33c3e24..ef1537b 100644
--- a/tests/src/com/android/providers/media/photopicker/ui/PhotosTabAdapterTest.java
+++ b/tests/src/com/android/providers/media/photopicker/ui/PhotosTabAdapterTest.java
@@ -182,9 +182,15 @@
/* cloudMediaAccountName */ mock(LiveData.class),
/* shouldShowChooseAppBanner */ mock(LiveData.class),
/* shouldShowCloudMediaAvailableBanner */ mock(LiveData.class),
- /* onChooseAppBannerClickListener */ mock(TabAdapter.OnBannerClickListener.class),
- /* onCloudMediaAvailableBannerClickListener */
- mock(TabAdapter.OnBannerClickListener.class));
+ /* shouldShowAccountUpdatedBanner */ mock(LiveData.class),
+ /* shouldShowChooseAccountBanner */ mock(LiveData.class),
+ /* onChooseAppBannerEventListener */ mock(TabAdapter.OnBannerEventListener.class),
+ /* onCloudMediaAvailableBannerEventListener */
+ mock(TabAdapter.OnBannerEventListener.class),
+ /* onAccountUpdatedBannerEventListener */
+ mock(TabAdapter.OnBannerEventListener.class),
+ /* onChooseAccountBannerEventListener */
+ mock(TabAdapter.OnBannerEventListener.class));
}
private static Item generateFakeImageItem(String id) {
diff --git a/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java b/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
index a0d4c79..848d641 100644
--- a/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
+++ b/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
@@ -274,6 +274,25 @@
}
@Test
+ public void testParseValuesFromPickImagesIntent_localOnlyTrue() {
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+
+ mPickerViewModel.parseValuesFromIntent(intent);
+
+ assertThat(mPickerViewModel.isLocalOnly()).isTrue();
+ }
+
+ @Test
+ public void testParseValuesFromPickImagesIntent_localOnlyFalse() {
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+
+ mPickerViewModel.parseValuesFromIntent(intent);
+
+ assertThat(mPickerViewModel.isLocalOnly()).isFalse();
+ }
+
+ @Test
public void testParseValuesFromGetContentIntent_validExtraMimeType() {
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/gif", "video/*"});
@@ -293,4 +312,25 @@
// non-media filters for GET_CONTENT show all images and videos
assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse();
}
+
+ @Test
+ public void testParseValuesFromGetContentIntent_localOnlyTrue() {
+ final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"video/*"});
+ intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+
+ mPickerViewModel.parseValuesFromIntent(intent);
+
+ assertThat(mPickerViewModel.isLocalOnly()).isTrue();
+ }
+
+ @Test
+ public void testParseValuesFromGetContentIntent_localOnlyFalse() {
+ final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"video/*"});
+
+ mPickerViewModel.parseValuesFromIntent(intent);
+
+ assertThat(mPickerViewModel.isLocalOnly()).isFalse();
+ }
}
diff --git a/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java b/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java
index cf9cb39..2831e96 100644
--- a/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java
@@ -48,12 +48,6 @@
} catch (UnsupportedOperationException expected) {
}
try {
- scanner.scanFile(new File("/dev/null"), MediaScanner.REASON_UNKNOWN,
- InstrumentationRegistry.getContext().getPackageName());
- fail();
- } catch (UnsupportedOperationException expected) {
- }
- try {
scanner.onDetachVolume(null);
fail();
} catch (UnsupportedOperationException expected) {
diff --git a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
index 7a1dbc3..8418030 100644
--- a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
@@ -734,24 +734,6 @@
assertThat(mModern.scanFile(image, REASON_UNKNOWN)).isNull();
}
- @Test
- public void testScanFileAndUpdateOwnerPackageName() throws Exception {
- final File image = new File(mDir, "image.jpg");
- final String thisPackageName = InstrumentationRegistry.getContext().getPackageName();
- stage(R.raw.test_image, image);
-
- assertQueryCount(0, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
- // scanning the image file inserts new database entry with OWNER_PACKAGE_NAME as
- // thisPackageName.
- assertNotNull(mModern.scanFile(image, REASON_UNKNOWN, thisPackageName));
- try (Cursor cursor = mIsolatedResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- new String[] {MediaColumns.OWNER_PACKAGE_NAME}, null, null, null)) {
- assertEquals(1, cursor.getCount());
- cursor.moveToNext();
- assertEquals(thisPackageName, cursor.getString(0));
- }
- }
-
/**
* Verify fix for obscure bug which would cause us to delete files outside a
* directory that share a common prefix.
diff --git a/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java b/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java
index 063f1b7..265d1a9 100644
--- a/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java
@@ -38,8 +38,6 @@
scanner.scanDirectory(new File("/dev/null"), MediaScanner.REASON_UNKNOWN);
scanner.scanFile(new File("/dev/null"), MediaScanner.REASON_UNKNOWN);
- scanner.scanFile(new File("/dev/null"), MediaScanner.REASON_UNKNOWN,
- InstrumentationRegistry.getContext().getPackageName());
scanner.onDetachVolume(null);
}