diff options
107 files changed, 679 insertions, 990 deletions
diff --git a/res/layout/item_cloud_video_preview.xml b/res/layout/item_cloud_video_preview.xml deleted file mode 100644 index f8c3b6e73..000000000 --- a/res/layout/item_cloud_video_preview.xml +++ /dev/null @@ -1,61 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 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. - --> - -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <FrameLayout - android:id="@+id/preview_player_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center"> - - <com.google.android.exoplayer2.ui.AspectRatioFrameLayout - android:id="@+id/preview_player_frame" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center"> - - <SurfaceView - android:id="@+id/preview_player_view" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - </com.google.android.exoplayer2.ui.AspectRatioFrameLayout> - - <FrameLayout - android:id="@+id/preview_player_controls" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center"> - - <include - android:layout_width="match_parent" - android:layout_height="match_parent" - layout="@layout/preview_video_controls" /> - </FrameLayout> - </FrameLayout> - - <ImageView - android:id="@+id/preview_video_image" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center" - android:scaleType="fitCenter" - android:contentDescription="@null" /> -</FrameLayout> diff --git a/res/layout/item_video_preview.xml b/res/layout/item_video_preview.xml index 1d7d32d49..f8c3b6e73 100644 --- a/res/layout/item_video_preview.xml +++ b/res/layout/item_video_preview.xml @@ -1,36 +1,55 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> +<!-- + ~ Copyright (C) 2022 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. + --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> - - <com.google.android.exoplayer2.ui.StyledPlayerView - android:id="@+id/preview_player_view" + <FrameLayout + android:id="@+id/preview_player_container" android:layout_width="match_parent" android:layout_height="match_parent" - android:visibility="gone" - app:use_controller="true" - app:animation_enabled="true" - app:show_timeout="0" - app:auto_show="false" - app:controller_layout_id="@layout/preview_video_controls"/> + android:layout_gravity="center"> + + <com.google.android.exoplayer2.ui.AspectRatioFrameLayout + android:id="@+id/preview_player_frame" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center"> + + <SurfaceView + android:id="@+id/preview_player_view" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + </com.google.android.exoplayer2.ui.AspectRatioFrameLayout> + + <FrameLayout + android:id="@+id/preview_player_controls" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center"> + + <include + android:layout_width="match_parent" + android:layout_height="match_parent" + layout="@layout/preview_video_controls" /> + </FrameLayout> + </FrameLayout> <ImageView android:id="@+id/preview_video_image" diff --git a/res/mipmap-hdpi/picker_app_icon.png b/res/mipmap-hdpi/picker_app_icon.png Binary files differindex c911ffcf5..566dd0c49 100644 --- a/res/mipmap-hdpi/picker_app_icon.png +++ b/res/mipmap-hdpi/picker_app_icon.png diff --git a/res/mipmap-mdpi/picker_app_icon.png b/res/mipmap-mdpi/picker_app_icon.png Binary files differindex 9a0f98afd..bf584de46 100644 --- a/res/mipmap-mdpi/picker_app_icon.png +++ b/res/mipmap-mdpi/picker_app_icon.png diff --git a/res/mipmap-xhdpi/picker_app_icon.png b/res/mipmap-xhdpi/picker_app_icon.png Binary files differindex ca55edd90..2b41f701f 100644 --- a/res/mipmap-xhdpi/picker_app_icon.png +++ b/res/mipmap-xhdpi/picker_app_icon.png diff --git a/res/mipmap-xxhdpi/picker_app_icon.png b/res/mipmap-xxhdpi/picker_app_icon.png Binary files differindex 531c94feb..a37b6fab3 100644 --- a/res/mipmap-xxhdpi/picker_app_icon.png +++ b/res/mipmap-xxhdpi/picker_app_icon.png diff --git a/res/mipmap-xxxhdpi/picker_app_icon.png b/res/mipmap-xxxhdpi/picker_app_icon.png Binary files differindex ce7280527..29d73ecac 100644 --- a/res/mipmap-xxxhdpi/picker_app_icon.png +++ b/res/mipmap-xxxhdpi/picker_app_icon.png diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml index 4094c662c..8195a2fa6 100644 --- a/res/values-af/strings.xml +++ b/res/values-af/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Media"</string> <string name="storage_description" msgid="4081716890357580107">"Plaaslike berging"</string> <string name="app_label" msgid="9035307001052716210">"Mediaberging"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Media"</string> <string name="artist_label" msgid="8105600993099120273">"Kunstenaar"</string> <string name="unknown" msgid="2059049215682829375">"Onbekend"</string> <string name="root_images" msgid="5861633549189045666">"Prente"</string> diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index 778965769..735486f36 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"ማህደረመረጃ"</string> <string name="storage_description" msgid="4081716890357580107">"አካባቢያዊ ማከማቻ"</string> <string name="app_label" msgid="9035307001052716210">"ማህደረ መረጃ ማከማቻ"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"ሚዲያ"</string> <string name="artist_label" msgid="8105600993099120273">"አርቲስት"</string> <string name="unknown" msgid="2059049215682829375">"የማይታወቅ"</string> <string name="root_images" msgid="5861633549189045666">"ምስሎች"</string> diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index 745d88a2a..f370817a5 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"الوسائط"</string> <string name="storage_description" msgid="4081716890357580107">"التخزين المحلي"</string> <string name="app_label" msgid="9035307001052716210">"تخزين الوسائط"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"الوسائط"</string> <string name="artist_label" msgid="8105600993099120273">"الفنان"</string> <string name="unknown" msgid="2059049215682829375">"غير معروف"</string> <string name="root_images" msgid="5861633549189045666">"الصور"</string> diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml index 3c0539356..a034a1a29 100644 --- a/res/values-as/strings.xml +++ b/res/values-as/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"মিডিয়া"</string> <string name="storage_description" msgid="4081716890357580107">"স্থানীয় ষ্ট’ৰেজ"</string> <string name="app_label" msgid="9035307001052716210">"মিডিয়া ষ্ট’ৰেজ"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"মিডিয়া"</string> <string name="artist_label" msgid="8105600993099120273">"শিল্পী"</string> <string name="unknown" msgid="2059049215682829375">"অজ্ঞাত"</string> <string name="root_images" msgid="5861633549189045666">"প্ৰতিচ্ছবি"</string> diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index 9192a53b5..19832e3af 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Media"</string> <string name="storage_description" msgid="4081716890357580107">"Yerli yaddaş"</string> <string name="app_label" msgid="9035307001052716210">"Media Yaddaşı"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Media"</string> <string name="artist_label" msgid="8105600993099120273">"Sənətçi"</string> <string name="unknown" msgid="2059049215682829375">"Naməlum"</string> <string name="root_images" msgid="5861633549189045666">"Təsvirlər"</string> diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index a5fbc0e85..f01a7dc0b 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Медыя"</string> <string name="storage_description" msgid="4081716890357580107">"Лакальнае сховішча"</string> <string name="app_label" msgid="9035307001052716210">"Медыясховішча"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Мультымедыя"</string> <string name="artist_label" msgid="8105600993099120273">"Выканаўца"</string> <string name="unknown" msgid="2059049215682829375">"Невядома"</string> <string name="root_images" msgid="5861633549189045666">"Відарысы"</string> diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index d85352d7c..c2d966c2f 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Мултимедия"</string> <string name="storage_description" msgid="4081716890357580107">"Локално хранилище"</string> <string name="app_label" msgid="9035307001052716210">"Мултимедийно хранилище"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Мултимедия"</string> <string name="artist_label" msgid="8105600993099120273">"Изпълнител"</string> <string name="unknown" msgid="2059049215682829375">"Неизвестно"</string> <string name="root_images" msgid="5861633549189045666">"Изображения"</string> diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml index faaa943bb..f0bbc594c 100644 --- a/res/values-bs/strings.xml +++ b/res/values-bs/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Mediji"</string> <string name="storage_description" msgid="4081716890357580107">"Lokalna pohrana"</string> <string name="app_label" msgid="9035307001052716210">"Medijska pohrana"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Medij"</string> <string name="artist_label" msgid="8105600993099120273">"Umjetnik"</string> <string name="unknown" msgid="2059049215682829375">"Nepoznato"</string> <string name="root_images" msgid="5861633549189045666">"Slike"</string> diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index 87fa8d57f..b4f9df885 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Multimèdia"</string> <string name="storage_description" msgid="4081716890357580107">"Emmagatzematge local"</string> <string name="app_label" msgid="9035307001052716210">"Emmagatzematge multimèdia"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Contingut multimèdia"</string> <string name="artist_label" msgid="8105600993099120273">"Artista"</string> <string name="unknown" msgid="2059049215682829375">"Desconegut"</string> <string name="root_images" msgid="5861633549189045666">"Imatges"</string> diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index b7a7b01a5..a95d75ab7 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Média"</string> <string name="storage_description" msgid="4081716890357580107">"Místní úložiště"</string> <string name="app_label" msgid="9035307001052716210">"Úložiště médií"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Média"</string> <string name="artist_label" msgid="8105600993099120273">"Interpret"</string> <string name="unknown" msgid="2059049215682829375">"Neznámý"</string> <string name="root_images" msgid="5861633549189045666">"Obrázky"</string> diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index 09cb22489..b605b8eda 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Medier"</string> <string name="storage_description" msgid="4081716890357580107">"Lokalt lager"</string> <string name="app_label" msgid="9035307001052716210">"Medielagring"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Mediefiler"</string> <string name="artist_label" msgid="8105600993099120273">"Kunstner"</string> <string name="unknown" msgid="2059049215682829375">"Ukendt"</string> <string name="root_images" msgid="5861633549189045666">"Billeder"</string> diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 6d329cdb8..592438e60 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Medien"</string> <string name="storage_description" msgid="4081716890357580107">"Lokaler Speicher"</string> <string name="app_label" msgid="9035307001052716210">"Medienspeicher"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Medien"</string> <string name="artist_label" msgid="8105600993099120273">"Interpret"</string> <string name="unknown" msgid="2059049215682829375">"Unbekannt"</string> <string name="root_images" msgid="5861633549189045666">"Bilder"</string> diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index 1f6e00b5b..7dd01b958 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Multimedia"</string> <string name="storage_description" msgid="4081716890357580107">"Almacenamiento local"</string> <string name="app_label" msgid="9035307001052716210">"Almacenamiento multimedia"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Multimedia"</string> <string name="artist_label" msgid="8105600993099120273">"Artista"</string> <string name="unknown" msgid="2059049215682829375">"Desconocido"</string> <string name="root_images" msgid="5861633549189045666">"Imágenes"</string> diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 4bc45cb91..05d275a7f 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Multimedia"</string> <string name="storage_description" msgid="4081716890357580107">"Almacenamiento local"</string> <string name="app_label" msgid="9035307001052716210">"Almacenamiento multimedia"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Multimedia"</string> <string name="artist_label" msgid="8105600993099120273">"Artista"</string> <string name="unknown" msgid="2059049215682829375">"Desconocido"</string> <string name="root_images" msgid="5861633549189045666">"Imágenes"</string> diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index e23f14a54..7c89a9c60 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Meedia"</string> <string name="storage_description" msgid="4081716890357580107">"Kohalik salvestusruum"</string> <string name="app_label" msgid="9035307001052716210">"Meediumi salvestusruum"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Meedia"</string> <string name="artist_label" msgid="8105600993099120273">"Esitaja"</string> <string name="unknown" msgid="2059049215682829375">"Teadmata"</string> <string name="root_images" msgid="5861633549189045666">"Pildid"</string> diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index 0297200af..d50fce988 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"رسانه"</string> <string name="storage_description" msgid="4081716890357580107">"فضای ذخیرهسازی محلی"</string> <string name="app_label" msgid="9035307001052716210">"فضای ذخیرهسازی رسانه"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"رسانه"</string> <string name="artist_label" msgid="8105600993099120273">"هنرمند"</string> <string name="unknown" msgid="2059049215682829375">"نامشخص"</string> <string name="root_images" msgid="5861633549189045666">"تصویر"</string> diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 01ae3320f..5975059b8 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Media"</string> <string name="storage_description" msgid="4081716890357580107">"Paikallinen tallennustila"</string> <string name="app_label" msgid="9035307001052716210">"Median tallennustila"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Media"</string> <string name="artist_label" msgid="8105600993099120273">"Artisti"</string> <string name="unknown" msgid="2059049215682829375">"Tuntematon"</string> <string name="root_images" msgid="5861633549189045666">"Kuvat"</string> diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index a16db0242..d4f4dbf25 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Multimédia"</string> <string name="storage_description" msgid="4081716890357580107">"Stockage local"</string> <string name="app_label" msgid="9035307001052716210">"Stockage multimédia"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Contenu multimédia"</string> <string name="artist_label" msgid="8105600993099120273">"Artiste"</string> <string name="unknown" msgid="2059049215682829375">"Inconnu"</string> <string name="root_images" msgid="5861633549189045666">"Images"</string> diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index dd7186978..488291d66 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Multimédia"</string> <string name="storage_description" msgid="4081716890357580107">"Stockage local"</string> <string name="app_label" msgid="9035307001052716210">"Stockage multimédia"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Multimédia"</string> <string name="artist_label" msgid="8105600993099120273">"Artiste"</string> <string name="unknown" msgid="2059049215682829375">"Inconnu"</string> <string name="root_images" msgid="5861633549189045666">"Images"</string> diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index f7e788b4d..7a3965b33 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Multimedia"</string> <string name="storage_description" msgid="4081716890357580107">"Almacenamento local"</string> <string name="app_label" msgid="9035307001052716210">"Almacenamento multimedia"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Contido multimedia"</string> <string name="artist_label" msgid="8105600993099120273">"Artista"</string> <string name="unknown" msgid="2059049215682829375">"Descoñecida"</string> <string name="root_images" msgid="5861633549189045666">"Imaxes"</string> diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml index d228a361a..6edca727e 100644 --- a/res/values-gu/strings.xml +++ b/res/values-gu/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"મીડિયા"</string> <string name="storage_description" msgid="4081716890357580107">"સ્થાનિક સ્ટોરેજ"</string> <string name="app_label" msgid="9035307001052716210">"મીડિયા સ્ટોરેજ"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"મીડિયા"</string> <string name="artist_label" msgid="8105600993099120273">"કલાકાર"</string> <string name="unknown" msgid="2059049215682829375">"અજાણ"</string> <string name="root_images" msgid="5861633549189045666">"છબીઓ"</string> diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index ec5eb9ce6..25d7c373f 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"मीडिया"</string> <string name="storage_description" msgid="4081716890357580107">"स्थानीय जगह"</string> <string name="app_label" msgid="9035307001052716210">"मीडिया मेमोरी"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"मीडिया"</string> <string name="artist_label" msgid="8105600993099120273">"कलाकार"</string> <string name="unknown" msgid="2059049215682829375">"अज्ञात"</string> <string name="root_images" msgid="5861633549189045666">"इमेज"</string> diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index 5f3a31fd5..ed77a5531 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Mediji"</string> <string name="storage_description" msgid="4081716890357580107">"Lokalna pohrana"</string> <string name="app_label" msgid="9035307001052716210">"Pohranjivanje na mediju"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Mediji"</string> <string name="artist_label" msgid="8105600993099120273">"Izvođač"</string> <string name="unknown" msgid="2059049215682829375">"Nepoznato"</string> <string name="root_images" msgid="5861633549189045666">"Slike"</string> diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index a6ba73c69..03b231e54 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Média"</string> <string name="storage_description" msgid="4081716890357580107">"Helyi tárhely"</string> <string name="app_label" msgid="9035307001052716210">"Médiatároló"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Média"</string> <string name="artist_label" msgid="8105600993099120273">"Előadó"</string> <string name="unknown" msgid="2059049215682829375">"Ismeretlen"</string> <string name="root_images" msgid="5861633549189045666">"Képek"</string> diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index 83add6b07..129fd1c15 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Մեդիա"</string> <string name="storage_description" msgid="4081716890357580107">"Սարքի հիշողություն"</string> <string name="app_label" msgid="9035307001052716210">"Մեդիա կրիչ"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Մեդիա"</string> <string name="artist_label" msgid="8105600993099120273">"Կատարող"</string> <string name="unknown" msgid="2059049215682829375">"Անհայտ"</string> <string name="root_images" msgid="5861633549189045666">"Պատկերներ"</string> diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index 2cbaae1b1..d7a135b88 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Media"</string> <string name="storage_description" msgid="4081716890357580107">"Penyimpanan lokal"</string> <string name="app_label" msgid="9035307001052716210">"Penyimpanan Media"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Media"</string> <string name="artist_label" msgid="8105600993099120273">"Artis"</string> <string name="unknown" msgid="2059049215682829375">"Tidak diketahui"</string> <string name="root_images" msgid="5861633549189045666">"Gambar"</string> diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index 0d9e5ca26..7b874ded6 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Margmiðlun"</string> <string name="storage_description" msgid="4081716890357580107">"Staðbundin vistun"</string> <string name="app_label" msgid="9035307001052716210">"Efnisgeymsla"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Efni"</string> <string name="artist_label" msgid="8105600993099120273">"Flytjandi"</string> <string name="unknown" msgid="2059049215682829375">"Óþekkt"</string> <string name="root_images" msgid="5861633549189045666">"Myndir"</string> diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 19a189e26..e13675116 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Supporti multimediali"</string> <string name="storage_description" msgid="4081716890357580107">"Archiviazione locale"</string> <string name="app_label" msgid="9035307001052716210">"Media Storage"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Contenuti multimediali"</string> <string name="artist_label" msgid="8105600993099120273">"Artista"</string> <string name="unknown" msgid="2059049215682829375">"Sconosciuto"</string> <string name="root_images" msgid="5861633549189045666">"Immagini"</string> diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index 2d2f56ba7..2b3c0c177 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"מדיה"</string> <string name="storage_description" msgid="4081716890357580107">"אחסון מקומי"</string> <string name="app_label" msgid="9035307001052716210">"אחסון מדיה"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"מדיה"</string> <string name="artist_label" msgid="8105600993099120273">"אומן"</string> <string name="unknown" msgid="2059049215682829375">"לא ידוע"</string> <string name="root_images" msgid="5861633549189045666">"תמונות"</string> diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml index 752b73088..f9b15517d 100644 --- a/res/values-kk/strings.xml +++ b/res/values-kk/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Мультимeдиа"</string> <string name="storage_description" msgid="4081716890357580107">"Жергілікті жад"</string> <string name="app_label" msgid="9035307001052716210">"Мультимедиа қоймасы"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Meдиа"</string> <string name="artist_label" msgid="8105600993099120273">"Орындаушы"</string> <string name="unknown" msgid="2059049215682829375">"Белгісіз"</string> <string name="root_images" msgid="5861633549189045666">"Кескіндер"</string> diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index ff82a6bbe..cc694f1bd 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"មេឌៀ"</string> <string name="storage_description" msgid="4081716890357580107">"ទំហំផ្ទុកមូលដ្ឋាន"</string> <string name="app_label" msgid="9035307001052716210">"ទំហំផ្ទុកមេឌៀ"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"មេឌៀ"</string> <string name="artist_label" msgid="8105600993099120273">"សិល្បករ"</string> <string name="unknown" msgid="2059049215682829375">"មិនស្គាល់"</string> <string name="root_images" msgid="5861633549189045666">"រូបភាព"</string> diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index cf5d34f5a..5b11b4cde 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"미디어"</string> <string name="storage_description" msgid="4081716890357580107">"로컬 저장소"</string> <string name="app_label" msgid="9035307001052716210">"미디어 저장소"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"미디어"</string> <string name="artist_label" msgid="8105600993099120273">"아티스트"</string> <string name="unknown" msgid="2059049215682829375">"알 수 없음"</string> <string name="root_images" msgid="5861633549189045666">"이미지"</string> diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml index 0153ba110..241291929 100644 --- a/res/values-lo/strings.xml +++ b/res/values-lo/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"ມີເດຍ"</string> <string name="storage_description" msgid="4081716890357580107">"ບ່ອນຈັດເກັບຂໍ້ມູນໃນເຄື່ອງ"</string> <string name="app_label" msgid="9035307001052716210">"ພື້ນທີ່ຈັດເກັບຂໍ້ມູນມີເດຍ"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"ສື່"</string> <string name="artist_label" msgid="8105600993099120273">"ສິນລະປິນ"</string> <string name="unknown" msgid="2059049215682829375">"ບໍ່ຮູ້ຈັກ"</string> <string name="root_images" msgid="5861633549189045666">"ຮູບພາບ"</string> diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 106a7567c..5e7b7d53c 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Medija"</string> <string name="storage_description" msgid="4081716890357580107">"Vietinė saugykla"</string> <string name="app_label" msgid="9035307001052716210">"Medijos saugykla"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Medija"</string> <string name="artist_label" msgid="8105600993099120273">"Atlikėjas"</string> <string name="unknown" msgid="2059049215682829375">"Nežinoma"</string> <string name="root_images" msgid="5861633549189045666">"Vaizdai"</string> diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index fb3996cda..a201f12f1 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Multivide"</string> <string name="storage_description" msgid="4081716890357580107">"Lokālā krātuve"</string> <string name="app_label" msgid="9035307001052716210">"Multivides krātuve"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Multivide"</string> <string name="artist_label" msgid="8105600993099120273">"Izpildītājs"</string> <string name="unknown" msgid="2059049215682829375">"Nezināms"</string> <string name="root_images" msgid="5861633549189045666">"Attēli"</string> diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index 048adefd8..bb9f21ad1 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Media"</string> <string name="storage_description" msgid="4081716890357580107">"Storan setempat"</string> <string name="app_label" msgid="9035307001052716210">"Storan Media"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Media"</string> <string name="artist_label" msgid="8105600993099120273">"Artis"</string> <string name="unknown" msgid="2059049215682829375">"Tidak diketahui"</string> <string name="root_images" msgid="5861633549189045666">"Imej"</string> diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index 3ab35be3c..5d33238e5 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"မီဒီယာ"</string> <string name="storage_description" msgid="4081716890357580107">"စက်တွင်း သိုလှောင်ခန်း"</string> <string name="app_label" msgid="9035307001052716210">"မီဒီယာ သိုလှောင်ခန်း"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"မီဒီယာ"</string> <string name="artist_label" msgid="8105600993099120273">"အနုပညာရှင်"</string> <string name="unknown" msgid="2059049215682829375">"အမျိုးအမည်မသိ"</string> <string name="root_images" msgid="5861633549189045666">"ပုံများ"</string> diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index 7a82cf185..e73aea8a7 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Medier"</string> <string name="storage_description" msgid="4081716890357580107">"Lokal lagring"</string> <string name="app_label" msgid="9035307001052716210">"Medielagring"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Medier"</string> <string name="artist_label" msgid="8105600993099120273">"Artist"</string> <string name="unknown" msgid="2059049215682829375">"Ukjent"</string> <string name="root_images" msgid="5861633549189045666">"Bilder"</string> diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index f622d145c..f1ea0631e 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"मिडिया"</string> <string name="storage_description" msgid="4081716890357580107">"स्थानीय भण्डारण"</string> <string name="app_label" msgid="9035307001052716210">"मिडिया भण्डारण"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"मिडिया"</string> <string name="artist_label" msgid="8105600993099120273">"कलाकार"</string> <string name="unknown" msgid="2059049215682829375">"अज्ञात"</string> <string name="root_images" msgid="5861633549189045666">"फोटो"</string> diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index f4281ef88..832d8bc5c 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Media"</string> <string name="storage_description" msgid="4081716890357580107">"Lokale opslag"</string> <string name="app_label" msgid="9035307001052716210">"Mediaopslag"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Media"</string> <string name="artist_label" msgid="8105600993099120273">"Artiest"</string> <string name="unknown" msgid="2059049215682829375">"Onbekend"</string> <string name="root_images" msgid="5861633549189045666">"Afbeeldingen"</string> diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml index d4a3ef927..05511dadd 100644 --- a/res/values-or/strings.xml +++ b/res/values-or/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"ମିଡିଆ"</string> <string name="storage_description" msgid="4081716890357580107">"ଲୋକାଲ୍ ଷ୍ଟୋରେଜ୍"</string> <string name="app_label" msgid="9035307001052716210">"ମିଡିଆ ଷ୍ଟୋରେଜ୍"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"ମିଡିଆ"</string> <string name="artist_label" msgid="8105600993099120273">"କଳାକାର"</string> <string name="unknown" msgid="2059049215682829375">"ଅଜଣା"</string> <string name="root_images" msgid="5861633549189045666">"ଇମେଜ୍"</string> diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml index 74221a293..b1c7c3535 100644 --- a/res/values-pa/strings.xml +++ b/res/values-pa/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"ਮੀਡੀਆ"</string> <string name="storage_description" msgid="4081716890357580107">"ਸਥਾਨਕ ਸਟੋਰੇਜ"</string> <string name="app_label" msgid="9035307001052716210">"ਮੀਡੀਆ ਸਟੋਰੇਜ"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"ਮੀਡੀਆ"</string> <string name="artist_label" msgid="8105600993099120273">"ਕਲਾਕਾਰ"</string> <string name="unknown" msgid="2059049215682829375">"ਅਗਿਆਤ"</string> <string name="root_images" msgid="5861633549189045666">"ਚਿੱਤਰ"</string> diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index c02b334f9..e40e6d4f0 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Multimedia"</string> <string name="storage_description" msgid="4081716890357580107">"Pamięć lokalna"</string> <string name="app_label" msgid="9035307001052716210">"Przechowywanie multimediów"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Multimedia"</string> <string name="artist_label" msgid="8105600993099120273">"Wykonawca"</string> <string name="unknown" msgid="2059049215682829375">"Nieznany"</string> <string name="root_images" msgid="5861633549189045666">"Obrazy"</string> diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index 1ef42106e..4db74cf64 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Multimédia"</string> <string name="storage_description" msgid="4081716890357580107">"Armazenamento local"</string> <string name="app_label" msgid="9035307001052716210">"Armazenamento de multimédia"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Multimédia"</string> <string name="artist_label" msgid="8105600993099120273">"Artista"</string> <string name="unknown" msgid="2059049215682829375">"Desconhecido"</string> <string name="root_images" msgid="5861633549189045666">"Imagens"</string> diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index fa3d679e6..4844c6434 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Мультимедиа"</string> <string name="storage_description" msgid="4081716890357580107">"Локальное хранилище"</string> <string name="app_label" msgid="9035307001052716210">"Хранилище мультимедиа"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Мультимедиа"</string> <string name="artist_label" msgid="8105600993099120273">"Исполнитель"</string> <string name="unknown" msgid="2059049215682829375">"Неизвестно"</string> <string name="root_images" msgid="5861633549189045666">"Изображения"</string> diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml index f25fdc012..c9ffffc3e 100644 --- a/res/values-si/strings.xml +++ b/res/values-si/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"මාධ්ය"</string> <string name="storage_description" msgid="4081716890357580107">"පෙදෙසි ආචයනය"</string> <string name="app_label" msgid="9035307001052716210">"මාධ්ය ගබඩාව"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"මාධ්ය"</string> <string name="artist_label" msgid="8105600993099120273">"කලාකරු"</string> <string name="unknown" msgid="2059049215682829375">"නොදනී"</string> <string name="root_images" msgid="5861633549189045666">"රූප"</string> diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index 48d0663c8..a3653acd1 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Predstavnost"</string> <string name="storage_description" msgid="4081716890357580107">"Lokalna shramba"</string> <string name="app_label" msgid="9035307001052716210">"Shramba za predstavnost"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Predstavnost"</string> <string name="artist_label" msgid="8105600993099120273">"Izvajalec"</string> <string name="unknown" msgid="2059049215682829375">"Neznano"</string> <string name="root_images" msgid="5861633549189045666">"Slike"</string> diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index f77ad0657..35f975fbe 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Media"</string> <string name="storage_description" msgid="4081716890357580107">"Hapësira ruajtëse lokale"</string> <string name="app_label" msgid="9035307001052716210">"Hapësira ruajtëse e medias"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Media"</string> <string name="artist_label" msgid="8105600993099120273">"Artisti"</string> <string name="unknown" msgid="2059049215682829375">"I panjohur"</string> <string name="root_images" msgid="5861633549189045666">"Fotografitë"</string> diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index ac14f37dc..262b83a38 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Media"</string> <string name="storage_description" msgid="4081716890357580107">"Lokal lagring"</string> <string name="app_label" msgid="9035307001052716210">"Medialagring"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Media"</string> <string name="artist_label" msgid="8105600993099120273">"Artist"</string> <string name="unknown" msgid="2059049215682829375">"Okänd"</string> <string name="root_images" msgid="5861633549189045666">"Bilder"</string> diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml index 74302d3dc..735c55a46 100644 --- a/res/values-sw/strings.xml +++ b/res/values-sw/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Maudhui"</string> <string name="storage_description" msgid="4081716890357580107">"Hifadhi ya ndani"</string> <string name="app_label" msgid="9035307001052716210">"Hifadhi ya Maudhui"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Maudhui"</string> <string name="artist_label" msgid="8105600993099120273">"Msanii"</string> <string name="unknown" msgid="2059049215682829375">"Isiyojulikana"</string> <string name="root_images" msgid="5861633549189045666">"Picha"</string> diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml index 33cd6e42f..468586c6b 100644 --- a/res/values-ta/strings.xml +++ b/res/values-ta/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"மீடியா"</string> <string name="storage_description" msgid="4081716890357580107">"சாதனச் சேமிப்பகம்"</string> <string name="app_label" msgid="9035307001052716210">"மீடியா சேமிப்பிடம்"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"மீடியா"</string> <string name="artist_label" msgid="8105600993099120273">"கலைஞர்"</string> <string name="unknown" msgid="2059049215682829375">"அறியாதது"</string> <string name="root_images" msgid="5861633549189045666">"Images"</string> diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index 6f44520e8..a70fab96f 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"మీడియా"</string> <string name="storage_description" msgid="4081716890357580107">"స్థానిక నిల్వ"</string> <string name="app_label" msgid="9035307001052716210">"మీడియా నిల్వ"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"మీడియా"</string> <string name="artist_label" msgid="8105600993099120273">"కళాకారుడు"</string> <string name="unknown" msgid="2059049215682829375">"తెలియదు"</string> <string name="root_images" msgid="5861633549189045666">"ఇమేజ్లు"</string> diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index 8ab39b74a..b666c7c39 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"สื่อ"</string> <string name="storage_description" msgid="4081716890357580107">"พื้นที่เก็บข้อมูลในเครื่อง"</string> <string name="app_label" msgid="9035307001052716210">"พื้นที่เก็บข้อมูลสื่อ"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"สื่อ"</string> <string name="artist_label" msgid="8105600993099120273">"ศิลปิน"</string> <string name="unknown" msgid="2059049215682829375">"ไม่ทราบ"</string> <string name="root_images" msgid="5861633549189045666">"รูปภาพ"</string> diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index b252453c9..ac006cc17 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Media"</string> <string name="storage_description" msgid="4081716890357580107">"Lokal na storage"</string> <string name="app_label" msgid="9035307001052716210">"Storage ng Media"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Media"</string> <string name="artist_label" msgid="8105600993099120273">"Artist"</string> <string name="unknown" msgid="2059049215682829375">"Hindi alam"</string> <string name="root_images" msgid="5861633549189045666">"Mga Larawan"</string> diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 5232087e1..b03bb2986 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Medya"</string> <string name="storage_description" msgid="4081716890357580107">"Yerel depolama"</string> <string name="app_label" msgid="9035307001052716210">"Medya Deposu"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Medya"</string> <string name="artist_label" msgid="8105600993099120273">"Sanatçı"</string> <string name="unknown" msgid="2059049215682829375">"Bilinmiyor"</string> <string name="root_images" msgid="5861633549189045666">"Resimler"</string> diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index b19305f14..39ff52577 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Медіа-файли"</string> <string name="storage_description" msgid="4081716890357580107">"Локальна пам’ять"</string> <string name="app_label" msgid="9035307001052716210">"Сховище медіа-файлів"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Медіа"</string> <string name="artist_label" msgid="8105600993099120273">"Виконавець"</string> <string name="unknown" msgid="2059049215682829375">"Невідомо"</string> <string name="root_images" msgid="5861633549189045666">"Зображення"</string> diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml index c72614a2c..ae76c01a7 100644 --- a/res/values-ur/strings.xml +++ b/res/values-ur/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"میڈیا"</string> <string name="storage_description" msgid="4081716890357580107">"مقامی اسٹوریج"</string> <string name="app_label" msgid="9035307001052716210">"میڈیا اسٹوریج"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"میڈیا"</string> <string name="artist_label" msgid="8105600993099120273">"فنکار"</string> <string name="unknown" msgid="2059049215682829375">"نامعلوم"</string> <string name="root_images" msgid="5861633549189045666">"تصاوير"</string> diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 5881452ad..b256d480a 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"媒体"</string> <string name="storage_description" msgid="4081716890357580107">"本地存储空间"</string> <string name="app_label" msgid="9035307001052716210">"媒体存储设备"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"媒体"</string> <string name="artist_label" msgid="8105600993099120273">"音乐人"</string> <string name="unknown" msgid="2059049215682829375">"未知"</string> <string name="root_images" msgid="5861633549189045666">"图片"</string> diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index 2012981e3..cbc74c5ec 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"媒體"</string> <string name="storage_description" msgid="4081716890357580107">"本機儲存空間"</string> <string name="app_label" msgid="9035307001052716210">"媒體儲存空間"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"媒體"</string> <string name="artist_label" msgid="8105600993099120273">"歌手"</string> <string name="unknown" msgid="2059049215682829375">"不明"</string> <string name="root_images" msgid="5861633549189045666">"相片"</string> diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index 8a2c73dd0..a1e6f7872 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"媒體"</string> <string name="storage_description" msgid="4081716890357580107">"本機儲存空間"</string> <string name="app_label" msgid="9035307001052716210">"媒體儲存空間"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"媒體"</string> <string name="artist_label" msgid="8105600993099120273">"演出者"</string> <string name="unknown" msgid="2059049215682829375">"不明"</string> <string name="root_images" msgid="5861633549189045666">"圖片"</string> diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index 08b6cf687..1fd002956 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -19,8 +19,7 @@ <string name="uid_label" msgid="8421971615411294156">"Abezind"</string> <string name="storage_description" msgid="4081716890357580107">"Isitoreji sasendaweni"</string> <string name="app_label" msgid="9035307001052716210">"Isitoreji Semidiya"</string> - <!-- no translation found for picker_app_label (4254039089502164761) --> - <skip /> + <string name="picker_app_label" msgid="4254039089502164761">"Imidiya"</string> <string name="artist_label" msgid="8105600993099120273">"Umculi"</string> <string name="unknown" msgid="2059049215682829375">"Akwaziwa"</string> <string name="root_images" msgid="5861633549189045666">"Izithombe"</string> diff --git a/res/values/drawables.xml b/res/values/drawables.xml deleted file mode 100644 index a5a0b3d3b..000000000 --- a/res/values/drawables.xml +++ /dev/null @@ -1,21 +0,0 @@ -<!-- - ~ Copyright (C) 2022 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. - --> -<resources> - <!-- Override StyledPlayerView default control buttons --> - <drawable name="exo_styled_controls_play">@drawable/ic_preview_play</drawable> - <drawable name="exo_styled_controls_pause">@drawable/ic_preview_pause</drawable> - -</resources>
\ No newline at end of file diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java index a5c25110e..e4700b074 100644 --- a/src/com/android/providers/media/DatabaseHelper.java +++ b/src/com/android/providers/media/DatabaseHelper.java @@ -87,6 +87,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -499,30 +500,29 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { @Override public void onDowngrade(final SQLiteDatabase db, final int oldV, final int newV) { - Log.w(TAG, - String.format( - "onDowngrade() for %s from %s to %s. Deleting database:%s in case of a " - + "downgrade.", - mName, oldV, newV, mName)); + Log.w(TAG, String.format(Locale.ROOT, + "onDowngrade() for %s from %s to %s. Deleting database:%s in case of a " + + "downgrade.", mName, oldV, newV, mName)); deleteDatabaseFiles(); throw new IllegalStateException( - String.format("Crashing MP process on database downgrade of %s.", mName)); + String.format(Locale.ROOT, "Crashing MP process on database downgrade of %s.", + mName)); } private void deleteDatabaseFiles() { File dbDir = mContext.getDatabasePath(mName).getParentFile(); File[] files = dbDir.listFiles(); if (files == null) { - Log.w(TAG, - String.format("No database files found on path:%s.", dbDir.getAbsolutePath())); + Log.w(TAG, String.format(Locale.ROOT, "No database files found on path:%s.", + dbDir.getAbsolutePath())); return; } for (File file : files) { if (file.getName().startsWith(mName)) { file.delete(); - Log.w(TAG, - String.format("Database file:%s deleted.", file.getAbsolutePath())); + Log.w(TAG, String.format(Locale.ROOT, "Database file:%s deleted.", + file.getAbsolutePath())); } } } @@ -547,17 +547,17 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { boolean isLastUsedDatabaseSession = isLastUsedDatabaseSession(db); Optional<Long> nextRowIdFromXattrOptional = getNextRowIdFromXattr(); if (isLastUsedDatabaseSession && nextRowIdFromXattrOptional.isPresent()) { - Log.i(TAG, String.format("No database change across sequential open calls for %s.", - mName)); + Log.i(TAG, String.format(Locale.ROOT, + "No database change across sequential open calls for %s.", mName)); mNextRowIdBackup.set(nextRowIdFromXattrOptional.get()); updateSessionIdInDatabaseAndExternalStorage(db); return; } - Log.w(TAG, String.format( + Log.w(TAG, String.format(Locale.ROOT, "%s database inconsistent: isLastUsedDatabaseSession:%b, " - + "nextRowIdOptionalPresent:%b", - mName, isLastUsedDatabaseSession, nextRowIdFromXattrOptional.isPresent())); + + "nextRowIdOptionalPresent:%b", mName, isLastUsedDatabaseSession, + nextRowIdFromXattrOptional.isPresent())); // TODO(b/222313219): Add an assert to ensure that next row id xattr is always // present when DB session id matches across sequential open calls. updateNextRowIdInDatabaseAndExternalStorage(db); @@ -585,8 +585,8 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { boolean setOnExternalStorage = setXattr(DATA_MEDIA_XATTR_DIRECTORY_PATH, getSessionIdXattrKeyForDatabase(), uuid); if (setOnDatabase && setOnExternalStorage) { - Log.i(TAG, String.format("SessionId set to %s on paths %s and %s.", uuid, db.getPath(), - DATA_MEDIA_XATTR_DIRECTORY_PATH)); + Log.i(TAG, String.format(Locale.ROOT, "SessionId set to %s on paths %s and %s.", uuid, + db.getPath(), DATA_MEDIA_XATTR_DIRECTORY_PATH)); } } @@ -1809,7 +1809,8 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { } private void updateUserId(SQLiteDatabase db) { - db.execSQL(String.format("ALTER TABLE files ADD COLUMN _user_id INTEGER DEFAULT %d;", + db.execSQL(String.format(Locale.ROOT, + "ALTER TABLE files ADD COLUMN _user_id INTEGER DEFAULT %d;", UserHandle.myUserId())); } @@ -2271,10 +2272,10 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { backupNextRowId(nextRowId); // Insert and delete a row to update sqlite_sequence counter - db.execSQL(String.format("INSERT INTO files(_ID) VALUES (%d)", nextRowId)); - db.execSQL(String.format("DELETE FROM files WHERE _ID=%d", nextRowId)); - Log.i(TAG, String.format("Updated sqlite counter of Files table of %s to %d.", mName, - nextRowId)); + db.execSQL(String.format(Locale.ROOT, "INSERT INTO files(_ID) VALUES (%d)", nextRowId)); + db.execSQL(String.format(Locale.ROOT, "DELETE FROM files WHERE _ID=%d", nextRowId)); + Log.i(TAG, String.format(Locale.ROOT, "Updated sqlite counter of Files table of %s to %d.", + mName, nextRowId)); } /** @@ -2288,8 +2289,8 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { String.valueOf(backupId)); if (setOnExternalStorage) { mNextRowIdBackup.set(backupId); - Log.i(TAG, String.format("Backed up next row id as:%d on path:%s for %s.", backupId, - DATA_MEDIA_XATTR_DIRECTORY_PATH, mName)); + Log.i(TAG, String.format(Locale.ROOT, "Backed up next row id as:%d on path:%s for %s.", + backupId, DATA_MEDIA_XATTR_DIRECTORY_PATH, mName)); } } @@ -2299,7 +2300,7 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { Os.getxattr(DATA_MEDIA_XATTR_DIRECTORY_PATH, getNextRowIdXattrKeyForDatabase())))); } catch (Exception e) { - Log.e(TAG, String.format("Xattr:%s not found on external storage.", + Log.e(TAG, String.format(Locale.ROOT, "Xattr:%s not found on external storage.", getNextRowIdXattrKeyForDatabase()), e); return Optional.empty(); } @@ -2312,7 +2313,8 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { return EXTERNAL_DB_NEXT_ROW_ID_XATTR_KEY; } throw new RuntimeException( - String.format("Next row id xattr key not defined for database:%s.", mName)); + String.format(Locale.ROOT, "Next row id xattr key not defined for database:%s.", + mName)); } protected String getSessionIdXattrKeyForDatabase() { @@ -2322,7 +2324,8 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { return EXTERNAL_DB_SESSION_ID_XATTR_KEY; } throw new RuntimeException( - String.format("Session id xattr key not defined for database:%s.", mName)); + String.format(Locale.ROOT, "Session id xattr key not defined for database:%s.", + mName)); } protected static boolean setXattr(String path, String key, String value) { @@ -2334,9 +2337,8 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { Log.d(TAG, String.format("xattr set to %s for key:%s on path: %s.", value, key, path)); return true; } catch (Exception e) { - Log.e(TAG, - String.format("Failed to set xattr:%s to %s for path: %s.", key, value, path), - e); + Log.e(TAG, String.format(Locale.ROOT, "Failed to set xattr:%s to %s for path: %s.", key, + value, path), e); return false; } } @@ -2345,9 +2347,8 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { try { return Optional.of(Arrays.toString(Os.getxattr(path, key))); } catch (Exception e) { - Log.w(TAG, - String.format("Exception encountered while reading xattr:%s from path:%s.", key, - path)); + Log.w(TAG, String.format(Locale.ROOT, + "Exception encountered while reading xattr:%s from path:%s.", key, path)); return Optional.empty(); } } @@ -2380,7 +2381,8 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { } if (!(new File(DATA_MEDIA_XATTR_DIRECTORY_PATH)).exists()) { - Log.w(TAG, String.format("Skipping row id recovery as path:%s does not exist.", + Log.w(TAG, String.format(Locale.ROOT, + "Skipping row id recovery as path:%s does not exist.", DATA_MEDIA_XATTR_DIRECTORY_PATH)); return false; } diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java index 3305fa626..6354dcbbc 100644 --- a/src/com/android/providers/media/MediaProvider.java +++ b/src/com/android/providers/media/MediaProvider.java @@ -35,6 +35,7 @@ import static android.provider.MediaStore.MATCH_DEFAULT; import static android.provider.MediaStore.MATCH_EXCLUDE; import static android.provider.MediaStore.MATCH_INCLUDE; import static android.provider.MediaStore.MATCH_ONLY; +import static android.provider.MediaStore.MEDIA_IGNORE_FILENAME; import static android.provider.MediaStore.MY_UID; import static android.provider.MediaStore.PER_USER_RANGE; import static android.provider.MediaStore.QUERY_ARG_DEFER_SCAN; @@ -121,6 +122,7 @@ import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipDescription; +import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentProviderClient; import android.content.ContentProviderOperation; @@ -225,6 +227,7 @@ import com.android.providers.media.dao.FileRow; import com.android.providers.media.fuse.ExternalStorageServiceImpl; import com.android.providers.media.fuse.FuseDaemon; import com.android.providers.media.metrics.PulledMetrics; +import com.android.providers.media.photopicker.PhotoPickerActivity; import com.android.providers.media.photopicker.PickerDataLayer; import com.android.providers.media.photopicker.PickerSyncController; import com.android.providers.media.photopicker.data.ExternalDbFacade; @@ -342,6 +345,8 @@ public class MediaProvider extends ContentProvider { private static final String DIRECTORY_MEDIA = "media"; private static final String DIRECTORY_THUMBNAILS = ".thumbnails"; + private static final String TAKE_OVER_GET_CONTENT = "take_over_get_content"; + /** * Hard-coded filename where the current value of * {@link DatabaseHelper#getOrCreateUuid} is persisted on a physical SD card @@ -848,15 +853,16 @@ public class MediaProvider extends ContentProvider { Optional<Long> nextRowIdBackupOptional = helper.getNextRowId(); if (!nextRowIdBackupOptional.isPresent()) { - throw new RuntimeException(String.format("Cannot find next row id xattr for %s.", - helper.getDatabaseName())); + throw new RuntimeException( + String.format(Locale.ROOT, "Cannot find next row id xattr for %s.", + helper.getDatabaseName())); } if (id >= nextRowIdBackupOptional.get()) { helper.backupNextRowId(id); } else { - Log.v(TAG, String.format("Inserted id:%d less than next row id backup:%d.", id, - nextRowIdBackupOptional.get())); + Log.v(TAG, String.format(Locale.ROOT, "Inserted id:%d less than next row id backup:%d.", + id, nextRowIdBackupOptional.get())); } } @@ -1180,10 +1186,29 @@ public class MediaProvider extends ContentProvider { mExternalStorageAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid); } + checkDeviceConfigAndUpdateGetContentAlias(); + PulledMetrics.initialize(context); return true; } + @VisibleForTesting + protected void checkDeviceConfigAndUpdateGetContentAlias() { + final String photoPickerGetContentActivity = + PhotoPickerActivity.class.getPackage().getName() + ".PhotoPickerGetContentActivity"; + final ComponentName componentName = new ComponentName(getContext().getPackageName(), + photoPickerGetContentActivity); + + final int expectedState = getBooleanDeviceConfig(TAKE_OVER_GET_CONTENT, false) + ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + + Log.i(TAG, "Change component enabled state to " + expectedState + + " for PhotoPickerGetContentActivity. "); + getContext().getPackageManager().setComponentEnabledSetting(componentName, expectedState, + PackageManager.DONT_KILL_APP); + } + Optional<DatabaseHelper> getDatabaseHelper(String dbName) { if (dbName.equalsIgnoreCase(INTERNAL_DATABASE_NAME)) { return Optional.of(mInternalDatabase); @@ -1289,7 +1314,8 @@ public class MediaProvider extends ContentProvider { cleanMediaFilesForRemovedUser(signal); // Populate _SPECIAL_FORMAT column for files which have column value as NULL - detectSpecialFormat(signal); + // TODO(b/236620024): Do not update generation_modified for special_format value update + // detectSpecialFormat(signal); final long durationMillis = (SystemClock.elapsedRealtime() - startTime); Metrics.logIdleMaintenance(MediaStore.VOLUME_EXTERNAL, itemCount, @@ -6652,6 +6678,7 @@ public class MediaProvider extends ContentProvider { final File[] files = thumbDir.listFiles(); for (File thumbFile : (files != null) ? files : new File[0]) { if (Objects.equals(thumbFile.getName(), FILE_DATABASE_UUID)) continue; + if (Objects.equals(thumbFile.getName(), MEDIA_IGNORE_FILENAME)) continue; final String name = FileUtils.extractFileName(thumbFile.getName()); try { final long id = Long.parseLong(name); @@ -7794,8 +7821,6 @@ public class MediaProvider extends ContentProvider { } private boolean isPickerUri(Uri uri) { - // TODO(b/188394433): move this method to PickerResolver in the spirit of not - // adding picker logic to MediaProvider final int match = matchUri(uri, /* allowHidden */ isCallingPackageAllowedHidden()); return match == PICKER_ID; } @@ -8407,7 +8432,7 @@ public class MediaProvider extends ContentProvider { private void deleteIfAllowed(Uri uri, Bundle extras, String path) { try { - final File file = new File(path); + final File file = new File(path).getCanonicalFile(); checkAccess(uri, extras, file, true); deleteAndInvalidate(file); } catch (Exception e) { diff --git a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java index aa89047e7..36e6fb771 100644 --- a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java +++ b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java @@ -35,6 +35,7 @@ import android.graphics.Outline; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.UserHandle; @@ -139,7 +140,7 @@ public class PhotoPickerActivity extends AppCompatActivity { try { mPickerViewModel.parseValuesFromIntent(intent); } catch (IllegalArgumentException e) { - Log.e(TAG, "Finished activity due to an exception while parsing extras", e); + Log.e(TAG, "Finish activity due to an exception while parsing extras", e); setCancelledResultAndFinishSelf(); } @@ -238,6 +239,7 @@ public class PhotoPickerActivity extends AppCompatActivity { @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.browse) { + mPickerViewModel.logBrowseToDocumentsUi(Binder.getCallingUid(), getCallingPackage()); launchDocumentsUiAndFinishPicker(); } return super.onOptionsItemSelected(item); @@ -253,8 +255,8 @@ public class PhotoPickerActivity extends AppCompatActivity { // GET_CONTENT for all (media and non-media) files opens DocumentsUi, but it still shows // "Photo Picker app option. When the user clicks on "Photo Picker", the same intent which // includes filters to show non-media files as well is forwarded to PhotoPicker. - // Make sure Photo Picker is opened when the intent is explicitly forwarded. - if (isIntentForwarded(intent)) { + // Make sure Photo Picker is opened when the intent is explicitly forwarded by documentsUi + if (isIntentReferredByDocumentsUi(getReferrer())) { Log.i(TAG, "Open PhotoPicker when a forwarded ACTION_GET_CONTENT intent is received"); return; } @@ -264,8 +266,11 @@ public class PhotoPickerActivity extends AppCompatActivity { } } - private static boolean isIntentForwarded(Intent intent) { - return (intent.getFlags() & Intent.FLAG_ACTIVITY_FORWARD_RESULT) > 0; + private boolean isIntentReferredByDocumentsUi(Uri referrerAppUri) { + ComponentName documentsUiComponentName = getDocumentsUiComponentName(this); + String documentsUiPackageName = documentsUiComponentName != null + ? documentsUiComponentName.getPackageName() : null; + return referrerAppUri != null && referrerAppUri.getHost().equals(documentsUiPackageName); } private void launchDocumentsUiAndFinishPicker() { diff --git a/src/com/android/providers/media/photopicker/PhotoPickerProvider.java b/src/com/android/providers/media/photopicker/PhotoPickerProvider.java index 28a21f54a..79f7326e8 100644 --- a/src/com/android/providers/media/photopicker/PhotoPickerProvider.java +++ b/src/com/android/providers/media/photopicker/PhotoPickerProvider.java @@ -72,7 +72,7 @@ public class PhotoPickerProvider extends CloudMediaProvider { CloudProviderQueryExtras.fromCloudMediaBundle(extras); return mDbFacade.queryMedia(queryExtras.getGeneration(), queryExtras.getAlbumId(), - queryExtras.getMimeType()); + queryExtras.getMimeTypes()); } @Override @@ -88,7 +88,7 @@ public class PhotoPickerProvider extends CloudMediaProvider { final CloudProviderQueryExtras queryExtras = CloudProviderQueryExtras.fromCloudMediaBundle(extras); - return mDbFacade.queryAlbums(queryExtras.getMimeType()); + return mDbFacade.queryAlbums(queryExtras.getMimeTypes()); } @Override diff --git a/src/com/android/providers/media/photopicker/data/CloudProviderQueryExtras.java b/src/com/android/providers/media/photopicker/data/CloudProviderQueryExtras.java index 0100c052c..d2cb83dcd 100644 --- a/src/com/android/providers/media/photopicker/data/CloudProviderQueryExtras.java +++ b/src/com/android/providers/media/photopicker/data/CloudProviderQueryExtras.java @@ -18,6 +18,7 @@ package com.android.providers.media.photopicker.data; import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.BOOLEAN_DEFAULT; import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.LIMIT_DEFAULT; import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.LONG_DEFAULT; +import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.STRING_ARRAY_DEFAULT; import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.STRING_DEFAULT; import android.os.Bundle; @@ -31,7 +32,7 @@ import android.provider.MediaStore; public class CloudProviderQueryExtras { private final String mAlbumId; private final String mAlbumAuthority; - private final String mMimeType; + private final String[] mMimeTypes; private final long mSizeBytes; private final long mGeneration; private final int mLimit; @@ -41,7 +42,7 @@ public class CloudProviderQueryExtras { private CloudProviderQueryExtras() { mAlbumId = STRING_DEFAULT; mAlbumAuthority = STRING_DEFAULT; - mMimeType = STRING_DEFAULT; + mMimeTypes = STRING_ARRAY_DEFAULT; mSizeBytes = LONG_DEFAULT; mGeneration = LONG_DEFAULT; mLimit = LIMIT_DEFAULT; @@ -49,11 +50,11 @@ public class CloudProviderQueryExtras { mIsVideo = BOOLEAN_DEFAULT; } - private CloudProviderQueryExtras (String albumId, String albumAuthority, String mimeType, + private CloudProviderQueryExtras(String albumId, String albumAuthority, String[] mimeTypes, long sizeBytes, long generation, int limit, boolean isFavorite, boolean isVideo) { mAlbumId = albumId; mAlbumAuthority = albumAuthority; - mMimeType = mimeType; + mMimeTypes = mimeTypes; mSizeBytes = sizeBytes; mGeneration = generation; mLimit = limit; @@ -70,7 +71,7 @@ public class CloudProviderQueryExtras { final String albumId = bundle.getString(MediaStore.QUERY_ARG_ALBUM_ID, STRING_DEFAULT); final String albumAuthority = bundle.getString(MediaStore.QUERY_ARG_ALBUM_AUTHORITY, STRING_DEFAULT); - final String mimeType = bundle.getString(MediaStore.QUERY_ARG_MIME_TYPE, STRING_DEFAULT); + final String[] mimeTypes = bundle.getStringArray(MediaStore.QUERY_ARG_MIME_TYPE); final long sizeBytes = bundle.getLong(MediaStore.QUERY_ARG_SIZE_BYTES, LONG_DEFAULT); final long generation = LONG_DEFAULT; @@ -81,7 +82,7 @@ public class CloudProviderQueryExtras { final boolean isVideo = localProvider.equals(albumAuthority) && AlbumColumns.ALBUM_ID_VIDEOS.equals(albumId); - return new CloudProviderQueryExtras(albumId, albumAuthority, mimeType, sizeBytes, + return new CloudProviderQueryExtras(albumId, albumAuthority, mimeTypes, sizeBytes, generation, limit, isFavorite, isVideo); } @@ -93,8 +94,8 @@ public class CloudProviderQueryExtras { final String albumId = bundle.getString(CloudMediaProviderContract.EXTRA_ALBUM_ID, STRING_DEFAULT); final String albumAuthority = STRING_DEFAULT; - final String mimeType = bundle.getString(CloudMediaProviderContract.EXTRA_MIME_TYPE, - STRING_DEFAULT); + final String[] mimeTypes = bundle.getStringArray( + CloudMediaProviderContract.EXTRA_MIME_TYPE); final long sizeBytes = bundle.getLong(CloudMediaProviderContract.EXTRA_SIZE_LIMIT_BYTES, LONG_DEFAULT); final long generation = bundle.getLong(CloudMediaProviderContract.EXTRA_SYNC_GENERATION, @@ -104,14 +105,14 @@ public class CloudProviderQueryExtras { final boolean isFavorite = BOOLEAN_DEFAULT; final boolean isVideo = BOOLEAN_DEFAULT; - return new CloudProviderQueryExtras(albumId, albumAuthority, mimeType, sizeBytes, + return new CloudProviderQueryExtras(albumId, albumAuthority, mimeTypes, sizeBytes, generation, limit, isFavorite, isVideo); } public PickerDbFacade.QueryFilter toQueryFilter() { PickerDbFacade.QueryFilterBuilder qfb = new PickerDbFacade.QueryFilterBuilder(mLimit); qfb.setSizeBytes(mSizeBytes); - qfb.setMimeType(mMimeType); + qfb.setMimeTypes(mMimeTypes); qfb.setIsFavorite(mIsFavorite); qfb.setIsVideo(mIsVideo); qfb.setAlbumId(mAlbumId); @@ -121,7 +122,7 @@ public class CloudProviderQueryExtras { public Bundle toCloudMediaBundle() { final Bundle extras = new Bundle(); extras.putString(CloudMediaProviderContract.EXTRA_ALBUM_ID, mAlbumId); - extras.putString(CloudMediaProviderContract.EXTRA_MIME_TYPE, mMimeType); + extras.putStringArray(CloudMediaProviderContract.EXTRA_MIME_TYPE, mMimeTypes); extras.putLong(CloudMediaProviderContract.EXTRA_SIZE_LIMIT_BYTES, mSizeBytes); return extras; @@ -135,8 +136,8 @@ public class CloudProviderQueryExtras { return mAlbumAuthority; } - public String getMimeType() { - return mMimeType; + public String[] getMimeTypes() { + return mMimeTypes; } public long getSizeBytes() { diff --git a/src/com/android/providers/media/photopicker/data/ExternalDbFacade.java b/src/com/android/providers/media/photopicker/data/ExternalDbFacade.java index d849c1f5d..98f0693e0 100644 --- a/src/com/android/providers/media/photopicker/data/ExternalDbFacade.java +++ b/src/com/android/providers/media/photopicker/data/ExternalDbFacade.java @@ -27,10 +27,10 @@ import static android.provider.CloudMediaProviderContract.EXTRA_SYNC_GENERATION; import static android.provider.CloudMediaProviderContract.MediaCollectionInfo; import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.LONG_DEFAULT; +import static com.android.providers.media.photopicker.data.PickerDbFacade.addMimeTypesToQueryBuilderAndSelectionArgs; import static com.android.providers.media.photopicker.util.CursorUtils.getCursorLong; import static com.android.providers.media.photopicker.util.CursorUtils.getCursorString; import static com.android.providers.media.util.DatabaseUtils.bindList; -import static com.android.providers.media.util.DatabaseUtils.replaceMatchAnyChar; import android.content.ContentValues; import android.content.Context; @@ -272,7 +272,7 @@ public class ExternalDbFacade { * Returns all items from the files table where {@link MediaColumns#GENERATION_MODIFIED} * is greater than {@code generation}. */ - public Cursor queryMedia(long generation, String albumId, String mimeType) { + public Cursor queryMedia(long generation, String albumId, String[] mimeTypes) { final List<String> selectionArgs = new ArrayList<>(); final String orderBy = CloudMediaProviderContract.MediaColumns.DATE_TAKEN_MILLIS + " DESC"; @@ -281,7 +281,7 @@ public class ExternalDbFacade { qb.appendWhereStandalone(WHERE_GREATER_GENERATION); selectionArgs.add(String.valueOf(generation)); - selectionArgs.addAll(appendWhere(qb, albumId, mimeType)); + selectionArgs.addAll(appendWhere(qb, albumId, mimeTypes)); return qb.query(db, PROJECTION_MEDIA_COLUMNS, /* select */ null, selectionArgs.toArray(new String[selectionArgs.size()]), /* groupBy */ null, @@ -374,14 +374,14 @@ public class ExternalDbFacade { * Categories are determined with the {@link #LOCAL_ALBUM_IDS}. * If there are no media items under an albumId, the album is skipped from the results. */ - public Cursor queryAlbums(String mimeType) { + public Cursor queryAlbums(String[] mimeTypes) { final MatrixCursor c = new MatrixCursor(AlbumColumns.ALL_PROJECTION); for (String albumId: LOCAL_ALBUM_IDS) { Cursor cursor = mDatabaseHelper.runWithTransaction(db -> { final SQLiteQueryBuilder qb = createMediaQueryBuilder(); final List<String> selectionArgs = new ArrayList<>(); - selectionArgs.addAll(appendWhere(qb, albumId, mimeType)); + selectionArgs.addAll(appendWhere(qb, albumId, mimeTypes)); return qb.query(db, PROJECTION_ALBUM_DB, /* selection */ null, selectionArgs.toArray(new String[selectionArgs.size()]), /* groupBy */ null, @@ -419,13 +419,10 @@ public class ExternalDbFacade { } private static List<String> appendWhere(SQLiteQueryBuilder qb, String albumId, - String mimeType) { + String[] mimeTypes) { final List<String> selectionArgs = new ArrayList<>(); - if (mimeType != null) { - qb.appendWhereStandalone(WHERE_MIME_TYPE); - selectionArgs.add(replaceMatchAnyChar(mimeType)); - } + addMimeTypesToQueryBuilderAndSelectionArgs(qb, selectionArgs, mimeTypes); if (albumId == null) { return selectionArgs; diff --git a/src/com/android/providers/media/photopicker/data/ItemsProvider.java b/src/com/android/providers/media/photopicker/data/ItemsProvider.java index fb3287286..07fed2694 100644 --- a/src/com/android/providers/media/photopicker/data/ItemsProvider.java +++ b/src/com/android/providers/media/photopicker/data/ItemsProvider.java @@ -16,8 +16,6 @@ package com.android.providers.media.photopicker.data; -import static com.android.providers.media.photopicker.util.CursorUtils.getCursorString; - import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentProvider; @@ -30,15 +28,12 @@ import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.CloudMediaProviderContract; -import android.provider.CloudMediaProviderContract.AlbumColumns; import android.provider.MediaStore; import android.text.TextUtils; import android.util.Log; import com.android.modules.utils.build.SdkLevel; import com.android.providers.media.PickerUriResolver; -import com.android.providers.media.photopicker.data.PickerDbFacade; import com.android.providers.media.photopicker.data.model.Category; import com.android.providers.media.photopicker.data.model.UserId; @@ -57,7 +52,7 @@ public class ItemsProvider { /** * Returns a {@link Cursor} to all images/videos based on the param passed for - * {@code categoryType}, {@code offset}, {@code limit}, {@code mimeType} and {@code userId}. + * {@code categoryType}, {@code offset}, {@code limit}, {@code mimeTypes} and {@code userId}. * * <p> * By default the returned {@link Cursor} sorts by latest date taken. @@ -77,13 +72,13 @@ public class ItemsProvider { */ @Nullable public Cursor getItems(Category category, int offset, - int limit, @Nullable String mimeType, @Nullable UserId userId) throws + int limit, @Nullable String[] mimeTypes, @Nullable UserId userId) throws IllegalArgumentException { if (userId == null) { userId = UserId.CURRENT_USER; } - return queryMedia(limit, mimeType, category, userId); + return queryMedia(limit, mimeTypes, category, userId); } /** @@ -107,15 +102,15 @@ public class ItemsProvider { * in the category, */ @Nullable - public Cursor getCategories(@Nullable String mimeType, @Nullable UserId userId) { + public Cursor getCategories(@Nullable String[] mimeTypes, @Nullable UserId userId) { if (userId == null) { userId = UserId.CURRENT_USER; } - return queryAlbums(mimeType, userId); + return queryAlbums(mimeTypes, userId); } - private Cursor queryMedia(int limit, @Nullable String mimeType, + private Cursor queryMedia(int limit, String[] mimeTypes, @NonNull Category category, @NonNull UserId userId) throws IllegalStateException { final Bundle extras = new Bundle(); @@ -127,7 +122,9 @@ public class ItemsProvider { return null; } extras.putInt(MediaStore.QUERY_ARG_LIMIT, limit); - extras.putString(MediaStore.QUERY_ARG_MIME_TYPE, mimeType); + if (mimeTypes != null) { + extras.putStringArray(MediaStore.QUERY_ARG_MIME_TYPE, mimeTypes); + } extras.putString(MediaStore.QUERY_ARG_ALBUM_ID, category.getId()); extras.putString(MediaStore.QUERY_ARG_ALBUM_AUTHORITY, category.getAuthority()); @@ -144,7 +141,7 @@ public class ItemsProvider { } @Nullable - private Cursor queryAlbums(@Nullable String mimeType, @NonNull UserId userId) { + private Cursor queryAlbums(@Nullable String[] mimeTypes, @NonNull UserId userId) { final Bundle extras = new Bundle(); try (ContentProviderClient client = userId.getContentResolver(mContext) .acquireUnstableContentProviderClient(MediaStore.AUTHORITY)) { @@ -153,7 +150,9 @@ public class ItemsProvider { + MediaStore.AUTHORITY); return null; } - extras.putString(MediaStore.QUERY_ARG_MIME_TYPE, mimeType); + if (mimeTypes != null) { + extras.putStringArray(MediaStore.QUERY_ARG_MIME_TYPE, mimeTypes); + } final Uri uri = PickerUriResolver.PICKER_INTERNAL_URI.buildUpon() .appendPath(PickerUriResolver.ALBUM_PATH).build(); diff --git a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java index 3585a94e3..1a0dda2c1 100644 --- a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java +++ b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java @@ -545,12 +545,12 @@ public class PickerDbFacade { private final long mId; private final String mAlbumId; private final long mSizeBytes; - private final String mMimeType; + private final String[] mMimeTypes; private final boolean mIsFavorite; private final boolean mIsVideo; private QueryFilter(int limit, long dateTakenBeforeMs, long dateTakenAfterMs, long id, - String albumId, long sizeBytes, String mimeType, boolean isFavorite, + String albumId, long sizeBytes, String[] mimeTypes, boolean isFavorite, boolean isVideo) { this.mLimit = limit; this.mDateTakenBeforeMs = dateTakenBeforeMs; @@ -558,7 +558,7 @@ public class PickerDbFacade { this.mId = id; this.mAlbumId = albumId; this.mSizeBytes = sizeBytes; - this.mMimeType = mimeType; + this.mMimeTypes = mimeTypes; this.mIsFavorite = isFavorite; this.mIsVideo = isVideo; } @@ -568,6 +568,7 @@ public class PickerDbFacade { public static class QueryFilterBuilder { public static final long LONG_DEFAULT = -1; public static final String STRING_DEFAULT = null; + public static final String[] STRING_ARRAY_DEFAULT = null; public static final boolean BOOLEAN_DEFAULT = false; public static final int LIMIT_DEFAULT = 1000; @@ -578,7 +579,7 @@ public class PickerDbFacade { private long id = LONG_DEFAULT; private String albumId = STRING_DEFAULT; private long sizeBytes = LONG_DEFAULT; - private String mimeType = STRING_DEFAULT; + private String[] mimeTypes = STRING_ARRAY_DEFAULT; private boolean isFavorite = BOOLEAN_DEFAULT; private boolean mIsVideo = BOOLEAN_DEFAULT; @@ -622,8 +623,8 @@ public class PickerDbFacade { return this; } - public QueryFilterBuilder setMimeType(String mimeType) { - this.mimeType = mimeType; + public QueryFilterBuilder setMimeTypes(String[] mimeTypes) { + this.mimeTypes = mimeTypes; return this; } @@ -649,7 +650,7 @@ public class PickerDbFacade { public QueryFilter build() { return new QueryFilter(limit, dateTakenBeforeMs, dateTakenAfterMs, id, albumId, - sizeBytes, mimeType, isFavorite, mIsVideo); + sizeBytes, mimeTypes, isFavorite, mIsVideo); } } @@ -737,10 +738,7 @@ public class PickerDbFacade { qb.appendWhereStandalone(WHERE_MIME_TYPE); selectionArgs.add("video/%"); } - if (query.mMimeType != null) { - qb.appendWhereStandalone(WHERE_MIME_TYPE); - selectionArgs.add(query.mMimeType.replace('*', '%')); - } + addMimeTypesToQueryBuilderAndSelectionArgs(qb, selectionArgs, query.mMimeTypes); Cursor cursor = qb.query(mDatabase, PROJECTION_ALBUM_DB, /* selection */ null, selectionArgs.toArray(new String[0]), /* groupBy */ null, /* having */ null, @@ -1004,10 +1002,8 @@ public class PickerDbFacade { selectArgs.add(String.valueOf(query.mSizeBytes)); } - if (query.mMimeType != null) { - qb.appendWhereStandalone(WHERE_MIME_TYPE); - selectArgs.add(replaceMatchAnyChar(query.mMimeType)); - } + addMimeTypesToQueryBuilderAndSelectionArgs(qb, selectArgs, query.mMimeTypes); + if (query.mIsVideo) { qb.appendWhereStandalone(WHERE_MIME_TYPE); selectArgs.add(VIDEO_MIME_TYPES); @@ -1025,6 +1021,27 @@ public class PickerDbFacade { return selectArgs.toArray(new String[selectArgs.size()]); } + static void addMimeTypesToQueryBuilderAndSelectionArgs(SQLiteQueryBuilder qb, + List<String> selectionArgs, String[] mimeTypes) { + if (mimeTypes == null) { + return; + } + + mimeTypes = replaceMatchAnyChar(mimeTypes); + ArrayList<String> whereMimeTypes = new ArrayList<>(); + for (String mimeType : mimeTypes) { + if (!TextUtils.isEmpty(mimeType)) { + whereMimeTypes.add(WHERE_MIME_TYPE); + selectionArgs.add(mimeType); + } + } + + if (whereMimeTypes.isEmpty()) { + return; + } + qb.appendWhereStandalone(TextUtils.join(" OR ", whereMimeTypes)); + } + private static SQLiteQueryBuilder createMediaQueryBuilder() { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(TABLE_MEDIA); diff --git a/src/com/android/providers/media/photopicker/metrics/PhotoPickerUiEventLogger.java b/src/com/android/providers/media/photopicker/metrics/PhotoPickerUiEventLogger.java index 511c6b6c0..076ad318d 100644 --- a/src/com/android/providers/media/photopicker/metrics/PhotoPickerUiEventLogger.java +++ b/src/com/android/providers/media/photopicker/metrics/PhotoPickerUiEventLogger.java @@ -29,7 +29,9 @@ public class PhotoPickerUiEventLogger { @UiEvent(doc = "Photo picker opened in work profile") PHOTO_PICKER_OPEN_WORK_PROFILE(943), @UiEvent(doc = "Photo picker opened via GET_CONTENT intent") - PHOTO_PICKER_OPEN_GET_CONTENT(1080); + PHOTO_PICKER_OPEN_GET_CONTENT(1080), + @UiEvent(doc = "DocumentsUi opened by clicking on Browse in Photo picker") + PHOTO_PICKER_BROWSE_DOCUMENTSUI(1085); private final int mId; @@ -75,4 +77,17 @@ public class PhotoPickerUiEventLogger { callingPackage, instanceId); } + + /** + * Log metrics to notify that user has clicked on "Browse..." in Photo picker overflow menu. + * This UI click even opens DocumentsUi. + */ + public void logBrowseToDocumentsUi(InstanceId instanceId, int callingUid, + String callingPackage) { + logger.logWithInstanceId( + PhotoPickerEvent.PHOTO_PICKER_BROWSE_DOCUMENTSUI, + callingUid, + callingPackage, + instanceId); + } } diff --git a/src/com/android/providers/media/photopicker/ui/AlbumsTabFragment.java b/src/com/android/providers/media/photopicker/ui/AlbumsTabFragment.java index 3f946ca60..5060486a6 100644 --- a/src/com/android/providers/media/photopicker/ui/AlbumsTabFragment.java +++ b/src/com/android/providers/media/photopicker/ui/AlbumsTabFragment.java @@ -47,7 +47,7 @@ public class AlbumsTabFragment extends TabFragment { setEmptyMessage(R.string.picker_albums_empty_message); final AlbumsTabAdapter adapter = new AlbumsTabAdapter(mImageLoader, this::onItemClick, - mPickerViewModel.hasMimeTypeFilter()); + mPickerViewModel.hasMimeTypeFilters()); mPickerViewModel.getCategories().observe(this, categoryList -> { adapter.updateCategoryList(categoryList); // Handle emptyView's visibility diff --git a/src/com/android/providers/media/photopicker/ui/ExoPlayerWrapper.java b/src/com/android/providers/media/photopicker/ui/ExoPlayerWrapper.java deleted file mode 100644 index 44ceea275..000000000 --- a/src/com/android/providers/media/photopicker/ui/ExoPlayerWrapper.java +++ /dev/null @@ -1,321 +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.ui; - -import android.content.Context; -import android.media.AudioAttributes; -import android.media.AudioFocusRequest; -import android.media.AudioManager; -import android.net.Uri; -import android.view.View; -import android.view.accessibility.AccessibilityManager; -import android.widget.ImageButton; -import android.widget.ImageView; - -import com.android.providers.media.R; -import com.android.providers.media.photopicker.data.MuteStatus; - -import com.google.android.exoplayer2.DefaultLoadControl; -import com.google.android.exoplayer2.DefaultRenderersFactory; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.LoadControl; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector; -import com.google.android.exoplayer2.source.MediaParserExtractorAdapter; -import com.google.android.exoplayer2.source.ProgressiveMediaSource; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.ui.StyledPlayerView; -import com.google.android.exoplayer2.upstream.ContentDataSource; -import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.util.Clock; - -/** - * A helper class that assists in initialize/prepare/play/release of ExoPlayer. The class assumes - * that all its public methods are called from main thread only. - */ -class ExoPlayerWrapper { - private static final String TAG = "ExoPlayerWrapper"; - // The minimum duration of media that the player will attempt to ensure is buffered at all - // times. - private static final int MIN_BUFFER_MS = 1000; - // The maximum duration of media that the player will attempt to buffer. - private static final int MAX_BUFFER_MS = 2000; - // The duration of media that must be buffered for playback to start or resume following a user - // action such as a seek. - private static final int BUFFER_FOR_PLAYBACK_MS = 1000; - // The default duration of media that must be buffered for playback to resume after a rebuffer. - private static final int BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 1000; - private static final LoadControl sLoadControl = new DefaultLoadControl.Builder() - .setBufferDurationsMs( - MIN_BUFFER_MS, - MAX_BUFFER_MS, - BUFFER_FOR_PLAYBACK_MS, - BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS).build(); - private static final long PLAYER_CONTROL_ON_PLAY_TIMEOUT_MS = 1000; - private static final float VOLUME_LEVEL_MUTE = 0.0f; - private static final AudioAttributes sAudioAttributes = new AudioAttributes.Builder() - .setContentType(AudioAttributes.USAGE_MEDIA) - .setUsage(AudioAttributes.CONTENT_TYPE_MOVIE) - .build(); - - private final Context mContext; - private final MuteStatus mMuteStatus; - private ExoPlayer mExoPlayer; - private boolean mIsPlayerReleased = true; - private boolean mShouldShowControlsForNext = true; - private boolean mIsAccessibilityEnabled = false; - private AudioFocusRequest mAudioFocusRequest = null; - - public ExoPlayerWrapper(Context context, MuteStatus muteStatus) { - mContext = context; - mMuteStatus = muteStatus; - } - - /** - * Prepares the {@link ExoPlayer} and attaches it to given {@code styledPlayerView} and starts - * playing. - * Note: The method tries to release the {@link ExoPlayer} before preparing the new one. As we - * don't have previous page's {@link StyledPlayerView}, we can't switch the player from previous - * {@link StyledPlayerView} to new one. Hence, we try to create a new {@link ExoPlayer} instead. - */ - public void prepareAndPlay(StyledPlayerView styledPlayerView, ImageView imageView, Uri uri) { - // TODO(b/197083539): Explore options for not re-creating ExoPlayer everytime. - initializeExoPlayer(uri); - - setupPlayerLayout(styledPlayerView, imageView); - - // Prepare the player and play the video - mExoPlayer.prepare(); - mExoPlayer.setPlayWhenReady(true); - mIsPlayerReleased = false; - } - - public void resetPlayerIfNecessary() { - // Clear state of the previous player controls visibility state. Controls visibility state - // will only be tracked and used for contiguous videos in the preview. - mShouldShowControlsForNext = true; - // Release the player if necessary. - releaseIfNecessary(); - } - - private void initializeExoPlayer(Uri uri) { - // Try releasing the ExoPlayer first. - releaseIfNecessary(); - - mExoPlayer = createExoPlayer(); - // We always start from the beginning of the video, and we always repeat the video in a loop - mExoPlayer.setRepeatMode(Player.REPEAT_MODE_ONE); - // We only play one video in the player, hence we should always use setMediaItem instead of - // ExoPlayer#addMediaItem - mExoPlayer.setMediaItem(MediaItem.fromUri(uri)); - } - - private ExoPlayer createExoPlayer() { - // ProgressiveMediaFactory will be enough for video playback of videos on the device. - // This also reduces apk size. - ProgressiveMediaSource.Factory mediaSourceFactory = new ProgressiveMediaSource.Factory( - () -> new ContentDataSource(mContext), MediaParserExtractorAdapter.FACTORY); - - return new ExoPlayer.Builder(mContext, - new DefaultRenderersFactory(mContext), - mediaSourceFactory, - new DefaultTrackSelector(mContext), - sLoadControl, - DefaultBandwidthMeter.getSingletonInstance(mContext), - new DefaultAnalyticsCollector(Clock.DEFAULT)) - .setHandleAudioBecomingNoisy(true) - .build(); - } - - private void setupPlayerLayout(StyledPlayerView styledPlayerView, ImageView imageView) { - // Step1: Set-up Player layout - // TODO(b/197083539): Remove this if it drains battery. - styledPlayerView.setKeepScreenOn(true); - styledPlayerView.setPlayer(mExoPlayer); - styledPlayerView.setVisibility(View.VISIBLE); - - // Hide ImageView when the player is ready. - mExoPlayer.addListener(new Player.Listener() { - @Override - public void onPlaybackStateChanged(int playbackState) { - if (playbackState == Player.STATE_READY ) { - imageView.setVisibility(View.GONE); - } - } - }); - - // Step2: Set-up player control view - // Set-up video controls for accessibility mode - // Set Accessibility listeners and update the video controller visibility accordingly - AccessibilityManager accessibilityManager = - mContext.getSystemService(AccessibilityManager.class); - accessibilityManager.addAccessibilityStateChangeListener( - enabled -> updateControllerForAccessibilty(enabled, styledPlayerView)); - updateControllerForAccessibilty(accessibilityManager.isEnabled(), styledPlayerView); - - // Set-up video controls for non-accessibility mode - // Track if the controller layout should be visible for the next video. - styledPlayerView.setControllerVisibilityListener( - visibility -> mShouldShowControlsForNext = (visibility == View.VISIBLE)); - // Video controls will be visible if - // 1. this is the first video preview page or - // 2. the previous video had controls visible when the page was swiped or - // 3. the previous page was not a video preview - // or if we are in accessibility mode. - if (mShouldShowControlsForNext) { - styledPlayerView.showController(); - } - - // Player controls needs to be auto-hidden if they are shown - // 1. when the video starts previewing or - // 2. when the video starts playing from paused state. - // To achieve this, we hide the controller whenever player state changes to 'play' - mExoPlayer.addListener(new Player.Listener() { - @Override - public void onIsPlayingChanged(boolean isPlaying) { - if (mIsAccessibilityEnabled) { - // Player controls are always visible in accessibility mode. - return; - } - - // We don't have to hide controls if the state changed to PAUSED or controller - // isn't visible. - if (!isPlaying || !mShouldShowControlsForNext) return; - - // Set controller visibility of the next video to false so that we don't show the - // controls on the next video. - mShouldShowControlsForNext = false; - // Auto hide controller after 1s of player state changing to "Play". - styledPlayerView.postDelayed(() -> styledPlayerView.hideController(), - PLAYER_CONTROL_ON_PLAY_TIMEOUT_MS); - } - }); - - // Step3: Set-up mute button - final ImageButton muteButton = styledPlayerView.findViewById(R.id.preview_mute); - handleAudioFocusAndInitVolumeState(muteButton); - - // Add click listeners for mute button - muteButton.setOnClickListener(v -> { - mMuteStatus.setVolumeMuted(!mMuteStatus.isVolumeMuted()); - handleAudioFocusAndInitVolumeState(muteButton); - }); - - // Request or abandon audio focus on player state change. - mExoPlayer.addListener(new Player.Listener() { - @Override - public void onIsPlayingChanged(boolean isPlaying) { - if (isPlaying) { - handleAudioFocusAndInitVolumeState(muteButton); - } else { - abandonAudioFocusIfAny(); - } - } - }); - } - - /** - * Requests AudioFocus if current state of the volume state is volume on. Sets the volume of - * the playback if the AudioFocus request is granted. - * Also, updates the mute button based on the state of the muteStatus. - */ - private void handleAudioFocusAndInitVolumeState(ImageButton muteButton) { - if (mMuteStatus.isVolumeMuted()) { - mExoPlayer.setVolume(VOLUME_LEVEL_MUTE); - abandonAudioFocusIfAny(); - } else if (requestAudioFocus() == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - mExoPlayer.setVolume(getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC)); - } - - updateMuteButtonState(muteButton, mMuteStatus.isVolumeMuted()); - } - - /** - * Abandons the AudioFocus request so that the previous focus owner can resume their playback - */ - private void abandonAudioFocusIfAny() { - if (mAudioFocusRequest == null) return; - - getAudioManager().abandonAudioFocusRequest(mAudioFocusRequest); - mAudioFocusRequest = null; - } - - private void updateControllerForAccessibilty(boolean isEnabled, - StyledPlayerView styledPlayerView) { - mIsAccessibilityEnabled = isEnabled; - if (isEnabled) { - styledPlayerView.showController(); - styledPlayerView.setControllerHideOnTouch(false); - } else { - styledPlayerView.setControllerHideOnTouch(true); - } - } - - private void updateMuteButtonState(ImageButton muteButton, boolean isVolumeMuted) { - updateMuteButtonContentDescription(muteButton, isVolumeMuted); - updateMuteButtonIcon(muteButton, isVolumeMuted); - } - - private void updateMuteButtonContentDescription(ImageButton muteButton, boolean isVolumeMuted) { - muteButton.setContentDescription( - mContext.getString( - isVolumeMuted ? R.string.picker_unmute_video : R.string.picker_mute_video)); - } - - private void updateMuteButtonIcon(ImageButton muteButton, boolean isVolumeMuted) { - muteButton.setImageResource( - isVolumeMuted ? R.drawable.ic_volume_off : R.drawable.ic_volume_up); - } - - private AudioManager getAudioManager() { - return mContext.getSystemService(AudioManager.class); - } - - private int requestAudioFocus() { - // Always request new AudioFocus - abandonAudioFocusIfAny(); - - mAudioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT) - .setAudioAttributes(sAudioAttributes) - .setWillPauseWhenDucked(true) - .setAcceptsDelayedFocusGain(true) - .setOnAudioFocusChangeListener(focusChange -> { - if (focusChange == AudioManager.AUDIOFOCUS_LOSS - || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT - || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { - mExoPlayer.setPlayWhenReady(false); - } - }).build(); - - // We don't need to reset mAudioFocusRequest to null on failure of requestAudioFocus. This - // is because we always reset the AudioFocus before requesting, reset mechanism will also - // try to abandon AudioFocus if there is any. - return getAudioManager().requestAudioFocus(mAudioFocusRequest); - } - - private void releaseIfNecessary() { - // Release the player only when it's not already released. ExoPlayer doesn't crash if we try - // to release already released player, but ExoPlayer#release() may not be a no-op, hence we - // call release() only when it's not already released. - if (!mIsPlayerReleased) { - mExoPlayer.release(); - abandonAudioFocusIfAny(); - mIsPlayerReleased = true; - } - } -} diff --git a/src/com/android/providers/media/photopicker/ui/ImageLoader.java b/src/com/android/providers/media/photopicker/ui/ImageLoader.java index d62947127..69f5e3d03 100644 --- a/src/com/android/providers/media/photopicker/ui/ImageLoader.java +++ b/src/com/android/providers/media/photopicker/ui/ImageLoader.java @@ -17,6 +17,7 @@ package com.android.providers.media.photopicker.ui; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.ImageDecoder; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -26,12 +27,15 @@ import android.util.Log; import android.widget.ImageView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.providers.media.photopicker.data.model.Category; import com.android.providers.media.photopicker.data.model.Item; import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.load.Option; +import com.bumptech.glide.load.resource.gif.GifDrawable; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.signature.ObjectKey; @@ -44,6 +48,8 @@ public class ImageLoader { public static final Option<Boolean> THUMBNAIL_REQUEST = Option.memory(CloudMediaProviderContract.EXTRA_MEDIASTORE_THUMB, false); private static final String TAG = "ImageLoader"; + private static final RequestOptions THUMBNAIL_OPTION = + RequestOptions.option(THUMBNAIL_REQUEST, /* enableThumbnail */ true); private final Context mContext; public ImageLoader(Context context) { @@ -60,11 +66,8 @@ public class ImageLoader { // Always show all thumbnails as bitmap images instead of drawables // This is to ensure that we do not animate any thumbnail (for eg GIF) // TODO(b/194285082): Use drawable instead of bitmap, as it saves memory. - Glide.with(mContext) - .asBitmap() - .load(category.getCoverUri()) - .apply(RequestOptions.option(THUMBNAIL_REQUEST, true)) - .into(imageView); + loadWithGlide(getBitmapRequestBuilder(category.getCoverUri()), THUMBNAIL_OPTION, + /* signature */ null, imageView); } /** @@ -74,16 +77,11 @@ public class ImageLoader { * @param imageView the imageView shows the thumbnail */ public void loadPhotoThumbnail(@NonNull Item item, @NonNull ImageView imageView) { - Uri uri = item.getContentUri(); // Always show all thumbnails as bitmap images instead of drawables // This is to ensure that we do not animate any thumbnail (for eg GIF) // TODO(b/194285082): Use drawable instead of bitmap, as it saves memory. - Glide.with(mContext) - .asBitmap() - .load(uri) - .signature(getGlideSignature(item, /* prefix */ "")) - .apply(RequestOptions.option(THUMBNAIL_REQUEST, true)) - .into(imageView); + loadWithGlide(getBitmapRequestBuilder(item.getContentUri()), THUMBNAIL_OPTION, + getGlideSignature(item, /* prefix */ ""), imageView); } /** @@ -94,11 +92,8 @@ public class ImageLoader { */ public void loadImagePreview(@NonNull Item item, @NonNull ImageView imageView) { if (item.isGif()) { - Glide.with(mContext) - .asGif() - .load(item.getContentUri()) - .signature(getGlideSignature(item, /* prefix */ "")) - .into(imageView); + loadWithGlide(getGifRequestBuilder(item.getContentUri()), /* requestOptions */ null, + getGlideSignature(item, /* prefix */ ""), imageView); return; } @@ -108,11 +103,8 @@ public class ImageLoader { } // Preview as bitmap image for all other image types - Glide.with(mContext) - .asBitmap() - .load(item.getContentUri()) - .signature(getGlideSignature(item, /* prefix */ "")) - .into(imageView); + loadWithGlide(getBitmapRequestBuilder(item.getContentUri()), /* requestOptions */ null, + getGlideSignature(item, /* prefix */ ""), imageView); } private void loadAnimatedWebpPreview(@NonNull Item item, @NonNull ImageView imageView) { @@ -129,22 +121,16 @@ public class ImageLoader { // If we failed to decode drawable for a source using ImageDecoder, then try using uri // directly. Glide will show static image for an animated webp. That is okay as we tried our // best to load animated webp but couldn't, and we anyway show the GIF badge in preview. - Glide.with(mContext) - .load(drawable == null ? uri : drawable) - .signature(getGlideSignature(item, /* prefix */ "")) - .into(imageView); + loadWithGlide(getDrawableRequestBuilder(drawable == null ? uri : drawable), + /* requestOptions */ null, getGlideSignature(item, /* prefix */ ""), imageView); } /** * Loads the image from first frame of the given video item */ public void loadImageFromVideoForPreview(@NonNull Item item, @NonNull ImageView imageView) { - Glide.with(mContext) - .asBitmap() - .load(item.getContentUri()) - .apply(new RequestOptions().frame(1000)) - .signature(getGlideSignature(item, "Preview")) - .into(imageView); + loadWithGlide(getBitmapRequestBuilder(item.getContentUri()), + new RequestOptions().frame(1000), getGlideSignature(item, "Preview"), imageView); } private ObjectKey getGlideSignature(Item item, String prefix) { @@ -153,4 +139,37 @@ public class ImageLoader { MediaStore.getVersion(mContext) + prefix + item.getContentUri().toString() + item.getGenerationModified()); } + + private RequestBuilder<Bitmap> getBitmapRequestBuilder(Uri uri) { + return Glide.with(mContext) + .asBitmap() + .load(uri); + } + + private RequestBuilder<GifDrawable> getGifRequestBuilder(Uri uri) { + return Glide.with(mContext) + .asGif() + .load(uri); + } + + private RequestBuilder<Drawable> getDrawableRequestBuilder(Object model) { + return Glide.with(mContext) + .load(model); + } + + private <T> void loadWithGlide(RequestBuilder<T> requestBuilder, + @Nullable RequestOptions requestOptions, @Nullable ObjectKey signature, + ImageView imageView) { + RequestBuilder<T> newRequestBuilder = requestBuilder.clone(); + + if (requestOptions != null) { + newRequestBuilder = newRequestBuilder.apply(requestOptions); + } + + if (signature != null) { + newRequestBuilder = newRequestBuilder.signature(signature); + } + + newRequestBuilder.into(imageView); + } } diff --git a/src/com/android/providers/media/photopicker/ui/PlaybackHandler.java b/src/com/android/providers/media/photopicker/ui/PlaybackHandler.java deleted file mode 100644 index 7d31af9cc..000000000 --- a/src/com/android/providers/media/photopicker/ui/PlaybackHandler.java +++ /dev/null @@ -1,124 +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.ui; - -import android.content.Context; -import android.net.Uri; -import android.os.Looper; -import android.util.Log; -import android.view.View; -import android.widget.ImageView; - -import com.android.providers.media.R; -import com.android.providers.media.photopicker.data.MuteStatus; -import com.android.providers.media.photopicker.data.model.Item; - -import com.google.android.exoplayer2.ui.StyledPlayerView; - -/** - * A class to handle page selected state to initiate video playback or release video player - * resources. All the public methods of this class must be called from main thread as ExoPlayer - * should be prepared/released from main thread. - */ -class PlaybackHandler { - private static final String TAG = "PlaybackHandler"; - // Only main thread can call the methods in this class, hence we don't need to guard mVideoUri - // with lock while reading or writing to it. - private Uri mVideoUri = null; - private final ExoPlayerWrapper mExoPlayerWrapper; - - PlaybackHandler(Context context, MuteStatus muteStatus) { - mExoPlayerWrapper = new ExoPlayerWrapper(context, muteStatus); - } - - /** - * Handles video playback for the {@link ViewPager2} page when it's selected i.e., completely - * visible. - * <ul> - * <li> If the selected page is a video page, prepare and play the video associated with - * selected page - * <li> If the selected page is a video page and the same video is already playing, then no - * action will be taken. - * <li> If the selected page is non-video page, try releasing the ExoPlayer associated with - * previous page that was selected. - * </ul> - * @param view {@link RecyclerView.ViewHolder#itemView} of the selected page. - */ - public void handleVideoPlayback(View view) { - assertMainThread(); - - final Object tag = view.getTag(); - if (!(tag instanceof Item)) { - throw new IllegalStateException("Expected Item tag to be set to " + view); - } - - final Item item = (Item) tag; - if (!item.isVideo()) { - // We only need to handle video playback. For everything else, try releasing ExoPlayer - // if there is a prepared ExoPlayer of the previous page, also reset any player states - // when necessary. - mExoPlayerWrapper.resetPlayerIfNecessary(); - mVideoUri = null; - return; - } - - final Uri videoUri = item.getContentUri(); - if (mVideoUri != null && mVideoUri.equals(videoUri)) { - // Selected video is already handled. This must be a slight drag and drop, and we don't - // have to change state of the player. - Log.d(TAG, "Ignoring handlePageSelected of already selected page, with uri " - + videoUri); - return; - } - - final StyledPlayerView styledPlayerView = view.findViewById(R.id.preview_player_view); - if (styledPlayerView == null) { - throw new IllegalStateException("Expected to find StyledPlayerView in " + view); - } - final ImageView imageView = view.findViewById(R.id.preview_video_image); - - mVideoUri = videoUri; - mExoPlayerWrapper.prepareAndPlay(styledPlayerView, imageView, mVideoUri); - } - - public void onViewAttachedToWindow(View itemView) { - final ImageView imageView = itemView.findViewById(R.id.preview_video_image); - imageView.setVisibility(View.VISIBLE); - final StyledPlayerView styledPlayerView = itemView.findViewById(R.id.preview_player_view); - styledPlayerView.setVisibility(View.GONE); - styledPlayerView.setControllerVisibilityListener(null); - styledPlayerView.hideController(); - } - - /** - * Releases ExoPlayer if there is any. Also resets the saved video uri. - */ - public void releaseResources() { - assertMainThread(); - - mVideoUri = null; - mExoPlayerWrapper.resetPlayerIfNecessary(); - } - - private void assertMainThread() { - if (Looper.getMainLooper().isCurrentThread()) return; - - throw new IllegalStateException("PlaybackHandler methods are expected to be called from" - + " main thread. Current thread " + Looper.myLooper().getThread() - + ", Main thread" + Looper.getMainLooper().getThread()); - } -} diff --git a/src/com/android/providers/media/photopicker/ui/PreviewAdapter.java b/src/com/android/providers/media/photopicker/ui/PreviewAdapter.java index 8ce73117e..1df48777f 100644 --- a/src/com/android/providers/media/photopicker/ui/PreviewAdapter.java +++ b/src/com/android/providers/media/photopicker/ui/PreviewAdapter.java @@ -41,13 +41,10 @@ class PreviewAdapter extends RecyclerView.Adapter<BaseViewHolder> { private List<Item> mItemList = new ArrayList<>(); private final ImageLoader mImageLoader; private final RemotePreviewHandler mRemotePreviewHandler; - private final PlaybackHandler mPlaybackHandler; - private final boolean mIsRemotePreviewEnabled = RemotePreviewHandler.isRemotePreviewEnabled(); PreviewAdapter(Context context, MuteStatus muteStatus) { mImageLoader = new ImageLoader(context); mRemotePreviewHandler = new RemotePreviewHandler(context, muteStatus); - mPlaybackHandler = new PlaybackHandler(context, muteStatus); } @NonNull @@ -55,10 +52,8 @@ class PreviewAdapter extends RecyclerView.Adapter<BaseViewHolder> { public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { if (viewType == ITEM_TYPE_IMAGE) { return new PreviewImageHolder(viewGroup.getContext(), viewGroup, mImageLoader); - } else { - return new PreviewVideoHolder(viewGroup.getContext(), viewGroup, mImageLoader, - mIsRemotePreviewEnabled); } + return new PreviewVideoHolder(viewGroup.getContext(), viewGroup, mImageLoader); } @Override @@ -76,17 +71,8 @@ class PreviewAdapter extends RecyclerView.Adapter<BaseViewHolder> { final Item item = (Item) holder.itemView.getTag(); if (item.isVideo()) { - // TODO(b/222506900): Refactor thumbnail show / hide logic to be handled from a single - // place. Currently, we show the thumbnail here and hide it when playback starts in - // PlaybackHandler/RemotePreviewHandler. PreviewVideoHolder videoHolder = (PreviewVideoHolder) holder; - - if (mIsRemotePreviewEnabled) { - mRemotePreviewHandler.onViewAttachedToWindow(videoHolder, item); - return; - } - - mPlaybackHandler.onViewAttachedToWindow(holder.itemView); + mRemotePreviewHandler.onViewAttachedToWindow(videoHolder, item); } } @@ -106,28 +92,16 @@ class PreviewAdapter extends RecyclerView.Adapter<BaseViewHolder> { } void onHandlePageSelected(View itemView) { - if (mIsRemotePreviewEnabled) { - final Item item = (Item) itemView.getTag(); - mRemotePreviewHandler.onHandlePageSelected(item); - return; - } - - mPlaybackHandler.handleVideoPlayback(itemView); + final Item item = (Item) itemView.getTag(); + mRemotePreviewHandler.onHandlePageSelected(item); } void onStop() { - if (mIsRemotePreviewEnabled) { - mRemotePreviewHandler.onStop(); - return; - } - - mPlaybackHandler.releaseResources(); + mRemotePreviewHandler.onStop(); } void onDestroy() { - if (mIsRemotePreviewEnabled) { - mRemotePreviewHandler.onDestroy(); - } + mRemotePreviewHandler.onDestroy(); } Item getItem(int position) { diff --git a/src/com/android/providers/media/photopicker/ui/PreviewVideoHolder.java b/src/com/android/providers/media/photopicker/ui/PreviewVideoHolder.java index dcb169655..e8287670c 100644 --- a/src/com/android/providers/media/photopicker/ui/PreviewVideoHolder.java +++ b/src/com/android/providers/media/photopicker/ui/PreviewVideoHolder.java @@ -45,25 +45,17 @@ public class PreviewVideoHolder extends BaseViewHolder { private final ImageButton mPlayPauseButton; private final ImageButton mMuteButton; - PreviewVideoHolder(Context context, ViewGroup parent, ImageLoader imageLoader, - boolean enabledCloudMediaPreview) { - super(context, parent, enabledCloudMediaPreview ? R.layout.item_cloud_video_preview - : R.layout.item_video_preview); + PreviewVideoHolder(Context context, ViewGroup parent, ImageLoader imageLoader) { + super(context, parent, R.layout.item_video_preview); mImageLoader = imageLoader; mImageView = itemView.findViewById(R.id.preview_video_image); - mSurfaceView = enabledCloudMediaPreview ? itemView.findViewById(R.id.preview_player_view) - : null; - mPlayerFrame = enabledCloudMediaPreview ? - itemView.findViewById(R.id.preview_player_frame) : null; - mPlayerContainer = enabledCloudMediaPreview ? - itemView.findViewById(R.id.preview_player_container) : null; - mPlayerControlsRoot = enabledCloudMediaPreview ? itemView.findViewById( - R.id.preview_player_controls) : null; - mPlayPauseButton = enabledCloudMediaPreview ? itemView.findViewById( - R.id.exo_play_pause) : null; - mMuteButton = enabledCloudMediaPreview ? itemView.findViewById( - R.id.preview_mute) : null; + mSurfaceView = itemView.findViewById(R.id.preview_player_view); + mPlayerFrame = itemView.findViewById(R.id.preview_player_frame); + mPlayerContainer = itemView.findViewById(R.id.preview_player_container); + mPlayerControlsRoot = itemView.findViewById(R.id.preview_player_controls); + mPlayPauseButton = itemView.findViewById(R.id.exo_play_pause); + mMuteButton = itemView.findViewById(R.id.preview_mute); } @Override diff --git a/src/com/android/providers/media/photopicker/util/MimeFilterUtils.java b/src/com/android/providers/media/photopicker/util/MimeFilterUtils.java index b028a00a2..7872343a4 100644 --- a/src/com/android/providers/media/photopicker/util/MimeFilterUtils.java +++ b/src/com/android/providers/media/photopicker/util/MimeFilterUtils.java @@ -57,24 +57,31 @@ public class MimeFilterUtils { /** * Extracts relevant mime type filter for the given intent */ - public static String getMimeTypeFilter(Intent intent) { + public static String[] getMimeTypeFilters(Intent intent) throws IllegalArgumentException { // EXTRA_MIME_TYPES has higher priority over getType() filter. if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { final String[] extraMimeTypes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); - if (extraMimeTypes.length == 1) { - // We only support 1 mime type filter - // TODO(b/224756380): Add support for multiple mime type filters - return extraMimeTypes[0]; + if (requiresUnsupportedFilters(extraMimeTypes)) { + if (Intent.ACTION_GET_CONTENT.equals(intent.getAction())) { + // This is a special case in which PhotoPicker is explicitly opened from + // DocumentsUI as it is seen as one of the options. In this show all images + // and videos. + // If this was not a special case, then the picker would close itself and + // redirect the request to DocumentsUI before hitting this point. + return null; + } + + throw new IllegalArgumentException("Invalid EXTRA_MIME_TYPES value, only media " + + "mime type filters are accepted"); } - // Show all images/videos for multiple or empty mime type filters - return null; + return extraMimeTypes; } final String mimeType = intent.getType(); if (MimeFilterUtils.isMimeTypeMedia(mimeType)) { - return mimeType; + return new String[] { mimeType }; } return null; @@ -86,13 +93,8 @@ public class MimeFilterUtils { return true; } - // TODO(b/224756380): Add support for multiple mime type filters - if (mimeTypeFilters.length > 1) { - return true; - } - for (String mimeTypeFilter : mimeTypeFilters) { - if (!MimeFilterUtils.isMimeTypeMedia(mimeTypeFilter)) { + if (!isMimeTypeMedia(mimeTypeFilter)) { return true; } } diff --git a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java index 00d8fd073..5fe5effc5 100644 --- a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java +++ b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java @@ -76,7 +76,7 @@ public class PickerViewModel extends AndroidViewModel { private InstanceId mInstanceId; private PhotoPickerUiEventLogger mLogger; - private String mMimeTypeFilter = null; + private String[] mMimeTypeFilters = null; private int mBottomSheetState; private Category mCurrentCategory; @@ -151,7 +151,7 @@ public class PickerViewModel extends AndroidViewModel { final List<Item> items = new ArrayList<>(); try (Cursor cursor = mItemsProvider.getItems(category, /* offset */ 0, - /* limit */ -1, mMimeTypeFilter, userId)) { + /* limit */ -1, mMimeTypeFilters, userId)) { if (cursor == null || cursor.getCount() == 0) { Log.d(TAG, "Didn't receive any items for " + category + ", either cursor is null or cursor count is zero"); @@ -268,7 +268,7 @@ public class PickerViewModel extends AndroidViewModel { private List<Category> loadCategories(UserId userId) { final List<Category> categoryList = new ArrayList<>(); - try (final Cursor cursor = mItemsProvider.getCategories(mMimeTypeFilter, userId)) { + try (final Cursor cursor = mItemsProvider.getCategories(mMimeTypeFilters, userId)) { if (cursor == null || cursor.getCount() == 0) { Log.d(TAG, "Didn't receive any categories, either cursor is null or" + " cursor count is zero"); @@ -304,10 +304,10 @@ public class PickerViewModel extends AndroidViewModel { } /** - * Return whether the {@link #mMimeTypeFilter} is {@code null} or not + * Return whether the {@link #mMimeTypeFilters} is {@code null} or not */ - public boolean hasMimeTypeFilter() { - return !TextUtils.isEmpty(mMimeTypeFilter); + public boolean hasMimeTypeFilters() { + return mMimeTypeFilters != null && mMimeTypeFilters.length > 0; } /** @@ -316,7 +316,7 @@ public class PickerViewModel extends AndroidViewModel { public void parseValuesFromIntent(Intent intent) throws IllegalArgumentException { mUserIdManager.setIntentAndCheckRestrictions(intent); - mMimeTypeFilter = MimeFilterUtils.getMimeTypeFilter(intent); + mMimeTypeFilters = MimeFilterUtils.getMimeTypeFilters(intent); mSelection.parseSelectionValuesFromIntent(intent); } @@ -350,6 +350,13 @@ public class PickerViewModel extends AndroidViewModel { } } + /** + * Log metrics to notify that the user has clicked Browse to open DocumentsUi + */ + public void logBrowseToDocumentsUi(int callingUid, String callingPackage) { + mLogger.logBrowseToDocumentsUi(mInstanceId, callingUid, callingPackage); + } + public InstanceId getInstanceId() { return mInstanceId; } diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java index 41f53d57a..58759e206 100644 --- a/src/com/android/providers/media/scan/ModernMediaScanner.java +++ b/src/com/android/providers/media/scan/ModernMediaScanner.java @@ -1510,9 +1510,13 @@ public class ModernMediaScanner implements MediaScanner { @VisibleForTesting static @NonNull Optional<Integer> parseOptionalOrientation(int orientation) { switch (orientation) { + case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: case ExifInterface.ORIENTATION_NORMAL: return Optional.of(0); + case ExifInterface.ORIENTATION_TRANSPOSE: case ExifInterface.ORIENTATION_ROTATE_90: return Optional.of(90); + case ExifInterface.ORIENTATION_FLIP_VERTICAL: case ExifInterface.ORIENTATION_ROTATE_180: return Optional.of(180); + case ExifInterface.ORIENTATION_TRANSVERSE: case ExifInterface.ORIENTATION_ROTATE_270: return Optional.of(270); default: return Optional.empty(); } diff --git a/src/com/android/providers/media/util/DatabaseUtils.java b/src/com/android/providers/media/util/DatabaseUtils.java index 55efafc7f..cec6f27f1 100644 --- a/src/com/android/providers/media/util/DatabaseUtils.java +++ b/src/com/android/providers/media/util/DatabaseUtils.java @@ -535,8 +535,14 @@ public class DatabaseUtils { return sb.toString(); } - public static String replaceMatchAnyChar(@NonNull String arg) { - return arg.replace('*', '%'); + public static String[] replaceMatchAnyChar(@NonNull String[] arg) { + String[] result = arg.clone(); + for (int i = 0; i < arg.length; i++) { + if (result[i] != null) { + result[i] = result[i].replace('*', '%'); + } + } + return result; } public static boolean parseBoolean(@Nullable Object value, boolean def) { diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java index ee421c97b..de789388f 100644 --- a/src/com/android/providers/media/util/FileUtils.java +++ b/src/com/android/providers/media/util/FileUtils.java @@ -49,8 +49,8 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.os.UserHandle; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.provider.MediaStore; @@ -973,12 +973,13 @@ public class FileUtils { "(?i)^Android/(?:data|media|obb)/([^/]+)(/?.*)?"); /** - * Regex that matches Android/obb or Android/data path. + * Regex that matches exactly Android/obb or Android/data or Android/obb/ or Android/data/ + * suffix absolute file path. */ private static final Pattern PATTERN_DATA_OR_OBB_PATH = Pattern.compile( "(?i)^/storage/[^/]+/(?:[0-9]+/)?" + PROP_CROSS_USER_ROOT_PATTERN - + "Android/(?:data|obb)(?:/.*)?$"); + + "Android/(?:data|obb)/?$"); /** * Regex that matches Android/obb or Android/data relative path (as defined in @@ -1399,9 +1400,18 @@ public class FileUtils { resolvedDisplayName = displayName; } - final File filePath = buildPath(volumePath, - values.getAsString(MediaColumns.RELATIVE_PATH), resolvedDisplayName); - values.put(MediaColumns.DATA, filePath.getAbsolutePath()); + String relativePath = values.getAsString(MediaColumns.RELATIVE_PATH); + if (relativePath == null) { + relativePath = ""; + } + try { + final File filePath = buildPath(volumePath, relativePath, resolvedDisplayName); + values.put(MediaColumns.DATA, filePath.getCanonicalPath()); + } catch (IOException e) { + throw new IllegalArgumentException( + String.format("Failure in conversion to canonical file path. Failure path: %s.", + relativePath.concat(resolvedDisplayName)), e); + } } public static void sanitizeValues(@NonNull ContentValues values, @@ -1625,18 +1635,28 @@ public class FileUtils { Log.i(TAG, "Clearing cache for all apps"); final File rootDataDir = buildPath(Environment.getExternalStorageDirectory(), "Android", "data"); - for (File appDataDir : rootDataDir.listFiles()) { - try { - final File appCacheDir = new File(appDataDir, "cache"); - if (appCacheDir.isDirectory()) { - FileUtils.deleteContents(appCacheDir); + File[] appDataDirs = rootDataDir.listFiles(); + if (appDataDirs == null) { + // Couldn't delete any app cache dirs because the call to list files in root data dir + // failed (b/234521806). It is not clear why this call would fail because root data + // dir path should be well-formed. + Log.e(TAG, String.format("Couldn't delete any app cache dirs in root data dir %s !", + rootDataDir.getAbsolutePath())); + status = OsConstants.EIO; + } else { + for (File appDataDir : appDataDirs) { + try { + final File appCacheDir = new File(appDataDir, "cache"); + if (appCacheDir.isDirectory()) { + FileUtils.deleteContents(appCacheDir); + } + } catch (Exception e) { + // We want to avoid crashing MediaProvider at all costs, so we handle all + // "generic" exceptions here, and just report to the caller that an IO exception + // has occurred. We still try to clear the rest of the directories. + Log.e(TAG, "Couldn't delete all app cache dirs!", e); + status = OsConstants.EIO; } - } catch (Exception e) { - // We want to avoid crashing MediaProvider at all costs, so we handle all "generic" - // exceptions here, and just report to the caller that an IO exception has occurred. - // We still try to clear the rest of the directories. - Log.e(TAG, "Couldn't delete all app cache dirs!", e); - status = OsConstants.EIO; } } return status; diff --git a/tests/Android.bp b/tests/Android.bp index 05aa55604..17f90dbf0 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -158,7 +158,7 @@ android_test { "androidx.core_core", "androidx.test.rules", "guava", - "mockito-target-extended-minus-junit4", + "mockito-target", "modules-utils-build", "truth-prebuilt", "com.google.android.material_material", @@ -177,12 +177,6 @@ android_test { "exoplayer-mediaprovider-ui", ], - // these are needed for Extended Mockito - jni_libs: [ - "libdexmakerjvmtiagent", - "libstaticjvmtiagent", - ], - certificate: "media", aaptflags: ["--custom-package com.android.providers.media"], diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index fd9486312..15c2be4c4 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -9,10 +9,7 @@ <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> - <!--android:debuggable and android:largeHeap attributes are needed for Extended Mockito--> - <application android:label="MediaProvider Tests" - android:debuggable="true" - android:largeHeap="true"> + <application android:label="MediaProvider Tests"> <uses-library android:name="android.test.runner" /> <activity android:name="com.android.providers.media.GetResultActivity" /> diff --git a/tests/src/com/android/providers/media/IdleServiceTest.java b/tests/src/com/android/providers/media/IdleServiceTest.java index 80d2261fc..39f7e6e5d 100644 --- a/tests/src/com/android/providers/media/IdleServiceTest.java +++ b/tests/src/com/android/providers/media/IdleServiceTest.java @@ -121,6 +121,7 @@ public class IdleServiceTest { final File b = touch(buildPath(dir, DIRECTORY_MOVIES, ".thumbnails", "7654321.jpg")); final File c = touch(buildPath(dir, DIRECTORY_PICTURES, ".thumbnails", id + ".jpg")); final File d = touch(buildPath(dir, DIRECTORY_PICTURES, ".thumbnails", "random.bin")); + final File e = touch(buildPath(dir, DIRECTORY_PICTURES, ".thumbnails", ".nomedia")); // Idle maintenance pass should clean up unknown files MediaStore.runIdleMaintenance(resolver); @@ -128,6 +129,7 @@ public class IdleServiceTest { assertFalse(exists(b)); assertTrue(exists(c)); assertFalse(exists(d)); + assertTrue(exists(e)); // And change the UUID, which emulates ejecting and mounting a different // storage device; all thumbnails should then be invalidated @@ -136,12 +138,13 @@ public class IdleServiceTest { delete(uuidFile); touch(uuidFile); - // Idle maintenance pass should clean up all files + // Idle maintenance pass should clean up all files except .nomedia file MediaStore.runIdleMaintenance(resolver); assertFalse(exists(a)); assertFalse(exists(b)); assertFalse(exists(c)); assertFalse(exists(d)); + assertTrue(exists(e)); } /** diff --git a/tests/src/com/android/providers/media/MediaProviderTest.java b/tests/src/com/android/providers/media/MediaProviderTest.java index a04f57b97..ff56ee894 100644 --- a/tests/src/com/android/providers/media/MediaProviderTest.java +++ b/tests/src/com/android/providers/media/MediaProviderTest.java @@ -78,6 +78,7 @@ import com.android.providers.media.util.FileUtilsTest; import com.android.providers.media.util.SQLiteQueryBuilder; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Ignore; @@ -375,6 +376,42 @@ public class MediaProviderTest { } } + @Test + public void testInsertionWithInvalidFilePath_throwsIllegalArgumentException() { + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, "Android/media/com.example"); + values.put(MediaStore.Images.Media.DISPLAY_NAME, + "./../../../../../../../../../../../data/media/test.txt"); + + IllegalArgumentException illegalArgumentException = Assert.assertThrows( + IllegalArgumentException.class, () -> sIsolatedResolver.insert( + MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), + values)); + + assertThat(illegalArgumentException).hasMessageThat().contains( + "Primary directory Android not allowed for content://media/external_primary/file;" + + " allowed directories are [Download, Documents]"); + } + + @Test + public void testUpdationWithInvalidFilePath_throwsIllegalArgumentException() { + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, "Download"); + values.put(MediaStore.Images.Media.DISPLAY_NAME, "test.txt"); + Uri uri = sIsolatedResolver.insert( + MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), + values); + + final ContentValues newValues = new ContentValues(); + newValues.put(MediaStore.MediaColumns.DATA, "/storage/emulated/0/../../../data/media/"); + IllegalArgumentException illegalArgumentException = Assert.assertThrows( + IllegalArgumentException.class, + () -> sIsolatedResolver.update(uri, newValues, null)); + + assertThat(illegalArgumentException).hasMessageThat().contains( + "Requested path /data/media doesn't appear under [/storage/emulated/0]"); + } + /** * We already have solid coverage of this logic in * {@code CtsProviderTestCases}, but the coverage system currently doesn't @@ -642,6 +679,11 @@ public class MediaProviderTest { public int getCallingPackageTargetSdkVersion() { return Build.VERSION_CODES.Q; } + + @Override + protected void checkDeviceConfigAndUpdateGetContentAlias() { + // Ignore this as test app cannot read device config + } }; final ProviderInfo info = sIsolatedContext.getPackageManager() @@ -1069,6 +1111,11 @@ public class MediaProviderTest { public int getCallingPackageTargetSdkVersion() { return Build.VERSION_CODES.CUR_DEVELOPMENT; } + + @Override + protected void checkDeviceConfigAndUpdateGetContentAlias() { + // Ignore this as test app cannot read device config + } }; final ProviderInfo info = sIsolatedContext.getPackageManager() .resolveContentProvider(MediaStore.AUTHORITY, PackageManager.GET_META_DATA); diff --git a/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java b/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java index 2f90d2fa9..adeaa3b33 100644 --- a/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java +++ b/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java @@ -23,6 +23,7 @@ import static android.provider.CloudMediaProviderContract.EXTRA_SYNC_GENERATION; import static android.provider.CloudMediaProviderContract.MediaCollectionInfo; import static android.provider.CloudMediaProviderContract.MediaColumns; import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.LONG_DEFAULT; +import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.STRING_ARRAY_DEFAULT; import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.STRING_DEFAULT; import android.content.ContentResolver; @@ -34,7 +35,6 @@ import android.os.SystemClock; import android.provider.CloudMediaProvider; import android.provider.CloudMediaProviderContract; import android.text.TextUtils; -import android.util.Log; import com.android.providers.media.photopicker.LocalProvider; @@ -96,8 +96,9 @@ public class PickerProviderMediaGenerator { private Bundle mCursorExtra; // TODO(b/214592293): Add pagination support for testing purposes. - public Cursor getMedia(long generation, String albumId, String mimeType, long sizeBytes) { - final Cursor cursor = getCursor(mMedia, generation, albumId, mimeType, sizeBytes, + public Cursor getMedia(long generation, String albumId, String[] mimeTypes, + long sizeBytes) { + final Cursor cursor = getCursor(mMedia, generation, albumId, mimeTypes, sizeBytes, /* isDeleted */ false); if (mCursorExtra != null) { @@ -112,8 +113,8 @@ public class PickerProviderMediaGenerator { return cursor; } - public Cursor getAlbums(String mimeType, long sizeBytes, boolean isLocal) { - final Cursor cursor = getCursor(mAlbums, mimeType, sizeBytes, isLocal); + public Cursor getAlbums(String[] mimeTypes, long sizeBytes, boolean isLocal) { + final Cursor cursor = getCursor(mAlbums, mimeTypes, sizeBytes, isLocal); if (mCursorExtra != null) { cursor.setExtras(mCursorExtra); @@ -130,7 +131,7 @@ public class PickerProviderMediaGenerator { // TODO(b/214592293): Add pagination support for testing purposes. public Cursor getDeletedMedia(long generation) { final Cursor cursor = getCursor(mDeletedMedia, generation, /* albumId */ STRING_DEFAULT, - /* mimeType */ STRING_DEFAULT, /* sizeBytes */ LONG_DEFAULT, + STRING_ARRAY_DEFAULT, /* sizeBytes */ LONG_DEFAULT, /* isDeleted */ true); if (mCursorExtra != null) { @@ -260,7 +261,7 @@ public class PickerProviderMediaGenerator { } private static Cursor getCursor(List<TestMedia> mediaList, long generation, - String albumId, String mimeType, long sizeBytes, boolean isDeleted) { + String albumId, String[] mimeTypes, long sizeBytes, boolean isDeleted) { final MatrixCursor matrix; if (isDeleted) { matrix = new MatrixCursor(DELETED_MEDIA_PROJECTION); @@ -272,22 +273,22 @@ public class PickerProviderMediaGenerator { for (TestMedia media : mediaList) { if (!TextUtils.isEmpty(albumId) && matchesFilter(media, - albumId, mimeType, sizeBytes)) { + albumId, mimeTypes, sizeBytes)) { matrix.addRow(media.toAlbumMediaArray()); } else if (media.generation > generation - && matchesFilter(media, albumId, mimeType, sizeBytes)) { + && matchesFilter(media, albumId, mimeTypes, sizeBytes)) { matrix.addRow(media.toArray(isDeleted)); } } return matrix; } - private static Cursor getCursor(List<TestAlbum> albumList, String mimeType, long sizeBytes, - boolean isLocal) { + private static Cursor getCursor(List<TestAlbum> albumList, String[] mimeTypes, + long sizeBytes, boolean isLocal) { final MatrixCursor matrix = new MatrixCursor(ALBUM_PROJECTION); for (TestAlbum album : albumList) { - final String[] res = album.toArray(mimeType, sizeBytes, isLocal); + final String[] res = album.toArray(mimeTypes, sizeBytes, isLocal); if (res != null) { matrix.addRow(res); } @@ -398,13 +399,13 @@ public class PickerProviderMediaGenerator { this.media = media; } - public String[] toArray(String mimeType, long sizeBytes, boolean isLocal) { + public String[] toArray(String[] mimeTypes, long sizeBytes, boolean isLocal) { long mediaCount = 0; String mediaCoverId = null; long dateTakenMs = 0; for (TestMedia m : media) { - if (matchesFilter(m, id, mimeType, sizeBytes)) { + if (matchesFilter(m, id, mimeTypes, sizeBytes)) { if (mediaCount++ == 0) { mediaCoverId = m.getId(); dateTakenMs = m.dateTakenMs; @@ -442,14 +443,26 @@ public class PickerProviderMediaGenerator { } } - private static boolean matchesFilter(TestMedia media, String albumId, String mimeType, + private static boolean matchesFilter(TestMedia media, String albumId, String[] mimeTypes, long sizeBytes) { if (!Objects.equals(albumId, STRING_DEFAULT) && !Objects.equals(albumId, media.albumId)) { return false; } - if (!Objects.equals(mimeType, STRING_DEFAULT) && !media.mimeType.startsWith(mimeType)) { - return false; + + if (mimeTypes != null) { + boolean matchesMimeType = false; + for (String m : mimeTypes) { + if (m != null && media.mimeType.startsWith(m)) { + matchesMimeType = true; + break; + } + } + + if (!matchesMimeType) { + return false; + } } + if (sizeBytes != LONG_DEFAULT && media.sizeBytes > sizeBytes) { return false; } diff --git a/tests/src/com/android/providers/media/cloudproviders/CloudProviderPrimary.java b/tests/src/com/android/providers/media/cloudproviders/CloudProviderPrimary.java index a007139ad..d56328e0c 100644 --- a/tests/src/com/android/providers/media/cloudproviders/CloudProviderPrimary.java +++ b/tests/src/com/android/providers/media/cloudproviders/CloudProviderPrimary.java @@ -54,7 +54,7 @@ public class CloudProviderPrimary extends CloudMediaProvider { CloudProviderQueryExtras.fromCloudMediaBundle(extras); return mMediaGenerator.getMedia(queryExtras.getGeneration(), queryExtras.getAlbumId(), - queryExtras.getMimeType(), queryExtras.getSizeBytes()); + queryExtras.getMimeTypes(), queryExtras.getSizeBytes()); } @Override @@ -70,7 +70,7 @@ public class CloudProviderPrimary extends CloudMediaProvider { final CloudProviderQueryExtras queryExtras = CloudProviderQueryExtras.fromCloudMediaBundle(extras); - return mMediaGenerator.getAlbums(queryExtras.getMimeType(), queryExtras.getSizeBytes(), + return mMediaGenerator.getAlbums(queryExtras.getMimeTypes(), queryExtras.getSizeBytes(), /* isLocal */ false); } diff --git a/tests/src/com/android/providers/media/cloudproviders/CloudProviderSecondary.java b/tests/src/com/android/providers/media/cloudproviders/CloudProviderSecondary.java index abcb92f9e..a00cbafc3 100644 --- a/tests/src/com/android/providers/media/cloudproviders/CloudProviderSecondary.java +++ b/tests/src/com/android/providers/media/cloudproviders/CloudProviderSecondary.java @@ -16,7 +16,6 @@ package com.android.providers.media.cloudproviders; -import static android.provider.CloudMediaProviderContract.MediaCollectionInfo; import static com.android.providers.media.PickerProviderMediaGenerator.MediaGenerator; import android.content.res.AssetFileDescriptor; @@ -54,7 +53,7 @@ public class CloudProviderSecondary extends CloudMediaProvider { CloudProviderQueryExtras.fromCloudMediaBundle(extras); return mMediaGenerator.getMedia(queryExtras.getGeneration(), queryExtras.getAlbumId(), - queryExtras.getMimeType(), queryExtras.getSizeBytes()); + queryExtras.getMimeTypes(), queryExtras.getSizeBytes()); } @Override @@ -70,7 +69,7 @@ public class CloudProviderSecondary extends CloudMediaProvider { final CloudProviderQueryExtras queryExtras = CloudProviderQueryExtras.fromCloudMediaBundle(extras); - return mMediaGenerator.getAlbums(queryExtras.getMimeType(), queryExtras.getSizeBytes(), + return mMediaGenerator.getAlbums(queryExtras.getMimeTypes(), queryExtras.getSizeBytes(), /* isLocal */ false); } diff --git a/tests/src/com/android/providers/media/photopicker/ItemsProviderTest.java b/tests/src/com/android/providers/media/photopicker/ItemsProviderTest.java index 7608c928b..dbddc3eb1 100644 --- a/tests/src/com/android/providers/media/photopicker/ItemsProviderTest.java +++ b/tests/src/com/android/providers/media/photopicker/ItemsProviderTest.java @@ -482,7 +482,7 @@ public class ItemsProviderTest { File videoFile = assertCreateNewVideo(); try { final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, - /* limit */ -1, /* mimeType */ "image/*", /* userId */ null); + /* limit */ -1, /* mimeType */ new String[]{ "image/*"}, /* userId */ null); assertThat(res).isNotNull(); assertThat(res.getCount()).isEqualTo(1); @@ -504,7 +504,7 @@ public class ItemsProviderTest { File imageFile = assertCreateNewImage(); try { final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, - /* limit */ -1, /* mimeType */ "image/png", /* userId */ null); + /* limit */ -1, /* mimeType */ new String[]{"image/png"}, /* userId */ null); assertThat(res).isNotNull(); assertThat(res.getCount()).isEqualTo(0); } finally { @@ -526,7 +526,7 @@ public class ItemsProviderTest { File videoFileHidden = assertCreateNewVideo(hiddenDir); try { final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, - /* limit */ -1, /* mimeType */ "image/*", /* userId */ null); + /* limit */ -1, /* mimeType */ new String[]{"image/*"}, /* userId */ null); assertThat(res).isNotNull(); assertThat(res.getCount()).isEqualTo(0); } finally { @@ -549,7 +549,7 @@ public class ItemsProviderTest { File videoFile = assertCreateNewVideo(); try { final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, - /* limit */ -1, /* mimeType */ "video/*", /* userId */ null); + /* limit */ -1, /* mimeType */ new String[]{"video/*"}, /* userId */ null); assertThat(res).isNotNull(); assertThat(res.getCount()).isEqualTo(1); @@ -571,7 +571,7 @@ public class ItemsProviderTest { File videoFile = assertCreateNewVideo(); try { final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, - /* limit */ -1, /* mimeType */ "video/mp4", /* userId */ null); + /* limit */ -1, /* mimeType */ new String[]{"video/mp4"}, /* userId */ null); assertThat(res).isNotNull(); assertThat(res.getCount()).isEqualTo(1); } finally { @@ -592,7 +592,7 @@ public class ItemsProviderTest { File videoFileHidden = assertCreateNewVideo(hiddenDir); try { final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, - /* limit */ -1, /* mimeType */ "video/*", /* userId */ null); + /* limit */ -1, /* mimeType */ new String[]{"video/*"}, /* userId */ null); assertThat(res).isNotNull(); assertThat(res.getCount()).isEqualTo(0); } finally { diff --git a/tests/src/com/android/providers/media/photopicker/LocalProvider.java b/tests/src/com/android/providers/media/photopicker/LocalProvider.java index 3916c9daa..b1c1281a3 100644 --- a/tests/src/com/android/providers/media/photopicker/LocalProvider.java +++ b/tests/src/com/android/providers/media/photopicker/LocalProvider.java @@ -16,7 +16,6 @@ package com.android.providers.media.photopicker; -import static android.provider.CloudMediaProviderContract.MediaCollectionInfo; import static com.android.providers.media.PickerProviderMediaGenerator.MediaGenerator; import android.content.res.AssetFileDescriptor; @@ -53,7 +52,7 @@ public class LocalProvider extends CloudMediaProvider { CloudProviderQueryExtras.fromCloudMediaBundle(extras); return mMediaGenerator.getMedia(queryExtras.getGeneration(), queryExtras.getAlbumId(), - queryExtras.getMimeType(), queryExtras.getSizeBytes()); + queryExtras.getMimeTypes(), queryExtras.getSizeBytes()); } @Override @@ -69,7 +68,7 @@ public class LocalProvider extends CloudMediaProvider { final CloudProviderQueryExtras queryExtras = CloudProviderQueryExtras.fromCloudMediaBundle(extras); - return mMediaGenerator.getAlbums(queryExtras.getMimeType(), queryExtras.getSizeBytes(), + return mMediaGenerator.getAlbums(queryExtras.getMimeTypes(), queryExtras.getSizeBytes(), /* isLocal */ true); } diff --git a/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java b/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java index 6931cd67d..0f8e79d3f 100644 --- a/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java +++ b/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java @@ -559,7 +559,9 @@ public class PickerDataLayerTest { private static Bundle buildQueryArgs(String mimeType, long sizeBytes) { final Bundle queryArgs = new Bundle(); - queryArgs.putString(MediaStore.QUERY_ARG_MIME_TYPE, mimeType); + if (mimeType != null) { + queryArgs.putStringArray(MediaStore.QUERY_ARG_MIME_TYPE, new String[]{mimeType}); + } queryArgs.putLong(MediaStore.QUERY_ARG_SIZE_BYTES, sizeBytes); return queryArgs; diff --git a/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java b/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java index 53d6837d7..bab7246b2 100644 --- a/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java +++ b/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java @@ -1052,8 +1052,10 @@ public class PickerSyncControllerTest { when(mockContext.getResources()).thenReturn(mockResources); when(mockContext.getPackageManager()).thenReturn(mContext.getPackageManager()); - when(mockContext.getSystemService(StorageManager.class)) - .thenReturn(mContext.getSystemService(StorageManager.class)); + when(mockContext.getSystemServiceName(StorageManager.class)).thenReturn( + mContext.getSystemServiceName(StorageManager.class)); + when(mockContext.getSystemService(StorageManager.class)).thenReturn( + mContext.getSystemService(StorageManager.class)); when(mockContext.getSharedPreferences(anyString(), anyInt())).thenAnswer(i -> { return mContext.getSharedPreferences((String)i.getArgument(0), (int)i.getArgument(1)); }); 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 3a37efaf2..3e07a2e50 100644 --- a/tests/src/com/android/providers/media/photopicker/data/ExternalDbFacadeTest.java +++ b/tests/src/com/android/providers/media/photopicker/data/ExternalDbFacadeTest.java @@ -81,7 +81,9 @@ public class ExternalDbFacadeTest { private static final long GENERATION_MODIFIED5 = 5; private static final long SIZE = 8000; private static final String IMAGE_MIME_TYPE = "image/jpeg"; + private static final String[] IMAGE_MIME_TYPES_QUERY = new String[]{"image/jpeg"}; private static final String VIDEO_MIME_TYPE = "video/mp4"; + private static final String[] VIDEO_MIME_TYPES_QUERY = new String[]{"video/mp4"}; private static final long DURATION_MS = 5; private static final int IS_FAVORITE = 0; @@ -542,12 +544,12 @@ public class ExternalDbFacadeTest { } try (Cursor cursor = facade.queryMedia(/* generation */ 0, - /* albumId */ null, VIDEO_MIME_TYPE)) { + /* albumId */ null, VIDEO_MIME_TYPES_QUERY)) { assertThat(cursor.getCount()).isEqualTo(0); } try (Cursor cursor = facade.queryMedia(/* generation */ 0, - /* albumId */ null, IMAGE_MIME_TYPE)) { + /* albumId */ null, IMAGE_MIME_TYPES_QUERY)) { assertThat(cursor.getCount()).isEqualTo(1); cursor.moveToFirst(); @@ -616,17 +618,17 @@ public class ExternalDbFacadeTest { } try (Cursor cursor = facade.queryMedia(/* generation */ 0, - ALBUM_ID_SCREENSHOTS, IMAGE_MIME_TYPE)) { + ALBUM_ID_SCREENSHOTS, IMAGE_MIME_TYPES_QUERY)) { assertThat(cursor.getCount()).isEqualTo(0); } try (Cursor cursor = facade.queryMedia(/* generation */ 0, - ALBUM_ID_CAMERA, VIDEO_MIME_TYPE)) { + ALBUM_ID_CAMERA, VIDEO_MIME_TYPES_QUERY)) { assertThat(cursor.getCount()).isEqualTo(0); } try (Cursor cursor = facade.queryMedia(/* generation */ 0, - ALBUM_ID_CAMERA, IMAGE_MIME_TYPE)) { + ALBUM_ID_CAMERA, IMAGE_MIME_TYPES_QUERY)) { assertThat(cursor.getCount()).isEqualTo(1); cursor.moveToFirst(); @@ -762,7 +764,7 @@ public class ExternalDbFacadeTest { cv1.put(MediaColumns.RELATIVE_PATH, ExternalDbFacade.RELATIVE_PATH_CAMERA); helper.runWithTransaction(db -> db.insert(TABLE_FILES, null, cv1)); - // Insert video in camera ablum + // Insert video in camera album ContentValues cv2 = getContentValues(DATE_TAKEN_MS5, GENERATION_MODIFIED5); cv2.put(FileColumns.MIME_TYPE, VIDEO_MIME_TYPE); cv2.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_VIDEO); @@ -772,7 +774,7 @@ public class ExternalDbFacadeTest { assertThat(cursor.getCount()).isEqualTo(2); } - try (Cursor cursor = facade.queryAlbums(IMAGE_MIME_TYPE)) { + try (Cursor cursor = facade.queryAlbums(IMAGE_MIME_TYPES_QUERY)) { assertThat(cursor.getCount()).isEqualTo(1); // We verify the order of the albums only the image in camera is shown diff --git a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java index 264e9bd16..5e13bec9c 100644 --- a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java +++ b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java @@ -49,7 +49,9 @@ public class PickerDbFacadeTest { private static final String CLOUD_ID = "asdfghjkl;"; private static final String ALBUM_ID = "testAlbum"; private static final String VIDEO_MIME_TYPE = "video/mp4"; + private static final String[] VIDEO_MIME_TYPES_QUERY = new String[]{"video/mp4"}; private static final String IMAGE_MIME_TYPE = "image/jpeg"; + private static final String[] IMAGE_MIME_TYPES_QUERY = new String[]{"image/jpeg"}; private static final int STANDARD_MIME_TYPE_EXTENSION = MediaColumns.STANDARD_MIME_TYPE_EXTENSION_GIF; @@ -609,7 +611,7 @@ public class PickerDbFacadeTest { } @Test - public void testQueryWithMimeTypeFilter() throws Exception { + public void testQueryWithMimeTypesFilter() throws Exception { Cursor cursor1 = getMediaCursor(LOCAL_ID, DATE_TAKEN_MS, GENERATION_MODIFIED, /* mediaStoreUri */ null, SIZE_BYTES, "video/webm", STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false); @@ -622,12 +624,12 @@ public class PickerDbFacadeTest { // Verify all PickerDbFacade.QueryFilterBuilder qfbAll = new PickerDbFacade.QueryFilterBuilder(1000); - qfbAll.setMimeType("*/*"); + qfbAll.setMimeTypes(new String[]{"*/*"}); try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) { assertThat(cr.getCount()).isEqualTo(2); } - qfbAll.setMimeType("video/mp4"); + qfbAll.setMimeTypes(new String[]{"video/mp4"}); try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) { assertThat(cr.getCount()).isEqualTo(1); @@ -639,14 +641,14 @@ public class PickerDbFacadeTest { PickerDbFacade.QueryFilterBuilder qfbAfter = new PickerDbFacade.QueryFilterBuilder(1000); qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS - 1); qfbAfter.setId(0); - qfbAfter.setMimeType("video/*"); + qfbAfter.setMimeTypes(new String[]{"video/*"}); try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) { assertThat(cr.getCount()).isEqualTo(2); } qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS - 1); qfbAfter.setId(0); - qfbAfter.setMimeType("video/webm"); + qfbAfter.setMimeTypes(new String[]{"video/webm"}); try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) { assertThat(cr.getCount()).isEqualTo(1); @@ -658,14 +660,14 @@ public class PickerDbFacadeTest { PickerDbFacade.QueryFilterBuilder qfbBefore = new PickerDbFacade.QueryFilterBuilder(1000); qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS + 1); qfbBefore.setId(0); - qfbBefore.setMimeType("video/*"); + qfbBefore.setMimeTypes(new String[]{"video/*"}); try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) { assertThat(cr.getCount()).isEqualTo(2); } qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS + 1); qfbBefore.setId(0); - qfbBefore.setMimeType("video/mp4"); + qfbBefore.setMimeTypes(new String[]{"video/mp4"}); try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) { assertThat(cr.getCount()).isEqualTo(1); @@ -675,7 +677,7 @@ public class PickerDbFacadeTest { } @Test - public void testQueryWithSizeAndMimeTypeFilter() throws Exception { + public void testQueryWithSizeAndMimeTypesFilter() throws Exception { Cursor cursor1 = getMediaCursor(LOCAL_ID, DATE_TAKEN_MS, GENERATION_MODIFIED, /* mediaStoreUri */ null, /* sizeBytes */ 2, "video/webm", STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false); @@ -688,14 +690,14 @@ public class PickerDbFacadeTest { // mime_type and size filter matches all PickerDbFacade.QueryFilterBuilder qfbAll = new PickerDbFacade.QueryFilterBuilder(1000); - qfbAll.setMimeType("*/*"); + qfbAll.setMimeTypes(new String[]{"*/*"}); qfbAll.setSizeBytes(10); try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) { assertThat(cr.getCount()).isEqualTo(2); } // mime_type and size filter matches none - qfbAll.setMimeType("video/webm"); + qfbAll.setMimeTypes(new String[]{"video/webm"}); qfbAll.setSizeBytes(1); try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) { assertThat(cr.getCount()).isEqualTo(0); @@ -882,7 +884,7 @@ public class PickerDbFacadeTest { } @Test - public void testGetFavoritesAlbumWithMimeTypeFilter() throws Exception { + public void testGetFavoritesAlbumWithMimeTypesFilter() throws Exception { Cursor localCursor1 = getMediaCursor(LOCAL_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED, /* mediaStoreUri */ null, SIZE_BYTES, VIDEO_MIME_TYPE, STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true); @@ -933,7 +935,7 @@ public class PickerDbFacadeTest { /* count */ 2); } - qfb.setMimeType(IMAGE_MIME_TYPE); + qfb.setMimeTypes(IMAGE_MIME_TYPES_QUERY); try (Cursor cr = mFacade.getMergedAlbums(qfb.build())) { assertThat(cr.getCount()).isEqualTo(1); cr.moveToFirst(); @@ -945,7 +947,7 @@ public class PickerDbFacadeTest { /* count */ 1); } - qfb.setMimeType(VIDEO_MIME_TYPE); + qfb.setMimeTypes(VIDEO_MIME_TYPES_QUERY); try (Cursor cr = mFacade.getMergedAlbums(qfb.build())) { assertThat(cr.getCount()).isEqualTo(2); cr.moveToFirst(); @@ -964,7 +966,7 @@ public class PickerDbFacadeTest { /* count */ 2); } - qfb.setMimeType("foo"); + qfb.setMimeTypes(new String[]{"foo"}); try (Cursor cr = mFacade.getMergedAlbums(qfb.build())) { assertThat(cr.getCount()).isEqualTo(0); } @@ -1113,7 +1115,8 @@ public class PickerDbFacadeTest { } private static Cursor getAlbumMediaCursor(String id, long dateTakenMs, long generationModified, - String mediaStoreUri, long sizeBytes, String mimeType, int standardMimeTypeExtension) { + String mediaStoreUri, long sizeBytes, String mimeType, + int standardMimeTypeExtension) { String[] projectionKey = new String[] { MediaColumns.ID, MediaColumns.MEDIA_STORE_URI, diff --git a/tests/src/com/android/providers/media/photopicker/util/MimeFilterUtilsTest.java b/tests/src/com/android/providers/media/photopicker/util/MimeFilterUtilsTest.java index 2c98c3f14..966f0ed85 100644 --- a/tests/src/com/android/providers/media/photopicker/util/MimeFilterUtilsTest.java +++ b/tests/src/com/android/providers/media/photopicker/util/MimeFilterUtilsTest.java @@ -18,10 +18,15 @@ package com.android.providers.media.photopicker.util; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + import android.content.Intent; +import android.provider.MediaStore; import org.junit.Test; +import java.util.Arrays; + public class MimeFilterUtilsTest { private static final String[] MEDIA_MIME_TYPES = new String[] {"image/*", "video/*"}; @@ -59,7 +64,7 @@ public class MimeFilterUtilsTest { intent.setType(mimeType); intent.putExtra(Intent.EXTRA_MIME_TYPES, MEDIA_MIME_TYPES); - assertThat(MimeFilterUtils.requiresUnsupportedFilters(intent)).isTrue(); + assertThat(MimeFilterUtils.requiresUnsupportedFilters(intent)).isFalse(); } @Test @@ -80,7 +85,7 @@ public class MimeFilterUtilsTest { intent.setType(mimeType); intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"video/mp4", "image/gif"}); - assertThat(MimeFilterUtils.requiresUnsupportedFilters(intent)).isTrue(); + assertThat(MimeFilterUtils.requiresUnsupportedFilters(intent)).isFalse(); } @Test @@ -102,25 +107,47 @@ public class MimeFilterUtilsTest { } @Test - public void testGetMimeTypeFilter() { + public void testGetMimeTypeFilter_setType() { Intent intent = new Intent(); String mimeType = "image/*"; intent.setType(mimeType); - assertThat(MimeFilterUtils.getMimeTypeFilter(intent).equals(mimeType)).isTrue(); + assertThat(Arrays.equals(MimeFilterUtils.getMimeTypeFilters(intent), + new String[]{mimeType})).isTrue(); mimeType = "video/mp4"; intent.setType(mimeType); - assertThat(MimeFilterUtils.getMimeTypeFilter(intent).equals(mimeType)).isTrue(); mimeType = "*/*"; intent.setType(mimeType); - assertThat(MimeFilterUtils.getMimeTypeFilter(intent)).isNull(); + assertThat(MimeFilterUtils.getMimeTypeFilters(intent)).isNull(); + // Test EXTRA_MIME_TYPE has higher priority than setType mimeType = "image/*"; intent.setType(mimeType); - String[] extraMimeTypes = new String[] {"video/*"}; + String[] extraMimeTypes = new String[] {"video/mp4", "video/dng"}; + intent.putExtra(Intent.EXTRA_MIME_TYPES, extraMimeTypes); + assertThat(Arrays.equals(MimeFilterUtils.getMimeTypeFilters(intent), + extraMimeTypes)).isTrue(); + } + + @Test + public void testGetMimeTypeFilter_extraMimeType_pickImages() { + Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + String[] extraMimeTypes = new String[] {"audio/mp4", "video/dng"}; + intent.putExtra(Intent.EXTRA_MIME_TYPES, extraMimeTypes); + + assertThrows(IllegalArgumentException.class, () -> { + MimeFilterUtils.getMimeTypeFilters(intent); + }); + } + + @Test + public void testGetMimeTypeFilter_extraMimeType_getContent() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + String[] extraMimeTypes = new String[] {"audio/mp4", "video/dng"}; intent.putExtra(Intent.EXTRA_MIME_TYPES, extraMimeTypes); - assertThat(MimeFilterUtils.getMimeTypeFilter(intent).equals("video/*")).isTrue(); + + assertThat(MimeFilterUtils.getMimeTypeFilters(intent)).isNull(); } } 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 ec4afa191..ab5069639 100644 --- a/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java +++ b/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java @@ -21,6 +21,7 @@ import static android.provider.CloudMediaProviderContract.MediaColumns; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -30,7 +31,7 @@ import android.content.Intent; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; -import android.provider.CloudMediaProviderContract; +import android.provider.MediaStore; import android.text.format.DateUtils; import androidx.annotation.NonNull; @@ -325,7 +326,7 @@ public class PickerViewModelTest { @Override public Cursor getItems(Category category, int offset, - int limit, @Nullable String mimeType, @Nullable UserId userId) throws + int limit, @Nullable String[] mimeType, @Nullable UserId userId) throws IllegalArgumentException, IllegalStateException { final MatrixCursor c = new MatrixCursor(MediaColumns.ALL_PROJECTION); @@ -349,7 +350,7 @@ public class PickerViewModelTest { } @Nullable - public Cursor getCategories(@Nullable String mimeType, @Nullable UserId userId) { + public Cursor getCategories(@Nullable String[] mimeType, @Nullable UserId userId) { if (mCategoriesCursor != null) { return mCategoriesCursor; } @@ -368,31 +369,75 @@ public class PickerViewModelTest { } @Test - public void testParseValuesFromIntent_noMimeType_defaultFalse() { - final Intent intent = new Intent(); + public void testParseValuesFromPickImagesIntent_noMimeType_defaultFalse() { + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); mPickerViewModel.parseValuesFromIntent(intent); - assertThat(mPickerViewModel.hasMimeTypeFilter()).isFalse(); + assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse(); + } + + @Test + public void testParseValuesFromGetContentIntent_noMimeType_defaultFalse() { + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + intent.setType("*/*"); + + mPickerViewModel.parseValuesFromIntent(intent); + + assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse(); } @Test public void testParseValuesFromIntent_validMimeType() { - final Intent intent = new Intent(); + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); intent.setType("image/png"); mPickerViewModel.parseValuesFromIntent(intent); - assertThat(mPickerViewModel.hasMimeTypeFilter()).isTrue(); + assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue(); + } + + @Test + public void testParseValuesFromPickImagesIntent_validExtraMimeType() { + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/gif", "video/*"}); + + mPickerViewModel.parseValuesFromIntent(intent); + + assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue(); + } + + @Test + public void testParseValuesFromPickImagesIntent_invalidExtraMimeType() { + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"audio/*", "video/*"}); + + try { + mPickerViewModel.parseValuesFromIntent(intent); + fail("Photo Picker does not support non-media mime type filters"); + } catch (IllegalArgumentException expected) { + // Expected + } + } + + @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/*"}); + + mPickerViewModel.parseValuesFromIntent(intent); + + assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue(); } @Test - public void testParseValuesFromIntent_ignoreInvalidMimeType() { - final Intent intent = new Intent(); - intent.setType("audio/*"); + public void testParseValuesFromGetContentIntent_invalidExtraMimeType() { + final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"audio/*", "video/*"}); mPickerViewModel.parseValuesFromIntent(intent); - assertThat(mPickerViewModel.hasMimeTypeFilter()).isFalse(); + // non-media filters for GET_CONTENT show all images and videos + assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse(); } } diff --git a/tests/src/com/android/providers/media/scan/MediaScannerTest.java b/tests/src/com/android/providers/media/scan/MediaScannerTest.java index f8c27e6a6..4e0e26592 100644 --- a/tests/src/com/android/providers/media/scan/MediaScannerTest.java +++ b/tests/src/com/android/providers/media/scan/MediaScannerTest.java @@ -130,6 +130,11 @@ public class MediaScannerTest { protected void updateNextRowIdXattr(DatabaseHelper helper, long id) { // Ignoring this as test app would not have access to update xattr. } + + @Override + protected void checkDeviceConfigAndUpdateGetContentAlias() { + // Ignore this as test app cannot read device config + } }; mProvider.attachInfo(this, info); mResolver.addProvider(MediaStore.AUTHORITY, mProvider); diff --git a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java index 0450c1cee..e3c56f16e 100644 --- a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java +++ b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java @@ -198,8 +198,14 @@ public class ModernMediaScannerTest { assertEquals(270, (int) parseOptionalOrientation(ExifInterface.ORIENTATION_ROTATE_270).get()); - // We can't represent this as an orientation - assertFalse(parseOptionalOrientation(ExifInterface.ORIENTATION_TRANSPOSE).isPresent()); + assertEquals(0, + (int) parseOptionalOrientation(ExifInterface.ORIENTATION_FLIP_HORIZONTAL).get()); + assertEquals(90, + (int) parseOptionalOrientation(ExifInterface.ORIENTATION_TRANSPOSE).get()); + assertEquals(180, + (int) parseOptionalOrientation(ExifInterface.ORIENTATION_FLIP_VERTICAL).get()); + assertEquals(270, + (int) parseOptionalOrientation(ExifInterface.ORIENTATION_TRANSVERSE).get()); } @Test diff --git a/tests/src/com/android/providers/media/util/FileUtilsTest.java b/tests/src/com/android/providers/media/util/FileUtilsTest.java index 266c14473..a1f2b8237 100644 --- a/tests/src/com/android/providers/media/util/FileUtilsTest.java +++ b/tests/src/com/android/providers/media/util/FileUtilsTest.java @@ -63,6 +63,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1021,11 +1022,16 @@ public class FileUtilsTest { assertThat(isDataOrObbPath("/storage/emulated/0/Android/obb")).isTrue(); assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/data")).isTrue(); assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/obb")).isTrue(); - assertThat(isDataOrObbPath("/storage/emulated/0/Android/data/foo")).isTrue(); - assertThat(isDataOrObbPath("/storage/emulated/0/Android/obb/foo")).isTrue(); - assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/data/foo")).isTrue(); - assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/obb/foo")).isTrue(); + assertThat(isDataOrObbPath("/storage/emulated/0/Android/data/foo")).isFalse(); + assertThat(isDataOrObbPath("/storage/emulated/0/Android/obb/foo")).isFalse(); + assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/data/foo")).isFalse(); + assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/obb/foo")).isFalse(); + assertThat(isDataOrObbPath("/storage/emulated/10/Android/obb/foo")).isFalse(); + assertThat(isDataOrObbPath("/storage/emulated//Android/obb/foo")).isFalse(); + assertThat(isDataOrObbPath("/storage/emulated//Android/obb")).isFalse(); + assertThat(isDataOrObbPath("/storage/emulated/0//Android/obb")).isFalse(); + assertThat(isDataOrObbPath("/storage/emulated/0//Android/obb/foo")).isFalse(); assertThat(isDataOrObbPath("/storage/emulated/0/Android/")).isFalse(); assertThat(isDataOrObbPath("/storage/emulated/0/Android/media/")).isFalse(); assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/media/")).isFalse(); @@ -1200,4 +1206,27 @@ public class FileUtilsTest { assertTrue(values.containsKey(MediaColumns.BUCKET_DISPLAY_NAME)); assertNull(values.get(MediaColumns.BUCKET_DISPLAY_NAME)); } + + @Test + public void testComputeDataFromValuesForValidPath_success() { + final ContentValues values = new ContentValues(); + values.put(MediaColumns.RELATIVE_PATH, "Android/media/com.example"); + values.put(MediaColumns.DISPLAY_NAME, "./../../abc.txt"); + + FileUtils.computeDataFromValues(values, new File("/storage/emulated/0"), false); + + assertThat(values.getAsString(MediaColumns.DATA)).isEqualTo( + "/storage/emulated/0/Android/abc.txt"); + } + + @Test + public void testComputeDataFromValuesForInvalidPath_throwsIllegalArgumentException() { + final ContentValues values = new ContentValues(); + values.put(MediaColumns.RELATIVE_PATH, "\0"); + values.put(MediaColumns.DISPLAY_NAME, "./../../abc.txt"); + + assertThrows(IllegalArgumentException.class, + () -> FileUtils.computeDataFromValues(values, new File("/storage/emulated/0"), + false)); + } } |