Snap for 8982257 from 31af7a102d3761062419ccb9939609f26b2fd51f to mainline-conscrypt-release

Change-Id: I4e5592ac2ab51a0c14bd2bb416871522acfd4e2e
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index dfe0ce0..7df12ff 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -49,9 +49,13 @@
     <permission android:name="com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS"
                 android:protectionLevel="signature" />
 
+    <!-- We use Photo Picker app icon for this package. It is necessary for Photo Picker GET_CONTENT
+         take over. Some apps query packages that can handle GET_CONTENT and want to display the
+         icon and label of the package to the user. -->
     <application
             android:name="com.android.providers.media.MediaApplication"
             android:label="@string/app_label"
+            android:icon="@mipmap/picker_app_icon"
             android:allowBackup="false"
             android:supportsRtl="true"
             android:forceQueryable="true"
@@ -166,7 +170,6 @@
                 android:name="com.android.providers.media.photopicker.PhotoPickerActivity"
                 android:process=":PhotoPicker"
                 android:label="@string/picker_app_label"
-                android:icon="@mipmap/picker_app_icon"
                 android:theme="@style/PickerDefaultTheme"
                 android:exported="true"
                 android:excludeFromRecents="true"
diff --git a/apex/Android.bp b/apex/Android.bp
index c735880..ccc26f5 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -40,7 +40,11 @@
 
 sdk {
     name: "mediaprovider-module-sdk",
-    bootclasspath_fragments: ["com.android.mediaprovider-bootclasspath-fragment"],
+    apexes: [
+        // Adds exportable dependencies of the APEX to the sdk,
+        // e.g. *classpath_fragments.
+        "com.android.mediaprovider",
+    ],
 }
 
 // Encapsulate the contributions made by the com.android.mediaprovider to the bootclasspath.
diff --git a/apex/apex_manifest.json b/apex/apex_manifest.json
index fe2ed11..527b9f7 100644
--- a/apex/apex_manifest.json
+++ b/apex/apex_manifest.json
@@ -1,4 +1,7 @@
 {
   "name": "com.android.mediaprovider",
-  "version": 339990000
+
+  // Placeholder module version to be replaced during build.
+  // Do not change!
+  "version": 0
 }
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index ca67b8a..852535b 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -234,6 +234,8 @@
     public static final String EXTRA_RESULT = "result";
     /** {@hide} */
     public static final String EXTRA_FILE_DESCRIPTOR = "file_descriptor";
+    /** {@hide} */
+    public static final String EXTRA_LOCAL_PROVIDER = "local_provider";
 
     /** {@hide} */
     public static final String IS_SYSTEM_GALLERY_CALL = "is_system_gallery";
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index b2b8ab3..740b562 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -496,9 +496,8 @@
 }
 
 static double get_entry_timeout(const string& path, bool should_inval, struct fuse* fuse) {
-    string media_path = fuse->GetEffectiveRootPath() + "/Android/media";
     if (fuse->disable_dentry_cache || should_inval || is_package_owned_path(path, fuse->path) ||
-        android::base::StartsWithIgnoreCase(path, media_path) || fuse->ShouldNotCache(path)) {
+        fuse->ShouldNotCache(path)) {
         // We set dentry timeout to 0 for the following reasons:
         // 1. The dentry cache was completely disabled for the entire volume.
         // 2.1 Case-insensitive lookups need to invalidate other case-insensitive dentry matches
@@ -507,10 +506,7 @@
         // 3. With app data isolation enabled, app A should not guess existence of app B from the
         // Android/{data,obb}/<package> paths, hence we prevent the kernel from caching that
         // information.
-        // 4. Installd might delete Android/media/<package> dirs when app data is cleared.
-        // This can leave a stale entry in the kernel dcache, and break subsequent creation of the
-        // dir via FUSE.
-        // 5. The dentry cache was completely disabled for the given path.
+        // 4. The dentry cache was completely disabled for the given path.
         return 0;
     }
     return std::numeric_limits<double>::max();
diff --git a/res/layout-v33/safety_protection_section.xml b/res/layout-v33/safety_protection_section.xml
new file mode 100644
index 0000000..155132f
--- /dev/null
+++ b/res/layout-v33/safety_protection_section.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@android:drawable/ic_safety_protection"
+        android:contentDescription="@string/safety_protection_icon_label"/>
+
+    <TextView
+        android:id="@+id/safety_protection_display_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingStart="8dp"
+        android:paddingEnd="0dp"
+        android:paddingTop="3dp"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textSize="12sp" />
+</merge>
\ No newline at end of file
diff --git a/res/layout/activity_photo_picker.xml b/res/layout/activity_photo_picker.xml
index 1680806..fc73702 100644
--- a/res/layout/activity_photo_picker.xml
+++ b/res/layout/activity_photo_picker.xml
@@ -47,6 +47,12 @@
             android:src="@drawable/ic_drag"
             android:contentDescription="@null"/>
 
+        <com.android.providers.media.photopicker.ui.SafetyProtectionSectionView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="5dp"
+            android:layout_gravity="center" />
+
         <TextView
             android:id="@+id/privacy_text"
             android:layout_width="wrap_content"
diff --git a/res/layout/item_cloud_video_preview.xml b/res/layout/item_cloud_video_preview.xml
deleted file mode 100644
index f8c3b6e..0000000
--- 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 1d7d32d..f8c3b6e 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/values/drawables.xml b/res/mipmap-anydpi/picker_app_icon.xml
similarity index 70%
rename from res/values/drawables.xml
rename to res/mipmap-anydpi/picker_app_icon.xml
index a5a0b3d..e9f989a 100644
--- a/res/values/drawables.xml
+++ b/res/mipmap-anydpi/picker_app_icon.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
@@ -13,9 +14,8 @@
   ~ 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
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@android:color/white"/>
+    <foreground android:drawable="@mipmap/picker_app_icon_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/res/mipmap-anydpi/picker_app_icon_foreground.xml b/res/mipmap-anydpi/picker_app_icon_foreground.xml
new file mode 100644
index 0000000..a386171
--- /dev/null
+++ b/res/mipmap-anydpi/picker_app_icon_foreground.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+    <group android:scaleX="2.295"
+           android:scaleY="2.295"
+           android:translateX="26.46"
+           android:translateY="26.46">
+        <path
+            android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2ZM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18ZM8,16h12L20,4L8,4v12ZM4,22q-0.825,0 -1.412,-0.587Q2,20.825 2,20L2,6h2v14h14v2ZM8,4v12L8,4Z"
+            android:fillColor="#C4C7C5"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/res/mipmap-hdpi/picker_app_icon.png b/res/mipmap-hdpi/picker_app_icon.png
deleted file mode 100644
index c911ffc..0000000
--- a/res/mipmap-hdpi/picker_app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-mdpi/picker_app_icon.png b/res/mipmap-mdpi/picker_app_icon.png
deleted file mode 100644
index 9a0f98a..0000000
--- a/res/mipmap-mdpi/picker_app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xhdpi/picker_app_icon.png b/res/mipmap-xhdpi/picker_app_icon.png
deleted file mode 100644
index ca55edd..0000000
--- a/res/mipmap-xhdpi/picker_app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xxhdpi/picker_app_icon.png b/res/mipmap-xxhdpi/picker_app_icon.png
deleted file mode 100644
index 531c94f..0000000
--- a/res/mipmap-xxhdpi/picker_app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xxxhdpi/picker_app_icon.png b/res/mipmap-xxxhdpi/picker_app_icon.png
deleted file mode 100644
index ce72805..0000000
--- a/res/mipmap-xxxhdpi/picker_app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 8195a2f..6198f78 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Verwerk media …"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Kanselleer"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Wag"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Veiligheidbeskerming"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"Toestelspesifieke kodewisselingopletberigte"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"Toestelspesifieke kodewisselingvordering"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 735486f..d5301bd 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"ሚዲያን በማሰናዳት ላይ…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"ይቅር"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"ጠብቅ"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"የደህንነት ጥበቃ"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index f370817..c1df5aa 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"جارٍ معالجة الوسائط…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"إلغاء"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"الانتظار"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"حماية الأمن الشخصي"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index a034a1a..0b09c9d 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"মিডিয়াৰ প্ৰক্ৰিয়াকৰণ কৰি থকা হৈছে…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"বাতিল কৰক"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"অপেক্ষা কৰক"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"সুৰক্ষিত নিৰাপত্তা"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 19832e3..8944beb 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Media emal edilir…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Ləğv edin"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Gözləyin"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Güvənlik qoruması"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index fe84f33..254344e 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Obrađuju se mediji…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Otkaži"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Sačekaj"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Sigurnosna zaštita"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index f01a7dc..b3be1da 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Ідзе апрацоўка мультымедыя…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Скасаваць"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Пачакаць"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Ахова бяспекі"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index c2d966c..09b63ea 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Мултимедията се обработва…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Отказ"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Изчакване"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Защита на безопасността"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index a89424f..350ee31 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"মিডিয়া ফাইল প্রসেস করা হচ্ছে…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"বাতিল করুন"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"অপেক্ষা করুন"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"নিরাপত্তার সুরক্ষা"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"নেটিভ ট্রান্সকোড অ্যালার্ট"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"নেটিভ ট্রান্সকোড প্রোগ্রেস"</string>
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index f0bbc59..2af68dd 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Obrada medijskih fajlova…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Otkaži"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Sačekaj"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Zaštita sigurnosti"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index b4f9df8..1b05b7c 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"S\'està processant el contingut multimèdia…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Cancel·la"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Espera"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Protecció de seguretat"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index a95d75a..5e4b182 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Mediální obsah se zpracovává…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Zrušit"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Počkat"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Bezpečnostní ochrana"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index b605b8e..6865a55 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Behandler medier…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Annuller"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Vent"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Beskyttelse"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 592438e..556b4ce 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Medien werden verarbeitet…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Abbrechen"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Warten"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Schutz"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 5ed5abe..37b4550 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Επεξεργασία μέσων…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Ακύρωση"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Αναμονή"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Safety Protection"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 31be367..ddc73c3 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Processing media…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Cancel"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Wait"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Safety protection"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"Native transcode alerts"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"Native transcode progress"</string>
 </resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 31be367..ddc73c3 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Processing media…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Cancel"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Wait"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Safety protection"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"Native transcode alerts"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"Native transcode progress"</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 31be367..ddc73c3 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Processing media…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Cancel"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Wait"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Safety protection"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"Native transcode alerts"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"Native transcode progress"</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 31be367..ddc73c3 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Processing media…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Cancel"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Wait"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Safety protection"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"Native transcode alerts"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"Native transcode progress"</string>
 </resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index f6566e4..b68bcf3 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‏‎‎‏‎‏‎‎Processing media…‎‏‎‎‏‎"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎Cancel‎‏‎‎‏‎"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‏‎‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‏‎‎‏‏‏‎‏‎Wait‎‏‎‎‏‎"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‎Safety protection‎‏‎‎‏‎"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‎‏‏‏‏‎‎Native Transcode Alerts‎‏‎‎‏‎"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‎‏‏‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‎Native Transcode Progress‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 7dd01b9..3b775b6 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Procesando contenido multimedia…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Cancelar"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Esperar"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Protección de seguridad"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 05d275a..2099b4c 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Procesando elementos multimedia…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Cancelar"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Espera"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Protección de seguridad"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 7c89a9c..0453a28 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Meedia töötlemine …"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Tühista"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Oota"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Ohutuskaitse"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index f85e04c..78c46a2 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Multimedia-edukia prozesatzen…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Utzi"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Itxaron"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Segurtasun-babesa"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index d50fce9..802cbd3 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"درحال پردازش رسانه…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"لغو"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"انتظار"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"محافظت امنیتی"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 5975059..a945565 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Käsitellään mediasisältöä…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Peru"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Odota"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Turvallisuuden varmistaminen"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index d4f4dbf..529e07d 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Traitement du contenu multimédia en cours…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Annuler"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Patienter"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Protection de sécurité"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 488291d..6c10bd6 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Traitement des contenus multimédias…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Annuler"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Attendre"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Protection de sécurité"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 7a3965b..cd1128a 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Procesando contido multimedia…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Cancelar"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Esperar"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Protección de seguranza"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 6edca72..eba7e3b 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"મીડિયા પર પ્રક્રિયા થઈ રહી છે…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"રદ કરો"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"રાહ જુઓ"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"સલામતી સંરક્ષણ"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 25d7c37..bfba395 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"मीडिया को प्रोसेस किया जा रहा है…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"अभी नहीं"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"इंतज़ार करें"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"सुरक्षा के लिए बचाव"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index ed77a55..a8b5aee 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Obrada medijskih sadržaja…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Odustani"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Pričekaj"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Osiguranje"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 03b231e..a843ecd 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Médiatartalom feldolgozása…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Mégse"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Várakozás"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Biztonsági védelem"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 129fd1c..2a804e3 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Մեդիաֆայլը մշակվում է…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Չեղարկել"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Սպասել"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Անվտանգության պաշտպանություն"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index d7a135b..b2a023d 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Memproses media…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Batal"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Tunggu"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Perlindungan keselamatan"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 7b874de..ad4c23d 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Vinnur úr efni…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Hætta við"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Bíða"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Öryggisbúnaður"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index e136751..aec7b0b 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Elaborazione dei contenuti multimediali in corso…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Annulla"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Attendi"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Protezione di sicurezza"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 2b3c0c1..7d74c20 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"המדיה בעיבוד…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"ביטול"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"המתנה"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"הגנה על בטיחות"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"התראות של המרת קידוד מקורית"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"התקדמות של המרת קידוד מקורית"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 8a89764..3c1157c 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"メディアを処理しています…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"キャンセル"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"待機"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"安全保護"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 0ce5cb0..dd85e46 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"მედია მუშავდება…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"გაუქმება"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"მოცდა"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"უსაფრთხოების დაცვა"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"ტრანსკოდირების ადგილობრივი გაფრთხილება"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"ტრანსკოდირების ადგილობრივი პროგრესი"</string>
 </resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index f9b1551..fe71d9d 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Медиафайл өңделуде…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Бас тарту"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Күту"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Қауіпсіздікті қорғау"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index cc694f1..f36c8b5 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"កំពុងដំណើរការមេឌៀ…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"បោះបង់"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"រង់ចាំ"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"ការការពារសុវត្ថិភាព"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 17552ad..b75795c 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"ಪ್ರಕ್ರಿಯೆಯಲ್ಲಿರುವ ಮಾಧ್ಯಮ…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"ರದ್ದುಮಾಡಿ"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"ನಿರೀಕ್ಷಿಸಿ"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"ಭದ್ರತಾ ರಕ್ಷಣೆ"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 5b11b4c..99d1428 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"미디어 처리 중…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"취소"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"대기"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"안전 보안"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 65de3c4..943f7fd 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Медиа иштетилүүдө…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Жокко чыгаруу"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Күтүү"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Коопсуздукту коргоо"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 2412919..45a0b5f 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"ກຳລັງປະມວນຜົນມີເດຍ…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"ຍົກເລີກ"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"ລໍຖ້າ"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"ການປ້ອງກັນຄວາມປອດໄພ"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 5e7b7d5..ff61ba7 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Medija apdorojama…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Atšaukti"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Palaukti"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Apsauga"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index a201f12..9a8764e 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Notiek multivides apstrāde…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Atcelt"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Gaidīt"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Drošības aizsardzība"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index b8491d1..2a5a069 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Се транскодира…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Откажи"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Почекајте"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Безбедносна заштита"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 96bfc40..08ca678 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"മീഡിയ പ്രോസസ് ചെയ്യുന്നു…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"റദ്ദാക്കുക"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"കാത്തിരിക്കുക"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"സുരക്ഷാ പരിരക്ഷ"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"നേറ്റീവ് ട്രാൻസ്കോഡ് മുന്നറിയിപ്പുകൾ"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"നേറ്റീവ് ട്രാൻസ്കോഡ് പുരോഗതി"</string>
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index da976d1..0892e40 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Медиаг боловсруулж байна…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Цуцлах"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Хүлээх"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Аюулгүй байдлын хамгаалалт"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"Уугуул хөрвүүлгийн сэрэмжлүүлэг"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"Уугуул хөрвүүлгийн явц"</string>
 </resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 474863c..7d3b8f7 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"मीडियावर प्रक्रिया सुरू आहे…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"रद्द करा"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"प्रतीक्षा करा"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"सुरक्षितता संरक्षण"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"मूळ ट्रान्सकोड सूचना"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"मूळ ट्रान्सकोड प्रगती"</string>
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index bb9f21a..c468525 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Memproses media…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Batal"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Tunggu"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Perlindungan keselamatan"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 5d33238..572e607 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"မီဒီယာကို လုပ်ဆောင်နေသည်…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"မလုပ်တော့"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"စောင့်ရန်"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"လုံခြုံရေး ကာကွယ်မှု"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index e73aea8..626bd68 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Mediene behandles …"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Avbryt"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Vent"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Beskyttelse"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index f1ea063..db20b5d 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"मिडिया प्रोसेस गरिँदै छ…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"रद्द गर्नुहोस्"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"पर्खनुहोस्"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"सेफ्टी प्रोटेक्सन"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 832d8bc..d5fa95c 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Media verwerken…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Annuleren"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Wachten"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Beveiliging"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"Meldingen voor native transcodering"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"Voortgang van native transcodering"</string>
 </resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 05511da..cbd8834 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -118,6 +118,11 @@
     <string name="transcode_processing_success" msgid="447288876429730122">"ମିଡିଆ ପ୍ରକ୍ରିୟାକରଣ ସଫଳ ହୋଇଛି"</string>
     <string name="transcode_processing_started" msgid="7789086308155361523">"ମିଡିଆ ପ୍ରକ୍ରିୟାକରଣ ଆରମ୍ଭ କରାଯାଇଛି"</string>
     <string name="transcode_processing" msgid="6753136468864077258">"ମିଡିଆ ପ୍ରକ୍ରିୟାକରଣ କରାଯାଉଛି…"</string>
-    <string name="transcode_cancel" msgid="8555752601907598192">"ବାତିଲ୍ କରନ୍ତୁ"</string>
+    <string name="transcode_cancel" msgid="8555752601907598192">"ବାତିଲ କରନ୍ତୁ"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"ଅପେକ୍ଷା କରନ୍ତୁ"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"ସୁରକ୍ଷିତ ସୁରକ୍ଷା"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index b1c7c35..82e51f4 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"ਮੀਡੀਆ \'ਤੇ ਪ੍ਰਕਿਰਿਆ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"ਰੱਦ ਕਰੋ"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"ਉਡੀਕ ਕਰੋ"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"ਸੁਰੱਖਿਆ ਬਚਾਅ"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index e40e6d4..53774f1 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Przetwarzam multimedia…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Anuluj"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Czekaj"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Sprzęt zabezpieczający"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
index f896284..1fafd2f 100644
--- a/res/values-pt-rBR/strings.xml
+++ b/res/values-pt-rBR/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Processando mídia…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Cancelar"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Aguardar"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Proteção"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 4db74cf..5d3bd21 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"A processar multimédia…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Cancelar"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Aguardar"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Proteção de segurança"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"Alertas de transcodificação nativa"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"Progresso de transcodificação nativa"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index f896284..1fafd2f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Processando mídia…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Cancelar"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Aguardar"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Proteção"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 0e5d4b5..f749308 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Se procesează conținutul media…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Anulați"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Așteptați"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Protecția în caz de accidente"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 4844c64..589d08b 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Обработка медиафайла…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Отмена"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Подождать"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Защита безопасности"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index c9ffffc..89e5c46 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"මාධ්‍යය සකසමින්…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"අවලංගු කරන්න"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"රැඳී සිටින්න"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"සුරක්ෂිතතා ආරක්ෂණය"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 934f91e..e41c880 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Spracúvajú sa médiá…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Zrušiť"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Počkať"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Bezpečnosť"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index a3653ac..0376321 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Obdelovanje predstavnosti …"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Prekliči"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Počakaj"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Varnostna zaščita"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"Opozorila o izvornem prekodiranju"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"Napredek izvornega prekodiranja"</string>
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 35f975f..4348f24 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Media po përpunohet…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Anulo"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Prit"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Mbrojtja e sigurisë"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 07e5a2a..6f99c0e 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Обрађују се медији…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Откажи"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Сачекај"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Сигурносна заштита"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 262b83a..3f01d60 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Bearbetar media …"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Avbryt"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Vänta"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Säkerhet"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 735c55a..73cbc8e 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Inachakata maudhui…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Ghairi"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Subiri"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Ulinzi wa Usalama"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 468586c..5fe91c2 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"மீடியாவைச் செயலாக்குகிறது…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"ரத்துசெய்"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"காத்திருங்கள்"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"பாதுகாப்பு வளையம்"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index a70fab9..1c64d71 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"మీడియాను ప్రాసెస్ చేస్తోంది…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"రద్దు చేయండి"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"వేచి ఉండు"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"భద్రత రక్షణ"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"స్థానిక ట్రాన్స్‌కోడ్ అలర్ట్‌లు"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"స్థానిక ట్రాన్స్‌కోడ్ ప్రోగ్రెస్"</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index b666c7c..7112494 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"กำลังประมวลผลสื่อ…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"ยกเลิก"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"รอ"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"การปกป้องเพื่อความปลอดภัย"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index ac006cc..8820e5f 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Pinoproseso ang media…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Kanselahin"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Maghintay"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Proteksyon sa kaligtasan"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index b03bb29..f568d01 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Medya işleniyor…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"İptal"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Bekle"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Güvenlik koruması"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 39ff525..1ec73d1 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Обробка медіафайлів…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Скасувати"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Зачекати"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Захист"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index ae76c01..09d7810 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -120,4 +120,7 @@
     <string name="transcode_processing" msgid="6753136468864077258">"میڈیا پر کارروائی ہو رہی ہے…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"منسوخ کریں"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"انتظار کریں"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"سیفٹی پروٹیکشن"</string>
+    <string name="transcode_alert_channel" msgid="997332371757680478">"مقامی ٹرانسکوڈ کے الرٹس"</string>
+    <string name="transcode_progress_channel" msgid="6905136787933058387">"مقامی ٹرانسکوڈ کی پیشرفت"</string>
 </resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index d3babbb..a535ae5 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Mediaga ishlov berilmoqda…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Bekor qilish"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Kutish"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Xavfsizlik himoyasi"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 84d8836..f9da470 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Đang xử lý nội dung nghe nhìn..."</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Hủy"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Đợi"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Bảo vệ an toàn"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index b256d48..9558b5f 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"正在处理媒体内容…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"取消"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"等待"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"安全保护"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index cbc74c5..cad3528 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"正在處理媒體…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"取消"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"等待"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"安全保護"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index a1e6f78..6434772 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"正在處理媒體…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"取消"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"等待"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"安全防護"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 1fd0029..5f12bef 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -120,4 +120,9 @@
     <string name="transcode_processing" msgid="6753136468864077258">"Icubungula imidiya…"</string>
     <string name="transcode_cancel" msgid="8555752601907598192">"Khansela"</string>
     <string name="transcode_wait" msgid="8909773149560697501">"Linda"</string>
+    <string name="safety_protection_icon_label" msgid="6714354052747723623">"Ukuvikeleka kokuphepha"</string>
+    <!-- no translation found for transcode_alert_channel (997332371757680478) -->
+    <skip />
+    <!-- no translation found for transcode_progress_channel (6905136787933058387) -->
+    <skip />
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d6b8deb..1b3efa8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -406,4 +406,13 @@
 
     <!-- Transcode intermediate ANR dialog wait button. -->
     <string name="transcode_wait">Wait</string>
+
+    <!-- Safety Protection shield icon. -->
+    <string name="safety_protection_icon_label">Safety protection</string>
+
+    <!-- Transcode alert channel name. -->
+    <string name="transcode_alert_channel">Native Transcode Alerts</string>
+
+    <!-- Transcode progress channel name. -->
+    <string name="transcode_progress_channel">Native Transcode Progress</string>
 </resources>
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index e4700b0..90ad9c7 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -853,17 +853,21 @@
      */
     public void notifyChange(@NonNull Uri uri, int flags) {
         if (LOGV) Log.v(TAG, "Notifying " + uri);
+
+        // Also sync change to the network.
+        final int notifyFlags = flags | ContentResolver.NOTIFY_SYNC_TO_NETWORK;
+
         final TransactionState state = mTransactionState.get();
         if (state != null) {
-            ArraySet<Uri> set = state.notifyChanges.get(flags);
+            ArraySet<Uri> set = state.notifyChanges.get(notifyFlags);
             if (set == null) {
                 set = new ArraySet<>();
-                state.notifyChanges.put(flags, set);
+                state.notifyChanges.put(notifyFlags, set);
             }
             set.add(uri);
         } else {
             ForegroundThread.getExecutor().execute(() -> {
-                notifySingleChangeInternal(uri, flags);
+                notifySingleChangeInternal(uri, notifyFlags);
             });
         }
     }
@@ -1529,7 +1533,8 @@
                         + "||':'||new._id||':'||new.media_type||':'||new.is_download"
                         + "||':'||old.is_trashed||':'||new.is_trashed"
                         + "||':'||old.is_pending||':'||new.is_pending"
-                        + "||':'||old.is_favorite||':'||new.is_favorite"
+                        + "||':'||ifnull(old.is_favorite,0)"
+                        + "||':'||ifnull(new.is_favorite,0)"
                         + "||':'||ifnull(old._special_format,0)"
                         + "||':'||ifnull(new._special_format,0)"
                         + "||':'||ifnull(old.owner_package_name,'null')"
@@ -1877,7 +1882,7 @@
     static final int VERSION_S = 1209;
     // Leave some gaps in database version tagging to allow S schema changes
     // to go independent of T schema changes.
-    static final int VERSION_T = 1307;
+    static final int VERSION_T = 1308;
     public static final int VERSION_LATEST = VERSION_T;
 
     /**
@@ -2074,6 +2079,9 @@
                 // This is to ensure Animated Webp files are tagged
                 updateSpecialFormatToNotDetected(db);
             }
+            if (fromVersion < 1308) {
+                // Empty version bump to ensure triggers are recreated
+            }
 
             // If this is the legacy database, it's not worth recomputing data
             // values locally, since they'll be recomputed after the migration
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 2db5a31..d822966 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_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;
@@ -43,7 +44,6 @@
 import static android.provider.MediaStore.QUERY_ARG_MATCH_TRASHED;
 import static android.provider.MediaStore.QUERY_ARG_REDACTED_URI;
 import static android.provider.MediaStore.QUERY_ARG_RELATED_URI;
-import static android.provider.MediaStore.VOLUME_EXTERNAL;
 import static android.provider.MediaStore.getVolumeName;
 import static android.system.OsConstants.F_GETFL;
 
@@ -627,6 +627,7 @@
                         if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
                             mUserCache.invalidateWorkProfileOwnerApps(pkg);
                             mPickerSyncController.notifyPackageRemoval(pkg);
+                            invalidateDentryForExternalStorage(pkg);
                         }
                     } else {
                         Log.w(TAG, "Failed to retrieve package from intent: " + intent.getAction());
@@ -636,6 +637,18 @@
         }
     };
 
+    private void invalidateDentryForExternalStorage(String packageName) {
+        for (MediaVolume vol : mVolumeCache.getExternalVolumes()) {
+            try {
+                invalidateFuseDentry(String.format(Locale.ROOT,
+                        "%s/Android/media/%s/", getVolumePath(vol.getName()).getAbsolutePath(),
+                        packageName));
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, "External volume path not found for " + vol.getName(), e);
+            }
+        }
+    }
+
     private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -740,7 +753,7 @@
                     insertedRow.getId(), insertedRow.getMediaType(), insertedRow.isDownload());
             updateNextRowIdXattr(helper, insertedRow.getId());
             helper.postBackground(() -> {
-                if (helper.isExternal()) {
+                if (helper.isExternal() && !isFuseThread()) {
                     // Update the quota type on the filesystem
                     Uri fileUri = MediaStore.Files.getContentUri(insertedRow.getVolumeName(),
                             insertedRow.getId());
@@ -1312,7 +1325,8 @@
         // Cleaning media files for users that have been removed
         cleanMediaFilesForRemovedUser(signal);
 
-        // Populate _SPECIAL_FORMAT column for files which have column value as NULL
+        // Calculate standard_mime_type_extension column for files which have SPECIAL_FORMAT column
+        // value as NULL, and update the same in the picker db
         detectSpecialFormat(signal);
 
         final long durationMillis = (SystemClock.elapsedRealtime() - startTime);
@@ -1426,9 +1440,14 @@
     private void updateSpecialFormatColumn(SQLiteDatabase db, @NonNull CancellationSignal signal) {
         // This is to ensure we only do a bounded iteration over the rows as updates can fail, and
         // we don't want to keep running the query/update indefinitely.
-        final int totalRowsToUpdate = getPendingSpecialFormatRowsCount(db,signal);
-        for (int i = 0 ; i < totalRowsToUpdate ; i += IDLE_MAINTENANCE_ROWS_LIMIT) {
-            updateSpecialFormatForLimitedRows(db, signal);
+        final int totalRowsToUpdate = getPendingSpecialFormatRowsCount(db, signal);
+        for (int i = 0; i < totalRowsToUpdate; i += IDLE_MAINTENANCE_ROWS_LIMIT) {
+            try (PickerDbFacade.UpdateMediaOperation operation =
+                         mPickerDbFacade.beginUpdateMediaOperation(
+                                 PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY)) {
+                updateSpecialFormatForLimitedRows(db, signal, operation);
+                operation.setSuccess();
+            }
         }
     }
 
@@ -1442,14 +1461,12 @@
         }
     }
 
-    private void updateSpecialFormatForLimitedRows(SQLiteDatabase db,
-            @NonNull CancellationSignal signal) {
-        final SQLiteQueryBuilder qbForUpdate = getQueryBuilder(TYPE_UPDATE, FILES,
-                Files.getContentUri(VOLUME_EXTERNAL), Bundle.EMPTY, null);
+    private void updateSpecialFormatForLimitedRows(SQLiteDatabase externalDb,
+            @NonNull CancellationSignal signal, PickerDbFacade.UpdateMediaOperation operation) {
         // Accumulate all the new SPECIAL_FORMAT updates with their ids
         ArrayMap<Long, Integer> newSpecialFormatValues = new ArrayMap<>();
         final String limit = String.valueOf(IDLE_MAINTENANCE_ROWS_LIMIT);
-        try (Cursor c = queryForPendingSpecialFormatColumns(db, limit, signal)) {
+        try (Cursor c = queryForPendingSpecialFormatColumns(externalDb, limit, signal)) {
             while (c.moveToNext() && !signal.isCanceled()) {
                 final long id = c.getLong(0);
                 final String path = c.getString(1);
@@ -1457,25 +1474,35 @@
             }
         }
 
-        // Now, update all the new SPECIAL_FORMAT values.
-        final ContentValues values = new ContentValues();
+        // Now, update all the new SPECIAL_FORMAT values in both external db and picker db.
+        final ContentValues pickerDbValues = new ContentValues();
+        final ContentValues externalDbValues = new ContentValues();
         int count = 0;
-        for (long id: newSpecialFormatValues.keySet()) {
+        for (long id : newSpecialFormatValues.keySet()) {
             if (signal.isCanceled()) {
                 return;
             }
 
-            values.clear();
-            values.put(_SPECIAL_FORMAT, newSpecialFormatValues.get(id));
-            final String selection = MediaColumns._ID + "=?";
-            final String[] selectionArgs = new String[]{String.valueOf(id)};
-            if (qbForUpdate.update(db, values, selection, selectionArgs) == 1) {
+            int specialFormat = newSpecialFormatValues.get(id);
+
+            pickerDbValues.clear();
+            pickerDbValues.put(PickerDbFacade.KEY_STANDARD_MIME_TYPE_EXTENSION, specialFormat);
+            boolean pickerDbWriteSuccess = operation.execute(String.valueOf(id), pickerDbValues);
+
+            externalDbValues.clear();
+            externalDbValues.put(_SPECIAL_FORMAT, specialFormat);
+            final String externalDbSelection = MediaColumns._ID + "=?";
+            final String[] externalDbSelectionArgs = new String[]{String.valueOf(id)};
+            boolean externalDbWriteSuccess =
+                    externalDb.update("files", externalDbValues, externalDbSelection,
+                            externalDbSelectionArgs)
+                            == 1;
+
+            if (pickerDbWriteSuccess && externalDbWriteSuccess) {
                 count++;
-            } else {
-                Log.e(TAG, "Unable to update _SPECIAL_FORMAT for id = " + id);
             }
         }
-        Log.d(TAG, "Updated _SPECIAL_FORMAT for " + count + " items");
+        Log.d(TAG, "Updated standard_mime_type_extension for " + count + " items");
     }
 
     private int getSpecialFormatValue(String path) {
@@ -1975,6 +2002,8 @@
         }
     }
 
+    /** TODO(b/242153950) :Add negative tests for permission check of file lookup of synthetic
+     * paths. */
     private FileLookupResult handlePickerFileLookup(int userId, int uid, @NonNull String path) {
         final File file = new File(path);
         final List<String> syntheticRelativePathSegments =
@@ -2019,7 +2048,8 @@
                 // .../picker/<user-id>/<authority>/media/<media-id.extension>
                 final String fileUserId = syntheticRelativePathSegments.get(1);
                 final String authority = syntheticRelativePathSegments.get(2);
-                result = preparePickerMediaIdPathSegment(file, authority, lastSegment, fileUserId);
+                result = preparePickerMediaIdPathSegment(file, authority, lastSegment, fileUserId,
+                        uid);
                 break;
         }
 
@@ -2092,14 +2122,13 @@
     }
 
     private boolean preparePickerMediaIdPathSegment(File file, String authority, String fileName,
-            String userId) {
+            String userId, int uid) {
         final String mediaId = extractFileName(fileName);
         final String[] projection = new String[] { MediaStore.PickerMediaColumns.SIZE };
 
         final Uri uri = Uri.parse("content://media/picker/" + userId + "/" + authority + "/media/"
                 + mediaId);
-        try (Cursor cursor =  mPickerUriResolver.query(uri, projection, /* callingUid */0,
-                android.os.Process.myUid())) {
+        try (Cursor cursor = mPickerUriResolver.query(uri, projection, /* callingPid */0, uid)) {
             if (cursor != null && cursor.moveToFirst()) {
                 final int sizeBytesIdx = cursor.getColumnIndex(MediaStore.PickerMediaColumns.SIZE);
 
@@ -2647,9 +2676,18 @@
         boolean allowHidden = isCallingPackageAllowedHidden();
         final SQLiteQueryBuilder qbForUpdate = getQueryBuilder(TYPE_UPDATE,
                 matchUri(uriOldPath, allowHidden), uriOldPath, qbExtras, null);
+
+        // uriOldPath may use Files uri which doesn't allow modifying AudioColumns. Include
+        // AudioColumns projection map if we are modifying any audio columns while renaming
+        // database rows.
+        if (values.containsKey(AudioColumns.IS_RINGTONE)) {
+            qbForUpdate.setProjectionMap(getProjectionMap(AudioColumns.class, FileColumns.class));
+        }
+
         if (values.containsKey(FileColumns._MODIFIER)) {
             qbForUpdate.allowColumn(FileColumns._MODIFIER);
         }
+
         final String selection = MediaColumns.DATA + " =? ";
         int count = 0;
         boolean retryUpdateWithReplace = false;
@@ -2722,12 +2760,12 @@
             values.put(FileColumns._MODIFIER, FileColumns._MODIFIER_FUSE);
         }
 
-        final boolean allowHidden = isCallingPackageAllowedHidden();
-        if (!newMimeType.equalsIgnoreCase("null") &&
-                matchUri(getContentUriForFile(path, newMimeType), allowHidden) == AUDIO_MEDIA) {
+        if (MimeUtils.isAudioMimeType(newMimeType) && !values.containsKey(FileColumns._MODIFIER)) {
             computeAudioLocalizedValues(values);
             computeAudioKeyValues(values);
+            FileUtils.computeAudioTypeValuesFromData(path, values::put);
         }
+
         FileUtils.computeValuesFromData(values, isFuseThread());
         return values;
     }
@@ -3128,27 +3166,31 @@
                 return OsConstants.EPERM;
             }
 
-            // TODO(b/177049768): We shouldn't use getExternalStorageDirectory for these checks.
-            final File directoryAndroid = new File(Environment.getExternalStorageDirectory(),
-                    DIRECTORY_ANDROID_LOWER_CASE);
+            final File directoryAndroid = new File(
+                    extractVolumePath(oldPath).toLowerCase(Locale.ROOT),
+                    DIRECTORY_ANDROID_LOWER_CASE
+            );
             final File directoryAndroidMedia = new File(directoryAndroid, DIRECTORY_MEDIA);
+            String newPathLowerCase = newPath.toLowerCase(Locale.ROOT);
             if (directoryAndroidMedia.getAbsolutePath().equalsIgnoreCase(oldPath)) {
                 // Don't allow renaming 'Android/media' directory.
                 // Android/[data|obb] are bind mounted and these paths don't go through FUSE.
                 Log.e(TAG, errorMessage +  oldPath + " is a default folder in app external "
                         + "directory. Renaming a default folder is not allowed.");
                 return OsConstants.EPERM;
-            } else if (FileUtils.contains(directoryAndroid, new File(newPath))) {
-                if (newRelativePath.length == 1) {
-                    // New path is Android/*. Path is directly under Android. Don't allow moving
-                    // files and directories to Android/.
+            } else if (FileUtils.contains(directoryAndroid, new File(newPathLowerCase))) {
+                if (newRelativePath.length <= 2) {
+                    // Path is directly under Android, Android/media, Android/data, Android/obb or
+                    // some other directory under Android. Don't allow moving files and directories
+                    // in these paths. Files and directories are only allowed to move to path
+                    // Android/media/<app_specific_directory>/*
                     Log.e(TAG, errorMessage +  newPath + " is in app external directory. "
                             + "Renaming a file/directory to app external directory is not "
                             + "allowed.");
                     return OsConstants.EPERM;
-                } else if(!FileUtils.contains(directoryAndroidMedia, new File(newPath))) {
-                    // New path is  Android/*/*. Don't allow moving of files or directories
-                    // to app external directory other than media directory.
+                } else if (!FileUtils.contains(directoryAndroidMedia, new File(newPathLowerCase))) {
+                    // New path is not in Android/media/*. Don't allow moving of files or
+                    // directories to app external directory other than media directory.
                     Log.e(TAG, errorMessage +  newPath + " is not in external media directory."
                             + "File/directory can only be renamed to a path in external media "
                             + "directory. Renaming file/directory to path in other external "
@@ -3971,8 +4013,8 @@
                 // gallery is not allowed to create non-default top level directory.
                 final boolean createNonDefaultTopLevelDir = primary != null &&
                         !FileUtils.buildPath(volumePath, primary).exists();
-                validPath = !createNonDefaultTopLevelDir && canAccessMediaFile(
-                        res.getAbsolutePath(), /*excludeNonSystemGallery*/ true);
+                validPath = !createNonDefaultTopLevelDir && canSystemGalleryAccessTheFile(
+                        res.getAbsolutePath());
             }
 
             // Nothing left to check; caller can't use this path
@@ -6676,6 +6718,7 @@
                 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);
@@ -7362,14 +7405,14 @@
                                 helper.postBackground(() -> {
                                     scanFileAsMediaProvider(file, REASON_DEMAND);
                                     if (notifyTranscodeHelper) {
-                                        notifyTranscodeHelperOnUriPublished(updatedUri);
+                                        notifyTranscodeHelperOnUriPublished(updatedUri, file);
                                     }
                                 });
                             } else {
                                 helper.postBlocking(() -> {
                                     scanFileAsMediaProvider(file, REASON_DEMAND);
                                     if (notifyTranscodeHelper) {
-                                        notifyTranscodeHelperOnUriPublished(updatedUri);
+                                        notifyTranscodeHelperOnUriPublished(updatedUri, file);
                                     }
                                 });
                             }
@@ -7397,9 +7440,8 @@
         }
 
         // 2. Check if the calling package is a special app which has global access
-        if (isCallingPackageManager() ||
-                (canAccessMediaFile(srcPath, /* excludeNonSystemGallery */ true) &&
-                        (canAccessMediaFile(destPath, /* excludeNonSystemGallery */ true)))) {
+        if (isCallingPackageManager() || (canSystemGalleryAccessTheFile(srcPath) &&
+            (canSystemGalleryAccessTheFile(destPath)))) {
             return true;
         }
 
@@ -7424,7 +7466,11 @@
         return isSrcUpdateAllowed && isDestUpdateAllowed;
     }
 
-    private void notifyTranscodeHelperOnUriPublished(Uri uri) {
+    private void notifyTranscodeHelperOnUriPublished(Uri uri, File file) {
+        if (!mTranscodeHelper.supportsTranscode(file.getPath())) {
+            return;
+        }
+
         BackgroundThread.getExecutor().execute(() -> {
             final LocalCallingIdentity token = clearLocalCallingIdentity();
             try {
@@ -7437,6 +7483,10 @@
 
     private void notifyTranscodeHelperOnFileOpen(String path, String ioPath, int uid,
             int transformsReason) {
+        if (!mTranscodeHelper.supportsTranscode(path)) {
+            return;
+        }
+
         BackgroundThread.getExecutor().execute(() -> {
             final LocalCallingIdentity token = clearLocalCallingIdentity();
             try {
@@ -7818,8 +7868,6 @@
     }
 
     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;
     }
@@ -8299,10 +8347,9 @@
             requireOwnershipForItem(ownerPackageName, uri);
         }
 
-        final boolean callerIsOwner = Objects.equals(getCallingPackageOrSelf(), ownerPackageName);
         // Figure out if we need to redact contents
-        final boolean redactionNeeded =
-                (redactedUri != null) || (!callerIsOwner && isRedactionNeeded(uri));
+        final boolean redactionNeeded = isRedactionNeededForOpenViaContentResolver(redactedUri,
+                ownerPackageName, file);
         final RedactionInfo redactionInfo;
         try {
             redactionInfo = redactionNeeded ? getRedactionRanges(file)
@@ -8420,6 +8467,27 @@
         }
     }
 
+    private boolean isRedactionNeededForOpenViaContentResolver(Uri redactedUri,
+            String ownerPackageName, File file) {
+        // Redacted Uris should always redact information
+        if (redactedUri != null) {
+            return true;
+        }
+
+        final boolean callerIsOwner = Objects.equals(getCallingPackageOrSelf(), ownerPackageName);
+        if (callerIsOwner) {
+            return false;
+        }
+
+        // To be consistent with FUSE redaction checks we allow similar access for File Manager
+        // and System Gallery apps.
+        if (isCallingPackageManager() || canSystemGalleryAccessTheFile(file.getPath())) {
+            return false;
+        }
+
+        return isRedactionNeeded();
+    }
+
     private void deleteAndInvalidate(@NonNull Path path) {
         deleteAndInvalidate(path.toFile());
     }
@@ -8431,7 +8499,7 @@
 
     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) {
@@ -8478,9 +8546,6 @@
                 // We bypass db operations for legacy system galleries with W_E_S (see b/167307393).
                 // Tracking a longer term solution in b/168784136.
                 return true;
-            } else if (isCallingPackageRequestingLegacy()) {
-                // If requesting legacy, app should have W_E_S along with SystemGallery appops.
-                return false;
             } else if (!SdkLevel.isAtLeastS()) {
                 // We don't parse manifest flags for SdkLevel<=R yet. Hence, we don't bypass
                 // database updates for SystemGallery targeting R or above on R OS.
@@ -8497,25 +8562,23 @@
         return MimeUtils.resolveMediaType(mimeType);
     }
 
-    private boolean canAccessMediaFile(String filePath, boolean excludeNonSystemGallery) {
-        if (excludeNonSystemGallery && !isCallingPackageSystemGallery()) {
+    private boolean canSystemGalleryAccessTheFile(String filePath) {
+
+        if (!isCallingPackageSystemGallery()) {
             return false;
         }
-        switch (getFileMediaType(filePath)) {
-            case FileColumns.MEDIA_TYPE_IMAGE:
-                return mCallingIdentity.get().hasPermission(PERMISSION_WRITE_IMAGES);
-            case FileColumns.MEDIA_TYPE_VIDEO:
-                return mCallingIdentity.get().hasPermission(PERMISSION_WRITE_VIDEO);
-            default:
-                return false;
-        }
+
+        final int mediaType = getFileMediaType(filePath);
+
+        return mediaType ==  FileColumns.MEDIA_TYPE_IMAGE ||
+            mediaType ==  FileColumns.MEDIA_TYPE_VIDEO;
     }
 
     /**
      * Returns true if:
      * <ul>
      * <li>the calling identity is an app targeting Q or older versions AND is requesting legacy
-     * storage
+     * storage and has the corresponding legacy access (read/write) permissions
      * <li>the calling identity holds {@code MANAGE_EXTERNAL_STORAGE}
      * <li>the calling identity owns or has access to the filePath (eg /Android/data/com.foo)
      * <li>the calling identity has permission to write images and the given file is an image file
@@ -8540,7 +8603,7 @@
 
         // Apps with write access to images and/or videos can bypass our restrictions if all of the
         // the files they're accessing are of the compatible media type.
-        if (canAccessMediaFile(filePath, /*excludeNonSystemGallery*/ false)) {
+        if (canSystemGalleryAccessTheFile(filePath)) {
             return true;
         }
 
@@ -10616,7 +10679,13 @@
     }
 
     private boolean isCallingPackageSystemGallery() {
-        return mCallingIdentity.get().hasPermission(PERMISSION_IS_SYSTEM_GALLERY);
+        if (mCallingIdentity.get().hasPermission(PERMISSION_IS_SYSTEM_GALLERY)) {
+            if (isCallingPackageRequestingLegacy()) {
+                return isCallingPackageLegacyWrite();
+            }
+            return true;
+        }
+        return false;
     }
 
     private int getCallingUidOrSelf() {
@@ -10686,6 +10755,37 @@
         mTranscodeHelper.dump(writer);
         writer.println();
 
+        dumpNoMedia(writer);
+        writer.println();
+
         Logging.dumpPersistent(writer);
     }
+
+    private void dumpNoMedia(PrintWriter writer) {
+        final DatabaseHelper helper;
+        try {
+            helper = getDatabaseForUri(MediaStore.Files.EXTERNAL_CONTENT_URI);
+        } catch (VolumeNotFoundException e) {
+            Log.w(TAG, "Volume not found", e);
+            return;
+        }
+
+        writer.println(MediaStore.VOLUME_EXTERNAL + " nomedia files:");
+        final int noMediaDumpFrequency = 100;
+
+        try (Cursor cursor = helper.runWithoutTransaction(
+                db -> db.query("files", new String[]{FileColumns.DATA},
+                        FileColumns.DATA + " LIKE '%.nomedia'", null, null, null, null))) {
+            final int dataColumnIndex = cursor.getColumnIndex(FileColumns.DATA);
+            final StringBuilder nomediaPaths = new StringBuilder();
+            while (cursor.moveToNext()) {
+                nomediaPaths.append(cursor.getString(dataColumnIndex)).append("\n");
+                if (cursor.getPosition() % noMediaDumpFrequency == 0) {
+                    writer.print(nomediaPaths);
+                    nomediaPaths.setLength(0);
+                }
+            }
+            writer.println(nomediaPaths);
+        }
+    }
 }
diff --git a/src/com/android/providers/media/TranscodeHelperImpl.java b/src/com/android/providers/media/TranscodeHelperImpl.java
index 09e132f..8b7ae40 100644
--- a/src/com/android/providers/media/TranscodeHelperImpl.java
+++ b/src/com/android/providers/media/TranscodeHelperImpl.java
@@ -57,9 +57,9 @@
 import android.media.MediaFeature;
 import android.media.MediaFormat;
 import android.media.MediaTranscodingManager;
-import android.media.MediaTranscodingManager.VideoTranscodingRequest;
 import android.media.MediaTranscodingManager.TranscodingRequest.VideoFormatResolver;
 import android.media.MediaTranscodingManager.TranscodingSession;
+import android.media.MediaTranscodingManager.VideoTranscodingRequest;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -101,7 +101,6 @@
 
 import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -116,8 +115,8 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Set;
 import java.util.Optional;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -1074,7 +1073,7 @@
     }
 
     public void onFileOpen(String path, String ioPath, int uid, int transformsReason) {
-        if (!isTranscodeEnabled()) {
+        if (!isTranscodeEnabled() || !supportsTranscode(path)) {
             return;
         }
 
@@ -1090,43 +1089,41 @@
         };
 
         try (Cursor c = queryFileForTranscode(path, resolverInfoProjection)) {
-            if (c != null && c.moveToNext()) {
-                if (supportsTranscode(path)
-                        && isModernFormat(c.getString(0), c.getInt(6), c.getInt(7))) {
-                    if (transformsReason == 0) {
-                        MediaProviderStatsLog.write(
-                                TRANSCODING_DATA,
-                                getMetricsSafeNameForUid(uid) /* owner_package_name */,
-                                MediaProviderStatsLog.TRANSCODING_DATA__ACCESS_TYPE__READ_DIRECT,
-                                c.getLong(1) /* file size */,
-                                TRANSCODING_DATA__TRANSCODE_RESULT__UNDEFINED,
-                                -1 /* transcoding_duration */,
-                                c.getLong(2) /* video_duration */,
-                                c.getLong(3) /* capture_framerate */,
-                                -1 /* transcode_reason */,
-                                c.getLong(4) /* width */,
-                                c.getLong(5) /* height */,
-                                false /*hit_anr*/,
-                                TRANSCODING_DATA__FAILURE_CAUSE__CAUSE_UNKNOWN,
-                                TranscodingSession.ERROR_NONE);
-                    } else if (isTranscodeFileCached(path, ioPath)) {
-                            MediaProviderStatsLog.write(
-                                    TRANSCODING_DATA,
-                                    getMetricsSafeNameForUid(uid) /* owner_package_name */,
-                                    MediaProviderStatsLog.TRANSCODING_DATA__ACCESS_TYPE__READ_CACHE,
-                                    c.getLong(1) /* file size */,
-                                    TRANSCODING_DATA__TRANSCODE_RESULT__UNDEFINED,
-                                    -1 /* transcoding_duration */,
-                                    c.getLong(2) /* video_duration */,
-                                    c.getLong(3) /* capture_framerate */,
-                                    transformsReason /* transcode_reason */,
-                                    c.getLong(4) /* width */,
-                                    c.getLong(5) /* height */,
-                                    false /*hit_anr*/,
-                                    TRANSCODING_DATA__FAILURE_CAUSE__CAUSE_UNKNOWN,
-                                    TranscodingSession.ERROR_NONE);
-                    } // else if file is not in cache, we'll log at read(2) when we transcode
-                }
+            if (c != null && c.moveToNext()
+                    && isModernFormat(c.getString(0), c.getInt(6), c.getInt(7))) {
+                if (transformsReason == 0) {
+                    MediaProviderStatsLog.write(
+                            TRANSCODING_DATA,
+                            getMetricsSafeNameForUid(uid) /* owner_package_name */,
+                            MediaProviderStatsLog.TRANSCODING_DATA__ACCESS_TYPE__READ_DIRECT,
+                            c.getLong(1) /* file size */,
+                            TRANSCODING_DATA__TRANSCODE_RESULT__UNDEFINED,
+                            -1 /* transcoding_duration */,
+                            c.getLong(2) /* video_duration */,
+                            c.getLong(3) /* capture_framerate */,
+                            -1 /* transcode_reason */,
+                            c.getLong(4) /* width */,
+                            c.getLong(5) /* height */,
+                            false /*hit_anr*/,
+                            TRANSCODING_DATA__FAILURE_CAUSE__CAUSE_UNKNOWN,
+                            TranscodingSession.ERROR_NONE);
+                } else if (isTranscodeFileCached(path, ioPath)) {
+                    MediaProviderStatsLog.write(
+                            TRANSCODING_DATA,
+                            getMetricsSafeNameForUid(uid) /* owner_package_name */,
+                            MediaProviderStatsLog.TRANSCODING_DATA__ACCESS_TYPE__READ_CACHE,
+                            c.getLong(1) /* file size */,
+                            TRANSCODING_DATA__TRANSCODE_RESULT__UNDEFINED,
+                            -1 /* transcoding_duration */,
+                            c.getLong(2) /* video_duration */,
+                            c.getLong(3) /* capture_framerate */,
+                            transformsReason /* transcode_reason */,
+                            c.getLong(4) /* width */,
+                            c.getLong(5) /* height */,
+                            false /*hit_anr*/,
+                            TRANSCODING_DATA__FAILURE_CAUSE__CAUSE_UNKNOWN,
+                            TranscodingSession.ERROR_NONE);
+                } // else if file is not in cache, we'll log at read(2) when we transcode
             }
         } catch (IllegalStateException e) {
             Log.w(TAG, "Unable to log metrics on file open", e);
@@ -1657,7 +1654,8 @@
                 errorCode = mErrorCode;
             }
 
-            return String.format("<%s. Src: %s. Dst: %s. BlockedUids: %s. DurationMs: %sms"
+            return String.format(Locale.ROOT,
+                    "<%s. Src: %s. Dst: %s. BlockedUids: %s. DurationMs: %sms"
                     + ". Start: %s. Finish: %sms. HasAnr: %b. FailureReason: %d. ErrorCode: %d>",
                     session.toString(), mSrcPath, mDstPath, session.getClientUids(), durationMs,
                     startTime, finishTime, hasAnr, failureReason, errorCode);
@@ -1669,10 +1667,8 @@
         private static final int ALERT_DISMISS_DELAY_MS = 1000;
         private static final int SHOW_PROGRESS_THRESHOLD_TIME_MS = 1000;
         private static final String TRANSCODE_ALERT_CHANNEL_ID = "native_transcode_alert_channel";
-        private static final String TRANSCODE_ALERT_CHANNEL_NAME = "Native Transcode Alerts";
         private static final String TRANSCODE_PROGRESS_CHANNEL_ID =
                 "native_transcode_progress_channel";
-        private static final String TRANSCODE_PROGRESS_CHANNEL_NAME = "Native Transcode Progress";
 
         // Related to notification settings
         private static final String TRANSCODE_NOTIFICATION_SYS_PROP_KEY =
@@ -1794,7 +1790,8 @@
 
         private static void createAlertNotificationChannel(Context context) {
             NotificationChannel channel = new NotificationChannel(TRANSCODE_ALERT_CHANNEL_ID,
-                    TRANSCODE_ALERT_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
+                    getString(context, R.string.transcode_alert_channel),
+                    NotificationManager.IMPORTANCE_HIGH);
             NotificationManager notificationManager = context.getSystemService(
                     NotificationManager.class);
             notificationManager.createNotificationChannel(channel);
@@ -1802,7 +1799,8 @@
 
         private static void createProgressNotificationChannel(Context context) {
             NotificationChannel channel = new NotificationChannel(TRANSCODE_PROGRESS_CHANNEL_ID,
-                    TRANSCODE_PROGRESS_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
+                    getString(context, R.string.transcode_progress_channel),
+                    NotificationManager.IMPORTANCE_LOW);
             NotificationManager notificationManager = context.getSystemService(
                     NotificationManager.class);
             notificationManager.createNotificationChannel(channel);
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
index a4e121c..724e474 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.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;
@@ -103,6 +104,7 @@
 
     private int mToolbarHeight = 0;
     private boolean mIsAccessibilityEnabled;
+    private boolean mShouldLogCancelledResult = true;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -139,8 +141,8 @@
         try {
             mPickerViewModel.parseValuesFromIntent(intent);
         } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Finished activity due to an exception while parsing extras", e);
-            setCancelledResultAndFinishSelf();
+            Log.e(TAG, "Finish activity due to an exception while parsing extras", e);
+            finishWithoutLoggingCancelledResult();
         }
 
         mDragBar = findViewById(R.id.drag_bar);
@@ -254,8 +256,8 @@
         // 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;
         }
@@ -265,8 +267,11 @@
         }
     }
 
-    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() {
@@ -274,7 +279,12 @@
 
         startActivityAsUser(getDocumentsUiForwardingIntent(this, getIntent()),
                 UserId.CURRENT_USER.getUserHandle());
-        finish();
+        // RESULT_CANCELLED is not returned to the calling app as the DocumentsUi result will be
+        // returned. We don't have to log as this flow can be called in 2 cases:
+        // 1. GET_CONTENT had non-media filters, so the user or the app should be unaffected as they
+        // see that DocumentsUi was opened directly.
+        // 2. User clicked on "Browse.." button, in that case we already log that event separately.
+        finishWithoutLoggingCancelledResult();
     }
 
     @VisibleForTesting
@@ -422,12 +432,37 @@
     public void setResultAndFinishSelf() {
         setResult(Activity.RESULT_OK, getPickerResponseIntent(mSelection.canSelectMultiple(),
                 mSelection.getSelectedItems()));
+
+        logPickerSelectionConfirmed(mSelection.getSelectedItems().size());
+        finishWithoutLoggingCancelledResult();
+    }
+
+    /**
+     * This should be called if:
+     * * We are finishing Picker explicitly before the user has seen PhotoPicker UI due to known
+     *   checks/workflow.
+     * * We are not returning {@link Activity#RESULT_CANCELED}
+     */
+    private void finishWithoutLoggingCancelledResult() {
+        mShouldLogCancelledResult = false;
         finish();
     }
 
-    private void setCancelledResultAndFinishSelf() {
-        setResult(Activity.RESULT_CANCELED);
-        finish();
+    @Override
+    public void finish() {
+        if (mShouldLogCancelledResult) {
+            logPickerCancelled();
+        }
+        super.finish();
+    }
+
+    private void logPickerSelectionConfirmed(int countOfItemsConfirmed) {
+        mPickerViewModel.logPickerConfirm(Binder.getCallingUid(), getCallingPackage(),
+                countOfItemsConfirmed);
+    }
+
+    private void logPickerCancelled() {
+        mPickerViewModel.logPickerCancel(Binder.getCallingUid(), getCallingPackage());
     }
 
     /**
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerProvider.java b/src/com/android/providers/media/photopicker/PhotoPickerProvider.java
index 28a21f5..79f7326 100644
--- a/src/com/android/providers/media/photopicker/PhotoPickerProvider.java
+++ b/src/com/android/providers/media/photopicker/PhotoPickerProvider.java
@@ -72,7 +72,7 @@
                 CloudProviderQueryExtras.fromCloudMediaBundle(extras);
 
         return mDbFacade.queryMedia(queryExtras.getGeneration(), queryExtras.getAlbumId(),
-                queryExtras.getMimeType());
+                queryExtras.getMimeTypes());
     }
 
     @Override
@@ -88,7 +88,7 @@
         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/PickerDataLayer.java b/src/com/android/providers/media/photopicker/PickerDataLayer.java
index 671d5c1..217a799 100644
--- a/src/com/android/providers/media/photopicker/PickerDataLayer.java
+++ b/src/com/android/providers/media/photopicker/PickerDataLayer.java
@@ -109,6 +109,7 @@
         final List<Cursor> cursors = new ArrayList<>();
         final Bundle cursorExtra = new Bundle();
         cursorExtra.putString(MediaStore.EXTRA_CLOUD_PROVIDER, cloudProvider);
+        cursorExtra.putString(MediaStore.EXTRA_LOCAL_PROVIDER, mLocalProvider);
 
         // Favorites and Videos are merged albums.
         final Cursor mergedAlbums = mDbFacade.getMergedAlbums(queryExtras.toQueryFilter());
diff --git a/src/com/android/providers/media/photopicker/PickerSyncController.java b/src/com/android/providers/media/photopicker/PickerSyncController.java
index d0fb856..e14cdc9 100644
--- a/src/com/android/providers/media/photopicker/PickerSyncController.java
+++ b/src/com/android/providers/media/photopicker/PickerSyncController.java
@@ -55,6 +55,7 @@
 import com.android.modules.utils.build.SdkLevel;
 import com.android.providers.media.R;
 import com.android.providers.media.photopicker.data.PickerDbFacade;
+import com.android.providers.media.photopicker.metrics.PhotoPickerUiEventLogger;
 import com.android.providers.media.util.ForegroundThread;
 import com.android.providers.media.util.StringUtils;
 
@@ -77,8 +78,10 @@
     public static final String ALLOWED_CLOUD_PROVIDERS_KEY = "allowed_cloud_providers";
 
     private static final String PREFS_KEY_CLOUD_PROVIDER_AUTHORITY = "cloud_provider_authority";
-    private static final String PREFS_KEY_CLOUD_PROVIDER_PENDING_NOTIFICATTION =
+    private static final String PREFS_KEY_CLOUD_PROVIDER_PENDING_NOTIFICATION =
             "cloud_provider_pending_notification";
+    private static final String PREFS_KEY_IS_USER_CLOUD_MEDIA_AWARE =
+            "user_aware_about_cloud_media_app_settings";
     private static final String PREFS_KEY_CLOUD_PREFIX = "cloud_provider:";
     private static final String PREFS_KEY_LOCAL_PREFIX = "local_provider:";
 
@@ -111,6 +114,8 @@
     private final Runnable mSyncAllMediaCallback;
     private final Set<String> mAllowedCloudProviders;
 
+    private final PhotoPickerUiEventLogger mLogger;
+
     @GuardedBy("mLock")
     private CloudProviderInfo mCloudProviderInfo;
 
@@ -125,13 +130,15 @@
         mLocalProvider = localProvider;
         mSyncDelayMs = syncDelayMs;
         mSyncAllMediaCallback = this::syncAllMedia;
+        mLogger = new PhotoPickerUiEventLogger();
 
         final String cachedAuthority = mUserPrefs.getString(
                 PREFS_KEY_CLOUD_PROVIDER_AUTHORITY, null);
 
         mAllowedCloudProviders = parseAllowedCloudProviders(allowedCloudProviders);
 
-        final CloudProviderInfo defaultInfo = getDefaultCloudProviderInfo(cachedAuthority);
+        final CloudProviderInfo defaultInfo = getDefaultCloudProviderInfo(cachedAuthority,
+                isUserAwareAboutCloudMediaAppSettings());
 
         if (Objects.equals(defaultInfo.authority, cachedAuthority)) {
             // Just set it without persisting since it's not changing and persisting would
@@ -268,6 +275,9 @@
                 // reset on the facade
                 mDbFacade.setCloudProvider(null);
 
+                // TODO(b/242897322): Log from PickerViewModel using its InstanceId when relevant
+                mLogger.logPickerCloudProviderChanged(newProviderInfo.uid,
+                        newProviderInfo.packageName);
                 Log.i(TAG, "Cloud provider changed successfully. Old: "
                         + oldAuthority + ". New: " + newProviderInfo.authority);
             }
@@ -386,7 +396,7 @@
         }
 
         final boolean hasPendingNotification = mUserPrefs.getBoolean(
-                PREFS_KEY_CLOUD_PROVIDER_PENDING_NOTIFICATTION, false);
+                PREFS_KEY_CLOUD_PROVIDER_PENDING_NOTIFICATION, /* defaultValue */ false);
 
         if (!hasPendingNotification || (packageName == null)) {
             Log.d(TAG, "No pending UI notification");
@@ -413,8 +423,30 @@
         });
 
         // Clear the notification
+        updateBooleanUserPref(PREFS_KEY_CLOUD_PROVIDER_PENDING_NOTIFICATION, false);
+    }
+
+    /**
+     * Notifies about cloud media app banner displayed in picker UI
+     */
+    @VisibleForTesting
+    void notifyUserCloudMediaAware() {
+        updateBooleanUserPref(PREFS_KEY_IS_USER_CLOUD_MEDIA_AWARE, true);
+    }
+
+    private void updateBooleanUserPref(String key, boolean value) {
         final SharedPreferences.Editor editor = mUserPrefs.edit();
-        editor.putBoolean(PREFS_KEY_CLOUD_PROVIDER_PENDING_NOTIFICATTION, false);
+        editor.putBoolean(key, value);
+        editor.apply();
+    }
+
+    /**
+     * Clears the flag - user aware about cloud media app settings
+     */
+    @VisibleForTesting
+    void clearUserAwareAboutCloudMediaAppSettingsFlag() {
+        final SharedPreferences.Editor editor = mUserPrefs.edit();
+        editor.remove(PREFS_KEY_IS_USER_CLOUD_MEDIA_AWARE);
         editor.apply();
     }
 
@@ -563,10 +595,10 @@
 
         if (info.isEmpty()) {
             editor.remove(PREFS_KEY_CLOUD_PROVIDER_AUTHORITY);
-            editor.putBoolean(PREFS_KEY_CLOUD_PROVIDER_PENDING_NOTIFICATTION, false);
+            editor.putBoolean(PREFS_KEY_CLOUD_PROVIDER_PENDING_NOTIFICATION, false);
         } else {
             editor.putString(PREFS_KEY_CLOUD_PROVIDER_AUTHORITY, authority);
-            editor.putBoolean(PREFS_KEY_CLOUD_PROVIDER_PENDING_NOTIFICATTION, true);
+            editor.putBoolean(PREFS_KEY_CLOUD_PROVIDER_PENDING_NOTIFICATION, true);
         }
 
         editor.apply();
@@ -724,7 +756,18 @@
                 + totalRowcount + ". Cursor count: " + cursorCount);
     }
 
-    private CloudProviderInfo getDefaultCloudProviderInfo(String cachedProvider) {
+    /**
+     * Get the default {@link CloudProviderInfo} at {@link PickerSyncController} construction
+     */
+    @VisibleForTesting
+    CloudProviderInfo getDefaultCloudProviderInfo(String cachedProvider,
+            boolean isUserAwareAboutCloudMediaAppSettings) {
+        if (cachedProvider == null && isUserAwareAboutCloudMediaAppSettings) {
+            Log.i(TAG, "Skipping default cloud provider selection since the user has made an "
+                    + "explicit empty choice");
+            return CloudProviderInfo.EMPTY;
+        }
+
         final List<CloudProviderInfo> infos =
                 getSupportedCloudProviders(/* ignoreAllowList */ false);
 
@@ -740,7 +783,7 @@
 
             if (cachedProvider != null) {
                 for (CloudProviderInfo info : infos) {
-                    if (info.authority.equals(defaultCloudProviderAuthority)) {
+                    if (info.authority.equals(cachedProvider)) {
                         return info;
                     }
                 }
@@ -759,6 +802,17 @@
         return CloudProviderInfo.EMPTY;
     }
 
+    /**
+     * @return the value of the user pref
+     * {@link PREFS_KEY_IS_USER_CLOUD_MEDIA_AWARE} with the default value as
+     * {@code false}
+     */
+    @VisibleForTesting
+    boolean isUserAwareAboutCloudMediaAppSettings() {
+        return mUserPrefs.getBoolean(PREFS_KEY_IS_USER_CLOUD_MEDIA_AWARE,
+                /* defaultValue */ false);
+    }
+
     private Set<String> parseAllowedCloudProviders(String config) {
         Set<String> allowedProviders = new ArraySet<>();
         final String[] allowedProvidersConfig = config.split(",");
diff --git a/src/com/android/providers/media/photopicker/data/CloudProviderQueryExtras.java b/src/com/android/providers/media/photopicker/data/CloudProviderQueryExtras.java
index 0100c05..4a962bb 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 @@
 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 @@
 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 @@
     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 @@
         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,18 +71,16 @@
         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;
         final int limit = bundle.getInt(MediaStore.QUERY_ARG_LIMIT, LIMIT_DEFAULT);
 
-        final boolean isFavorite = localProvider.equals(albumAuthority)
-                && AlbumColumns.ALBUM_ID_FAVORITES.equals(albumId);
-        final boolean isVideo = localProvider.equals(albumAuthority)
-                && AlbumColumns.ALBUM_ID_VIDEOS.equals(albumId);
+        final boolean isFavorite = AlbumColumns.ALBUM_ID_FAVORITES.equals(albumId);
+        final boolean isVideo = 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 +92,8 @@
         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 +103,14 @@
         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 +120,7 @@
     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 +134,8 @@
         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 d849c1f..98f0693 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.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 @@
      * 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 @@
                 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 @@
      * 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 @@
     }
 
     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 fb32872..07fed26 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.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 @@
 
     /**
      * 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 @@
      */
     @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 @@
      *                        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 @@
                 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 @@
     }
 
     @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 @@
                         + 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 3585a94..de1d6b6 100644
--- a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
+++ b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
@@ -37,8 +37,6 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
-import android.os.SystemProperties;
-import android.provider.DeviceConfig;
 import android.provider.CloudMediaProviderContract;
 import android.provider.MediaStore;
 import android.text.TextUtils;
@@ -115,7 +113,6 @@
     public static final String KEY_DURATION_MS = "duration_ms";
     @VisibleForTesting
     public static final String KEY_MIME_TYPE = "mime_type";
-    @VisibleForTesting
     public static final String KEY_STANDARD_MIME_TYPE_EXTENSION = "standard_mime_type_extension";
     @VisibleForTesting
     public static final String KEY_IS_FAVORITE = "is_favorite";
@@ -145,14 +142,6 @@
                     KEY_DATE_TAKEN_MS, KEY_DATE_TAKEN_MS, KEY_ID);
     private static final String WHERE_ALBUM_ID = KEY_ALBUM_ID  + " = ?";
 
-    private static final String[] PROJECTION_ALBUM_DB = new String[] {
-        "COUNT(" + KEY_ID + ") AS " + CloudMediaProviderContract.AlbumColumns.MEDIA_COUNT,
-        "MAX(" + KEY_DATE_TAKEN_MS + ") AS "
-        + CloudMediaProviderContract.AlbumColumns.DATE_TAKEN_MILLIS,
-        String.format("IFNULL(%s, %s) AS %s", KEY_CLOUD_ID,
-                KEY_LOCAL_ID, CloudMediaProviderContract.AlbumColumns.MEDIA_COVER_ID)
-    };
-
     // Matches all media including cloud+local, cloud-only and local-only
     private static final SQLiteQueryBuilder QB_MATCH_ALL = createMediaQueryBuilder();
     // Matches media with id
@@ -240,6 +229,10 @@
      * Returns {@link DbWriteOperation} to clear album media for a given albumId from the picker
      * db.
      *
+     * <p>The {@link DbWriteOperation} clears local or cloud album based on {@code authority} and
+     * {@code albumId}. If {@code albumId} is null, it clears all local or cloud albums based on
+     * {@code authority}.
+     *
      * @param authority to determine whether local or cloud media should be cleared
      */
     public DbWriteOperation beginResetAlbumMediaOperation(String authority, String albumId) {
@@ -247,37 +240,38 @@
     }
 
     /**
+     * Returns {@link UpdateMediaOperation} to update media belonging to {@code authority} in the
+     * picker db.
+     *
+     * @param authority to determine whether local or cloud media should be updated
+     */
+    public UpdateMediaOperation beginUpdateMediaOperation(String authority) {
+        return new UpdateMediaOperation(mDatabase, isLocal(authority));
+    }
+
+    /**
      * Represents an atomic write operation to the picker database.
      *
      * <p>This class is not thread-safe and is meant to be used within a single thread only.
      */
-    public static abstract class DbWriteOperation implements AutoCloseable {
+    public abstract static class DbWriteOperation implements AutoCloseable {
 
         private final SQLiteDatabase mDatabase;
         private final boolean mIsLocal;
-        private final String mAlbumId;
 
         private boolean mIsSuccess = false;
 
-        // Needed for Album Media Write operations.
         private DbWriteOperation(SQLiteDatabase database, boolean isLocal) {
-            this(database, isLocal, "");
-        }
-
-        // Needed for Album Media Write operations.
-        private DbWriteOperation(SQLiteDatabase database, boolean isLocal, String albumId) {
             mDatabase = database;
             mIsLocal = isLocal;
-            mAlbumId = albumId;
             mDatabase.beginTransaction();
         }
 
-        /*
-         * Execute the write operation.
+        /**
+         * Execute a write operation.
          *
          * @param cursor containing items to add/remove
-         * @return {@link WriteResult} indicating success/failure and the number of {@code cursor}
-         *          items that were inserted/updated/deleted in the picker db
+         * @return number of {@code cursor} items that were inserted/updated/deleted in the db
          * @throws {@link IllegalStateException} if no DB transaction is active
          */
         public int execute(@Nullable Cursor cursor) {
@@ -315,21 +309,17 @@
             return mIsLocal;
         }
 
-        String albumId() {
-            return mAlbumId;
-        }
-
         int updateMedia(SQLiteQueryBuilder qb, ContentValues values,
                 String[] selectionArgs) {
             try {
                 if (qb.update(mDatabase, values, /* selection */ null, selectionArgs) > 0) {
                     return SUCCESS;
                 } else {
-                    Log.d(TAG, "Failed to update picker db media. ContentValues: " + values);
+                    Log.v(TAG, "Failed to update picker db media. ContentValues: " + values);
                     return FAIL;
                 }
             } catch (SQLiteConstraintException e) {
-                Log.d(TAG, "Failed to update picker db media. ContentValues: " + values, e);
+                Log.v(TAG, "Failed to update picker db media. ContentValues: " + values, e);
                 return RETRY;
             }
         }
@@ -348,6 +338,41 @@
         }
     }
 
+    /**
+     * Represents an atomic media update operation to the picker database.
+     *
+     * <p>This class is not thread-safe and is meant to be used within a single thread only.
+     */
+    public static final class UpdateMediaOperation extends DbWriteOperation {
+
+        private UpdateMediaOperation(SQLiteDatabase database, boolean isLocal) {
+            super(database, isLocal);
+        }
+
+        /**
+         * Execute a media update operation.
+         *
+         * @param id id of the media to be updated
+         * @param contentValues key-value pairs indicating fields to be updated for the media
+         * @return boolean indicating success/failure of the update
+         * @throws {@link IllegalStateException} if no DB transaction is active
+         */
+        public boolean execute(String id, ContentValues contentValues) {
+            final SQLiteDatabase database = getDatabase();
+            if (!database.inTransaction()) {
+                throw new IllegalStateException("No ongoing DB transaction.");
+            }
+
+            final SQLiteQueryBuilder qb = isLocal() ? QB_MATCH_LOCAL_ONLY : QB_MATCH_CLOUD;
+            return qb.update(database, contentValues, /* selection */ null, new String[] {id}) > 0;
+        }
+
+        @Override
+        int executeInternal(@Nullable Cursor cursor) {
+            throw new UnsupportedOperationException("Cursor updates are not supported.");
+        }
+    }
+
     private static final class AddMediaOperation extends DbWriteOperation {
 
         private AddMediaOperation(SQLiteDatabase database, boolean isLocal) {
@@ -394,11 +419,11 @@
                 if (QB_MATCH_ALL.insert(getDatabase(), values) > 0) {
                     return SUCCESS;
                 } else {
-                    Log.d(TAG, "Failed to insert picker db media. ContentValues: " + values);
+                    Log.v(TAG, "Failed to insert picker db media. ContentValues: " + values);
                     return FAIL;
                 }
             } catch (SQLiteConstraintException e) {
-                Log.d(TAG, "Failed to insert picker db media. ContentValues: " + values, e);
+                Log.v(TAG, "Failed to insert picker db media. ContentValues: " + values, e);
                 return RETRY;
             }
         }
@@ -408,7 +433,7 @@
             int res = insertMedia(values);
             if (res == RETRY) {
                 // Attempt equivalent of CONFLICT_REPLACE resolution
-                Log.d(TAG, "Retrying failed insert as update. ContentValues: " + values);
+                Log.v(TAG, "Retrying failed insert as update. ContentValues: " + values);
                 res = updateMedia(qb, values, selectionArgs);
             }
 
@@ -545,12 +570,12 @@
         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 +583,7 @@
             this.mId = id;
             this.mAlbumId = albumId;
             this.mSizeBytes = sizeBytes;
-            this.mMimeType = mimeType;
+            this.mMimeTypes = mimeTypes;
             this.mIsFavorite = isFavorite;
             this.mIsVideo = isVideo;
         }
@@ -568,6 +593,7 @@
     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 +604,7 @@
         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 +648,8 @@
             return this;
         }
 
-        public QueryFilterBuilder setMimeType(String mimeType) {
-            this.mimeType = mimeType;
+        public QueryFilterBuilder setMimeTypes(String[] mimeTypes) {
+            this.mimeTypes = mimeTypes;
             return this;
         }
 
@@ -649,7 +675,7 @@
 
         public QueryFilter build() {
             return new QueryFilter(limit, dateTakenBeforeMs, dateTakenAfterMs, id, albumId,
-                    sizeBytes, mimeType, isFavorite, mIsVideo);
+                    sizeBytes, mimeTypes, isFavorite, mIsVideo);
         }
     }
 
@@ -737,12 +763,9 @@
                 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,
+            Cursor cursor = qb.query(mDatabase, getAlbumProjection(), /* selection */ null,
                     selectionArgs.toArray(new String[0]), /* groupBy */ null, /* having */ null,
                     /* orderBy */ null, /* limit */ null);
 
@@ -761,13 +784,27 @@
                     /* displayName */ albumId,
                     getCursorString(cursor, AlbumColumns.MEDIA_COVER_ID),
                     String.valueOf(count),
-                    mLocalProvider,
+                    getCursorString(cursor, AlbumColumns.AUTHORITY),
             };
             c.addRow(projectionValue);
         }
         return c;
     }
 
+    private String[] getAlbumProjection() {
+        return new String[] {
+                "COUNT(" + KEY_ID + ") AS " + CloudMediaProviderContract.AlbumColumns.MEDIA_COUNT,
+                "MAX(" + KEY_DATE_TAKEN_MS + ") AS "
+                        + CloudMediaProviderContract.AlbumColumns.DATE_TAKEN_MILLIS,
+                String.format("IFNULL(%s, %s) AS %s", KEY_CLOUD_ID,
+                        KEY_LOCAL_ID, CloudMediaProviderContract.AlbumColumns.MEDIA_COVER_ID),
+                // Note that we prefer local provider over cloud provider if a media item is present
+                // locally and on cloud.
+                String.format("CASE WHEN %s IS NULL THEN '%s' ELSE '%s' END AS %s",
+                        KEY_LOCAL_ID, mCloudProvider, mLocalProvider, AlbumColumns.AUTHORITY)
+        };
+    }
+
     private boolean isLocal(String authority) {
         return mLocalProvider.equals(authority);
     }
@@ -908,7 +945,7 @@
     private static ContentValues cursorToContentValue(Cursor cursor, boolean isLocal,
             String albumId) {
         final ContentValues values = new ContentValues();
-        if(TextUtils.isEmpty(albumId)) {
+        if (TextUtils.isEmpty(albumId)) {
             values.put(KEY_IS_VISIBLE, 1);
         }
         else {
@@ -957,7 +994,7 @@
                     values.put(KEY_DURATION_MS, cursor.getLong(index));
                     break;
                 case CloudMediaProviderContract.MediaColumns.IS_FAVORITE:
-                    if(TextUtils.isEmpty(albumId)) {
+                    if (TextUtils.isEmpty(albumId)) {
                         values.put(KEY_IS_FAVORITE, cursor.getInt(index));
                     }
                     break;
@@ -1004,10 +1041,8 @@
             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 +1060,27 @@
         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);
@@ -1087,25 +1143,35 @@
         return qb;
     }
 
-    private static final class ResetAlbumOperation extends DbWriteOperation {
-        /**
-         * Resets the given cloud or local album_media identified by {@code isLocal} and
-         * {@code albumId}. If {@code albumId} is null, resets all the respective cloud or
-         * local albums.
-         */
+    private abstract static class AlbumWriteOperation extends DbWriteOperation {
+
+        private final String mAlbumId;
+
+        private AlbumWriteOperation(SQLiteDatabase database, boolean isLocal, String albumId) {
+            super(database, isLocal);
+            mAlbumId = albumId;
+        }
+
+        String getAlbumId() {
+            return mAlbumId;
+        }
+    }
+
+    private static final class ResetAlbumOperation extends AlbumWriteOperation {
+
         private ResetAlbumOperation(SQLiteDatabase database, boolean isLocal, String albumId) {
             super(database, isLocal, albumId);
         }
 
         @Override
         int executeInternal(@Nullable Cursor unused) {
-            final String albumId = albumId();
+            final String albumId = getAlbumId();
             final boolean isLocal = isLocal();
 
             final SQLiteQueryBuilder qb = createAlbumMediaQueryBuilder(isLocal);
 
             String[] selectionArgs = null;
-            if(!TextUtils.isEmpty(albumId)) {
+            if (!TextUtils.isEmpty(albumId)) {
                 qb.appendWhereStandalone(WHERE_ALBUM_ID);
                 selectionArgs = new String[]{albumId};
             }
@@ -1115,10 +1181,12 @@
         }
     }
 
-    private static final class AddAlbumMediaOperation extends DbWriteOperation {
+    private static final class AddAlbumMediaOperation extends AlbumWriteOperation {
+
         private AddAlbumMediaOperation(SQLiteDatabase database, boolean isLocal, String albumId) {
             super(database, isLocal, albumId);
-            if(TextUtils.isEmpty(albumId)) {
+
+            if (TextUtils.isEmpty(albumId)) {
                 throw new IllegalArgumentException("Missing albumId.");
             }
         }
@@ -1126,7 +1194,7 @@
         @Override
         int executeInternal(@Nullable Cursor cursor) {
             final boolean isLocal = isLocal();
-            final String albumId = albumId();
+            final String albumId = getAlbumId();
             final SQLiteQueryBuilder qb = createAlbumMediaQueryBuilder(isLocal);
             int counter = 0;
 
@@ -1136,10 +1204,10 @@
                     if (qb.insert(getDatabase(), values) > 0) {
                         counter++;
                     } else {
-                        Log.d(TAG, "Failed to insert album_media. ContentValues: " + values);
+                        Log.v(TAG, "Failed to insert album_media. ContentValues: " + values);
                     }
                 } catch (SQLiteConstraintException e) {
-                    Log.d(TAG, "Failed to insert album_media. ContentValues: " + values, e);
+                    Log.v(TAG, "Failed to insert album_media. ContentValues: " + values, e);
                 }
             }
 
diff --git a/src/com/android/providers/media/photopicker/data/model/Category.java b/src/com/android/providers/media/photopicker/data/model/Category.java
index 6166d4a..90a2143 100644
--- a/src/com/android/providers/media/photopicker/data/model/Category.java
+++ b/src/com/android/providers/media/photopicker/data/model/Category.java
@@ -17,44 +17,36 @@
 package com.android.providers.media.photopicker.data.model;
 
 import static android.provider.CloudMediaProviderContract.AlbumColumns;
-import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS;
-import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_SCREENSHOTS;
 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA;
 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_DOWNLOADS;
 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_FAVORITES;
+import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_SCREENSHOTS;
+import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS;
+
 import static com.android.providers.media.photopicker.util.CursorUtils.getCursorInt;
 import static com.android.providers.media.photopicker.util.CursorUtils.getCursorString;
 
-import android.annotation.StringDef;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Environment;
-import android.provider.CloudMediaProviderContract;
 import android.provider.MediaStore;
-import android.provider.MediaStore.Files.FileColumns;
 import android.text.TextUtils;
-import android.util.ArrayMap;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.providers.media.R;
 import com.android.providers.media.photopicker.data.ItemsProvider;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
 import java.util.Locale;
 
 /**
  * Defines each category (which is group of items) for the photo picker.
  */
 public class Category {
+    public static final String TAG = "PhotoPicker";
     public static final Category DEFAULT = new Category();
 
     private final String mId;
@@ -147,14 +139,20 @@
      * Create a {@link Category} from the {@code cursor}.
      */
     public static Category fromCursor(@NonNull Cursor cursor, @NonNull UserId userId) {
-        final boolean isLocal;
         String authority = getCursorString(cursor, AlbumColumns.AUTHORITY);
-        if (authority != null) {
-            isLocal = true;
-        } else {
-            isLocal = false;
-            authority = cursor.getExtras().getString(MediaStore.EXTRA_CLOUD_PROVIDER);
+        if (authority == null) {
+            // Authority will be null for cloud albums in cursor.
+            String cloudProvider = cursor.getExtras().getString(MediaStore.EXTRA_CLOUD_PROVIDER);
+            if (cloudProvider == null) {
+                // If cloud provider is null, cloud albums will not show up properly.
+                Log.e(TAG, "Cloud provider is set by the user but not passed in album media cursor"
+                        + " extras.");
+            } else {
+                authority = cloudProvider;
+            }
         }
+        final boolean isLocal = authority != null
+                && authority.equals(cursor.getExtras().getString(MediaStore.EXTRA_LOCAL_PROVIDER));
         final Uri coverUri = ItemsProvider.getItemsUri(
                 getCursorString(cursor, AlbumColumns.MEDIA_COVER_ID), authority, userId);
 
diff --git a/src/com/android/providers/media/photopicker/metrics/PhotoPickerUiEventLogger.java b/src/com/android/providers/media/photopicker/metrics/PhotoPickerUiEventLogger.java
index 076ad31..30c2fbe 100644
--- a/src/com/android/providers/media/photopicker/metrics/PhotoPickerUiEventLogger.java
+++ b/src/com/android/providers/media/photopicker/metrics/PhotoPickerUiEventLogger.java
@@ -31,7 +31,17 @@
         @UiEvent(doc = "Photo picker opened via GET_CONTENT intent")
         PHOTO_PICKER_OPEN_GET_CONTENT(1080),
         @UiEvent(doc = "DocumentsUi opened by clicking on Browse in Photo picker")
-        PHOTO_PICKER_BROWSE_DOCUMENTSUI(1085);
+        PHOTO_PICKER_BROWSE_DOCUMENTSUI(1085),
+        @UiEvent(doc = "Photo picker cancelled in work profile")
+        PHOTO_PICKER_CANCEL_WORK_PROFILE(1125),
+        @UiEvent(doc = "Photo picker cancelled in personal profile")
+        PHOTO_PICKER_CANCEL_PERSONAL_PROFILE(1126),
+        @UiEvent(doc = "Confirmed selection in Photo picker in work profile")
+        PHOTO_PICKER_CONFIRM_WORK_PROFILE(1127),
+        @UiEvent(doc = "Confirmed selection in Photo picker in personal profile")
+        PHOTO_PICKER_CONFIRM_PERSONAL_PROFILE(1128),
+        @UiEvent(doc = "User changed the active Photo picker cloud provider")
+        PHOTO_PICKER_CLOUD_PROVIDER_CHANGED(1135);
 
         private final int mId;
 
@@ -90,4 +100,66 @@
                 callingPackage,
                 instanceId);
     }
+
+    /**
+     * Log metrics to notify that user has confirmed selection in personal profile
+     */
+    public void logPickerConfirmPersonal(InstanceId instanceId, int callingUid,
+            String callingPackage, int countOfItemsConfirmed) {
+        logger.logWithInstanceIdAndPosition(
+                PhotoPickerEvent.PHOTO_PICKER_CONFIRM_PERSONAL_PROFILE,
+                callingUid,
+                callingPackage,
+                instanceId,
+                countOfItemsConfirmed);
+    }
+
+    /**
+     * Log metrics to notify that user has confirmed selection in work profile
+     */
+    public void logPickerConfirmWork(InstanceId instanceId, int callingUid,
+            String callingPackage, int countOfItemsConfirmed) {
+        logger.logWithInstanceIdAndPosition(
+                PhotoPickerEvent.PHOTO_PICKER_CONFIRM_WORK_PROFILE,
+                callingUid,
+                callingPackage,
+                instanceId,
+                countOfItemsConfirmed);
+    }
+
+    /**
+     * Log metrics to notify that user has cancelled picker (without any selection) in personal
+     * profile
+     */
+    public void logPickerCancelPersonal(InstanceId instanceId, int callingUid,
+            String callingPackage) {
+        logger.logWithInstanceId(
+                PhotoPickerEvent.PHOTO_PICKER_CANCEL_PERSONAL_PROFILE,
+                callingUid,
+                callingPackage,
+                instanceId);
+    }
+
+    /**
+     * Log metrics to notify that user has cancelled picker (without any selection) in work
+     * profile
+     */
+    public void logPickerCancelWork(InstanceId instanceId, int callingUid,
+            String callingPackage) {
+        logger.logWithInstanceId(
+                PhotoPickerEvent.PHOTO_PICKER_CANCEL_WORK_PROFILE,
+                callingUid,
+                callingPackage,
+                instanceId);
+    }
+
+    /**
+     * Log metrics to notify that the user has changed the active cloud provider
+     * @param cloudProviderUid     new active cloud provider uid
+     * @param cloudProviderPackage new active cloud provider package name
+     */
+    public void logPickerCloudProviderChanged(int cloudProviderUid, String cloudProviderPackage) {
+        logger.log(PhotoPickerEvent.PHOTO_PICKER_CLOUD_PROVIDER_CHANGED, cloudProviderUid,
+                cloudProviderPackage);
+    }
 }
diff --git a/src/com/android/providers/media/photopicker/ui/AlbumsTabFragment.java b/src/com/android/providers/media/photopicker/ui/AlbumsTabFragment.java
index 3f946ca..5060486 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 @@
         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 44ceea2..0000000
--- 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 d629471..69f5e3d 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.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 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 @@
         // 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 @@
      * @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 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 @@
         }
 
         // 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 @@
         // 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 @@
                 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 7d31af9..0000000
--- 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 8ce7311..1df4877 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 @@
     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 @@
     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 @@
 
         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 @@
     }
 
     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 dcb1696..e828767 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 @@
     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/ui/SafetyProtectionSectionView.java b/src/com/android/providers/media/photopicker/ui/SafetyProtectionSectionView.java
new file mode 100644
index 0000000..21dd503
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/ui/SafetyProtectionSectionView.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+package com.android.providers.media.photopicker.ui;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.text.Html;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.providers.media.R;
+import com.android.providers.media.photopicker.util.SafetyProtectionUtils;
+
+/**
+ * A custom view class for Safety Protection widget.
+ */
+public class SafetyProtectionSectionView extends LinearLayout {
+    public SafetyProtectionSectionView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    public SafetyProtectionSectionView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public SafetyProtectionSectionView(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context);
+    }
+
+    public SafetyProtectionSectionView(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(context);
+    }
+
+    private void init(Context context) {
+        setGravity(Gravity.CENTER);
+        setOrientation(HORIZONTAL);
+        int visibility = SafetyProtectionUtils.shouldShowSafetyProtectionResources(context)
+                ? View.VISIBLE : View.GONE;
+        setVisibility(visibility);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        Context context = getContext();
+        if (SafetyProtectionUtils.shouldShowSafetyProtectionResources(context)) {
+            LayoutInflater.from(context).inflate(R.layout.safety_protection_section, this);
+            TextView safetyProtectionDisplayTextView =
+                    requireViewById(R.id.safety_protection_display_text);
+            safetyProtectionDisplayTextView.setText(Html.fromHtml(
+                    context.getString(android.R.string.safety_protection_display_text), 0));
+        }
+    }
+}
diff --git a/src/com/android/providers/media/photopicker/util/MimeFilterUtils.java b/src/com/android/providers/media/photopicker/util/MimeFilterUtils.java
index b028a00..7872343 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 @@
     /**
      * 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 @@
             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/util/SafetyProtectionUtils.java b/src/com/android/providers/media/photopicker/util/SafetyProtectionUtils.java
new file mode 100644
index 0000000..e3f459a
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/util/SafetyProtectionUtils.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package com.android.providers.media.photopicker.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Build;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.ChecksSdkIntAtLeast;
+
+import com.android.modules.utils.build.SdkLevel;
+
+/**
+ * Util class for whether we should show the safety protection resources.
+ */
+public class SafetyProtectionUtils {
+    private static final String SAFETY_PROTECTION_RESOURCES_ENABLED = "safety_protection_enabled";
+
+    /**
+     * Determines whether we should show the safety protection resources.
+     * We show the resources only if
+     * (1) the build version is T or after and
+     * (2) the feature flag safety_protection_enabled is enabled and
+     * (3) the config value config_safetyProtectionEnabled is enabled/true and
+     * (4) the resources exist (currently the resources only exist on GMS devices)
+     */
+    @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
+    public static boolean shouldShowSafetyProtectionResources(Context context) {
+        return SdkLevel.isAtLeastT()
+                && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                        SAFETY_PROTECTION_RESOURCES_ENABLED, false)
+                && context.getResources().getBoolean(
+                        Resources.getSystem()
+                                .getIdentifier("config_safetyProtectionEnabled",
+                                        "bool", "android"))
+                && context.getDrawable(android.R.drawable.ic_safety_protection) != null
+                && context.getString(android.R.string.safety_protection_display_text) != null
+                && !context.getString(android.R.string.safety_protection_display_text).isEmpty();
+    }
+}
diff --git a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
index 1de5839..a285cbf 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 @@
     private InstanceId mInstanceId;
     private PhotoPickerUiEventLogger mLogger;
 
-    private String mMimeTypeFilter = null;
+    private String[] mMimeTypeFilters = null;
     private int mBottomSheetState;
 
     private Category mCurrentCategory;
@@ -151,7 +151,7 @@
         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 @@
 
     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 @@
     }
 
     /**
-     * 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 void parseValuesFromIntent(Intent intent) throws IllegalArgumentException {
         mUserIdManager.setIntentAndCheckRestrictions(intent);
 
-        mMimeTypeFilter = MimeFilterUtils.getMimeTypeFilter(intent);
+        mMimeTypeFilters = MimeFilterUtils.getMimeTypeFilters(intent);
 
         mSelection.parseSelectionValuesFromIntent(intent);
     }
@@ -357,6 +357,30 @@
         mLogger.logBrowseToDocumentsUi(mInstanceId, callingUid, callingPackage);
     }
 
+    /**
+     * Log metrics to notify that the user has confirmed selection
+     */
+    public void logPickerConfirm(int callingUid, String callingPackage, int countOfItemsConfirmed) {
+        if (getUserIdManager().isManagedUserSelected()) {
+            mLogger.logPickerConfirmWork(mInstanceId, callingUid, callingPackage,
+                    countOfItemsConfirmed);
+        } else {
+            mLogger.logPickerConfirmPersonal(mInstanceId, callingUid, callingPackage,
+                    countOfItemsConfirmed);
+        }
+    }
+
+    /**
+     * Log metrics to notify that the user has exited Picker without any selection
+     */
+    public void logPickerCancel(int callingUid, String callingPackage) {
+        if (getUserIdManager().isManagedUserSelected()) {
+            mLogger.logPickerCancelWork(mInstanceId, callingUid, callingPackage);
+        } else {
+            mLogger.logPickerCancelPersonal(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 41f53d5..5afe15e 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -92,7 +92,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.modules.utils.build.SdkLevel;
 import com.android.providers.media.MediaVolume;
 import com.android.providers.media.util.DatabaseUtils;
 import com.android.providers.media.util.ExifUtils;
@@ -119,7 +118,6 @@
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -1210,22 +1208,6 @@
         return op;
     }
 
-    private static ArrayMap<String, String> sAudioTypes = new ArrayMap<>();
-
-    static {
-        sAudioTypes.put(Environment.DIRECTORY_RINGTONES, AudioColumns.IS_RINGTONE);
-        sAudioTypes.put(Environment.DIRECTORY_NOTIFICATIONS, AudioColumns.IS_NOTIFICATION);
-        sAudioTypes.put(Environment.DIRECTORY_ALARMS, AudioColumns.IS_ALARM);
-        sAudioTypes.put(Environment.DIRECTORY_PODCASTS, AudioColumns.IS_PODCAST);
-        sAudioTypes.put(Environment.DIRECTORY_AUDIOBOOKS, AudioColumns.IS_AUDIOBOOK);
-        sAudioTypes.put(Environment.DIRECTORY_MUSIC, AudioColumns.IS_MUSIC);
-        if (SdkLevel.isAtLeastS()) {
-            sAudioTypes.put(Environment.DIRECTORY_RECORDINGS, AudioColumns.IS_RECORDING);
-        } else {
-            sAudioTypes.put(FileUtils.DIRECTORY_RECORDINGS, AudioColumns.IS_RECORDING);
-        }
-    }
-
     private static @NonNull ContentProviderOperation.Builder scanItemAudio(long existingId,
             File file, BasicFileAttributes attrs, String mimeType, int mediaType,
             String volumeName) {
@@ -1236,17 +1218,7 @@
         op.withValue(MediaColumns.ALBUM, file.getParentFile().getName());
         op.withValue(AudioColumns.TRACK, null);
 
-        final String lowPath = file.getAbsolutePath().toLowerCase(Locale.ROOT);
-        boolean anyMatch = false;
-        for (int i = 0; i < sAudioTypes.size(); i++) {
-            final boolean match = lowPath
-                    .contains('/' + sAudioTypes.keyAt(i).toLowerCase(Locale.ROOT) + '/');
-            op.withValue(sAudioTypes.valueAt(i), match ? 1 : 0);
-            anyMatch |= match;
-        }
-        if (!anyMatch) {
-            op.withValue(AudioColumns.IS_MUSIC, 1);
-        }
+        FileUtils.computeAudioTypeValuesFromData(file.getAbsolutePath(), op::withValue);
 
         try (FileInputStream is = new FileInputStream(file)) {
             try (MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
@@ -1510,9 +1482,13 @@
     @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 55efafc..cec6f27 100644
--- a/src/com/android/providers/media/util/DatabaseUtils.java
+++ b/src/com/android/providers/media/util/DatabaseUtils.java
@@ -535,8 +535,14 @@
         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 ee421c9..d4cdc7e 100644
--- a/src/com/android/providers/media/util/FileUtils.java
+++ b/src/com/android/providers/media/util/FileUtils.java
@@ -49,17 +49,19 @@
 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;
+import android.provider.MediaStore.Audio.AudioColumns;
 import android.provider.MediaStore.MediaColumns;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.webkit.MimeTypeMap;
 
@@ -91,6 +93,7 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Consumer;
+import java.util.function.ObjIntConsumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -417,8 +420,8 @@
             // When file size exceeds MAX_READ_STRING_SIZE, file is either
             // corrupted or doesn't the contain expected data. Hence we return
             // Optional.empty() which will be interpreted as empty file.
-            Logging.logPersistent(String.format("Ignored reading %s, file size exceeds %d", file,
-                    MAX_READ_STRING_SIZE));
+            Logging.logPersistent(String.format(Locale.ROOT,
+                    "Ignored reading %s, file size exceeds %d", file, MAX_READ_STRING_SIZE));
         } catch (NoSuchFileException ignored) {
         }
         return Optional.empty();
@@ -973,12 +976,13 @@
             "(?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 +1403,53 @@
             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);
+        }
+    }
+
+    @VisibleForTesting
+    static ArrayMap<String, String> sAudioTypes = new ArrayMap<>();
+
+    static {
+        sAudioTypes.put(Environment.DIRECTORY_RINGTONES, AudioColumns.IS_RINGTONE);
+        sAudioTypes.put(Environment.DIRECTORY_NOTIFICATIONS, AudioColumns.IS_NOTIFICATION);
+        sAudioTypes.put(Environment.DIRECTORY_ALARMS, AudioColumns.IS_ALARM);
+        sAudioTypes.put(Environment.DIRECTORY_PODCASTS, AudioColumns.IS_PODCAST);
+        sAudioTypes.put(Environment.DIRECTORY_AUDIOBOOKS, AudioColumns.IS_AUDIOBOOK);
+        sAudioTypes.put(Environment.DIRECTORY_MUSIC, AudioColumns.IS_MUSIC);
+        if (SdkLevel.isAtLeastS()) {
+            sAudioTypes.put(Environment.DIRECTORY_RECORDINGS, AudioColumns.IS_RECORDING);
+        } else {
+            sAudioTypes.put(FileUtils.DIRECTORY_RECORDINGS, AudioColumns.IS_RECORDING);
+        }
+    }
+
+    /**
+     * Compute values for columns in {@code sAudioTypes} based on the given {@code filePath}.
+     */
+    public static void computeAudioTypeValuesFromData(@NonNull String filePath,
+            @NonNull ObjIntConsumer<String> consumer) {
+        final String lowPath = filePath.toLowerCase(Locale.ROOT);
+        boolean anyMatch = false;
+        for (int i = 0; i < sAudioTypes.size(); i++) {
+            final boolean match = lowPath
+                    .contains('/' + sAudioTypes.keyAt(i).toLowerCase(Locale.ROOT) + '/');
+            consumer.accept(sAudioTypes.valueAt(i), match ? 1 : 0);
+            anyMatch |= match;
+        }
+        if (!anyMatch) {
+            consumer.accept(AudioColumns.IS_MUSIC, 1);
+        }
     }
 
     public static void sanitizeValues(@NonNull ContentValues values,
@@ -1559,7 +1607,7 @@
         }
 
         // .nomedia is present which makes this directory as hidden directory
-        Logging.logPersistent("Observed non-standard " + nomedia);
+        Log.d(TAG, "Observed non-standard " + nomedia);
         return true;
     }
 
@@ -1625,18 +1673,28 @@
         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/src/com/android/providers/media/util/IsoInterface.java b/src/com/android/providers/media/util/IsoInterface.java
index 03b46c9..8da64b9 100644
--- a/src/com/android/providers/media/util/IsoInterface.java
+++ b/src/com/android/providers/media/util/IsoInterface.java
@@ -48,6 +48,7 @@
     private static final String TAG = "IsoInterface";
     private static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
 
+    public static final int BOX_ILST = 0x696c7374;
     public static final int BOX_FTYP = 0x66747970;
     public static final int BOX_HDLR = 0x68646c72;
     public static final int BOX_UUID = 0x75756964;
@@ -96,7 +97,7 @@
 
     private static class Box {
         public final int type;
-        public final long[] range;
+        public long[] range;
         public UUID uuid;
         public byte[] data;
         public List<Box> children;
@@ -132,7 +133,7 @@
         return new UUID(high, low);
     }
 
-    private static @Nullable Box parseNextBox(@NonNull FileDescriptor fd, long end,
+    private static @Nullable Box parseNextBox(@NonNull FileDescriptor fd, long end, int parentType,
             @NonNull String prefix) throws ErrnoException, IOException {
         final long pos = Os.lseek(fd, 0, OsConstants.SEEK_CUR);
 
@@ -210,6 +211,9 @@
                 box.headerSize += 4;
             }
             Os.lseek(fd, pos + box.headerSize, OsConstants.SEEK_SET);
+        } else if (type == BOX_XYZ && parentType == BOX_ILST) {
+            box.range = new long[] {box.range[0], headerSize,
+                    box.range[0] + headerSize, box.range[1] - headerSize};
         }
 
         if (LOGV) {
@@ -222,7 +226,7 @@
             box.children = new ArrayList<>();
 
             Box child;
-            while ((child = parseNextBox(fd, pos + len, prefix + "  ")) != null) {
+            while ((child = parseNextBox(fd, pos + len, type, prefix + "  ")) != null) {
                 box.children.add(child);
             }
         }
@@ -252,7 +256,7 @@
             final long end = Os.lseek(fd, 0, OsConstants.SEEK_END);
             Os.lseek(fd, 0, OsConstants.SEEK_SET);
             Box box;
-            while ((box = parseNextBox(fd, end, "")) != null) {
+            while ((box = parseNextBox(fd, end, -1, "")) != null) {
                 mRoots.add(box);
             }
         } catch (ErrnoException e) {
@@ -291,9 +295,11 @@
     public @NonNull long[] getBoxRanges(int type) {
         LongArray res = new LongArray();
         for (Box box : mFlattened) {
-            if (box.type == type) {
-                res.add(box.range[0] + box.headerSize);
-                res.add(box.range[0] + box.range[1]);
+            for (int i = 0; i < box.range.length; i += 2) {
+                if (box.type == type) {
+                    res.add(box.range[i] + box.headerSize);
+                    res.add(box.range[i] + box.range[i + 1]);
+                }
             }
         }
         return res.toArray();
@@ -302,9 +308,11 @@
     public @NonNull long[] getBoxRanges(@NonNull UUID uuid) {
         LongArray res = new LongArray();
         for (Box box : mFlattened) {
-            if (box.type == BOX_UUID && Objects.equals(box.uuid, uuid)) {
-                res.add(box.range[0] + box.headerSize);
-                res.add(box.range[0] + box.range[1]);
+            for (int i = 0; i < box.range.length; i += 2) {
+                if (box.type == BOX_UUID && Objects.equals(box.uuid, uuid)) {
+                    res.add(box.range[i] + box.headerSize);
+                    res.add(box.range[i] + box.range[i + 1]);
+                }
             }
         }
         return res.toArray();
diff --git a/src/com/android/providers/media/util/Logging.java b/src/com/android/providers/media/util/Logging.java
index ff69e5c..1afd18b 100644
--- a/src/com/android/providers/media/util/Logging.java
+++ b/src/com/android/providers/media/util/Logging.java
@@ -34,9 +34,8 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.text.SimpleDateFormat;
-import java.util.Comparator;
 import java.util.Date;
-import java.util.Optional;
+import java.util.Locale;
 import java.util.stream.Stream;
 
 public class Logging {
@@ -53,7 +52,7 @@
     private static final int PERSISTENT_COUNT = 4;
     private static final long PERSISTENT_AGE = DateUtils.WEEK_IN_MILLIS;
     private static final SimpleDateFormat DATE_FORMAT =
-            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.ROOT);
     private static final Object LOCK = new Object();
 
     @GuardedBy("LOCK")
@@ -77,7 +76,9 @@
     /**
      * Write the given message to persistent logs.
      */
-    public static void logPersistent(@NonNull String msg) {
+    public static void logPersistent(@NonNull String format, @Nullable Object ... args) {
+        final String msg = String.format(Locale.ROOT, format, args);
+
         Log.i(TAG, msg);
 
         synchronized (LOCK) {
diff --git a/src/com/android/providers/media/util/Metrics.java b/src/com/android/providers/media/util/Metrics.java
index 024151d..b97df56 100644
--- a/src/com/android/providers/media/util/Metrics.java
+++ b/src/com/android/providers/media/util/Metrics.java
@@ -45,10 +45,10 @@
 public class Metrics {
     public static void logScan(@NonNull String volumeName, int reason, long itemCount,
             long durationMillis, int insertCount, int updateCount, int deleteCount) {
-        Logging.logPersistent(String.format(
+        Logging.logPersistent(
                 "Scanned %s due to %s, found %d items in %dms, %d inserts %d updates %d deletes",
                 volumeName, translateReason(reason), itemCount, durationMillis, insertCount,
-                updateCount, deleteCount));
+                updateCount, deleteCount);
 
         final float normalizedDurationMillis = ((float) durationMillis) / itemCount;
         final float normalizedInsertCount = ((float) insertCount) / itemCount;
@@ -90,9 +90,9 @@
 
     public static void logPermissionGranted(@NonNull String volumeName, int uid, String packageName,
             int itemCount) {
-        Logging.logPersistent(String.format(
+        Logging.logPersistent(
                 "Granted permission to %3$d items on %1$s to %2$s",
-                volumeName, packageName, itemCount));
+                volumeName, packageName, itemCount);
 
         MediaProviderStatsLog.write(MEDIA_PROVIDER_PERMISSION_REQUESTED,
                 translateVolumeName(volumeName), uid, itemCount,
@@ -101,9 +101,9 @@
 
     public static void logPermissionDenied(@NonNull String volumeName, int uid, String packageName,
             int itemCount) {
-        Logging.logPersistent(String.format(
+        Logging.logPersistent(
                 "Denied permission to %3$d items on %1$s to %2$s",
-                volumeName, packageName, itemCount));
+                volumeName, packageName, itemCount);
 
         MediaProviderStatsLog.write(MEDIA_PROVIDER_PERMISSION_REQUESTED,
                 translateVolumeName(volumeName), uid, itemCount,
@@ -112,9 +112,9 @@
 
     public static void logSchemaChange(@NonNull String volumeName, int versionFrom, int versionTo,
             long itemCount, long durationMillis, @NonNull String databaseUuid) {
-        Logging.logPersistent(String.format(
+        Logging.logPersistent(
                 "Changed schema version on %s from %d to %d, %d items taking %dms UUID %s",
-                volumeName, versionFrom, versionTo, itemCount, durationMillis, databaseUuid));
+                volumeName, versionFrom, versionTo, itemCount, durationMillis, databaseUuid);
 
         final float normalizedDurationMillis = ((float) durationMillis) / itemCount;
 
@@ -125,9 +125,9 @@
 
     public static void logIdleMaintenance(@NonNull String volumeName, long itemCount,
             long durationMillis, int staleThumbnails, int expiredMedia) {
-        Logging.logPersistent(String.format(
+        Logging.logPersistent(
                 "Idle maintenance on %s, %d items taking %dms, %d stale, %d expired",
-                volumeName, itemCount, durationMillis, staleThumbnails, expiredMedia));
+                volumeName, itemCount, durationMillis, staleThumbnails, expiredMedia);
 
         final float normalizedDurationMillis = ((float) durationMillis) / itemCount;
         final float normalizedStaleThumbnails = ((float) staleThumbnails) / itemCount;
diff --git a/src/com/android/providers/media/util/Preconditions.java b/src/com/android/providers/media/util/Preconditions.java
index fb87130..ffb29c6 100644
--- a/src/com/android/providers/media/util/Preconditions.java
+++ b/src/com/android/providers/media/util/Preconditions.java
@@ -16,6 +16,8 @@
 
 package com.android.providers.media.util;
 
+import java.util.Locale;
+
 public final class Preconditions {
 
     /**
@@ -49,11 +51,11 @@
             String valueName) {
         if (value < lower) {
             throw new IllegalArgumentException(
-                    String.format(
+                    String.format(Locale.ROOT,
                             "%s is out of range of [%d, %d] (too low)", valueName, lower, upper));
         } else if (value > upper) {
             throw new IllegalArgumentException(
-                    String.format(
+                    String.format(Locale.ROOT,
                             "%s is out of range of [%d, %d] (too high)", valueName, lower, upper));
         }
 
diff --git a/tests/Android.bp b/tests/Android.bp
index 05aa556..17f90db 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -158,7 +158,7 @@
         "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 @@
         "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 fd94863..a3c81af 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -9,10 +9,8 @@
     <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">
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+    <application android:label="MediaProvider Tests">
         <uses-library android:name="android.test.runner" />
 
         <activity android:name="com.android.providers.media.GetResultActivity" />
diff --git a/tests/client/src/com/android/providers/media/client/ClientPlaylistTest.java b/tests/client/src/com/android/providers/media/client/ClientPlaylistTest.java
index 9aaaae2..e9b9cbf 100644
--- a/tests/client/src/com/android/providers/media/client/ClientPlaylistTest.java
+++ b/tests/client/src/com/android/providers/media/client/ClientPlaylistTest.java
@@ -396,7 +396,7 @@
         public void onChange(boolean selfChange, Uri uri, int flags) {
             Log.v(TAG, String.format("onChange(%b, %s, %d)", selfChange, uri.toString(), flags));
 
-            if (flags == this.flags) {
+            if ((flags & this.flags) == this.flags) {
                 latch.countDown();
             }
         }
diff --git a/tests/client/src/com/android/providers/media/client/PerformanceTest.java b/tests/client/src/com/android/providers/media/client/PerformanceTest.java
index 0088514..b792ee2 100644
--- a/tests/client/src/com/android/providers/media/client/PerformanceTest.java
+++ b/tests/client/src/com/android/providers/media/client/PerformanceTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.content.ContentProviderOperation;
@@ -27,9 +26,7 @@
 import android.content.ContentValues;
 import android.database.ContentObserver;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.Environment;
-import android.os.SystemClock;
 import android.provider.MediaStore;
 import android.provider.MediaStore.MediaColumns;
 import android.util.Log;
@@ -383,10 +380,10 @@
                     selfChange, asSet(uris).toString(), flags));
 
             if (this.uriCount == 1) {
-                if (asSet(uris).size() == 1 && flags == this.flags) {
+                if (asSet(uris).size() == 1 && (flags & this.flags) == this.flags) {
                     latch.countDown();
                 }
-            } else if (flags == this.flags) {
+            } else if ((flags & this.flags) == this.flags) {
                 // NotifyChange for bulk operations will be sent in batches.
                 final int receivedCount = asSet(uris).size();
 
diff --git a/tests/res/raw/test_video_gps_ilst_tag.mp4 b/tests/res/raw/test_video_gps_ilst_tag.mp4
new file mode 100644
index 0000000..d65ff15
--- /dev/null
+++ b/tests/res/raw/test_video_gps_ilst_tag.mp4
Binary files differ
diff --git a/tests/src/com/android/providers/media/IdleServiceTest.java b/tests/src/com/android/providers/media/IdleServiceTest.java
index 80d2261..3d81b25 100644
--- a/tests/src/com/android/providers/media/IdleServiceTest.java
+++ b/tests/src/com/android/providers/media/IdleServiceTest.java
@@ -121,6 +121,7 @@
         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 @@
         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 @@
         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));
     }
 
     /**
@@ -246,9 +249,8 @@
                 assertThat(cr.getCount()).isEqualTo(1);
                 assertThat(cr.moveToFirst()).isNotNull();
                 assertThat(cr.getInt(0)).isEqualTo(_SPECIAL_FORMAT_NONE);
-                // Make sure updating special format column updates GENERATION_MODIFIED;
-                // This is essential for picker db to know which rows were modified.
-                assertThat(cr.getInt(1)).isGreaterThan(initialGenerationModified);
+                // Make sure that updating special format column doesn't update GENERATION_MODIFIED
+                assertThat(cr.getInt(1)).isEqualTo(initialGenerationModified);
             }
         } finally {
             file.delete();
diff --git a/tests/src/com/android/providers/media/MediaProviderTest.java b/tests/src/com/android/providers/media/MediaProviderTest.java
index 01578fa..ff56ee8 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.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 @@
         }
     }
 
+    @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
diff --git a/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java b/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java
index 2f90d2f..adeaa3b 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.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.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 @@
         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 @@
             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 @@
         // 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 @@
         }
 
         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 @@
 
             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 @@
             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 @@
         }
     }
 
-    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 a007139..d56328e 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 @@
                 CloudProviderQueryExtras.fromCloudMediaBundle(extras);
 
         return mMediaGenerator.getMedia(queryExtras.getGeneration(), queryExtras.getAlbumId(),
-                queryExtras.getMimeType(), queryExtras.getSizeBytes());
+                queryExtras.getMimeTypes(), queryExtras.getSizeBytes());
     }
 
     @Override
@@ -70,7 +70,7 @@
         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 abcb92f..a00cbaf 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 @@
                 CloudProviderQueryExtras.fromCloudMediaBundle(extras);
 
         return mMediaGenerator.getMedia(queryExtras.getGeneration(), queryExtras.getAlbumId(),
-                queryExtras.getMimeType(), queryExtras.getSizeBytes());
+                queryExtras.getMimeTypes(), queryExtras.getSizeBytes());
     }
 
     @Override
@@ -70,7 +69,7 @@
         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 7608c92..dbddc3e 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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 3916c9d..b1c1281 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 @@
                 CloudProviderQueryExtras.fromCloudMediaBundle(extras);
 
         return mMediaGenerator.getMedia(queryExtras.getGeneration(), queryExtras.getAlbumId(),
-                queryExtras.getMimeType(), queryExtras.getSizeBytes());
+                queryExtras.getMimeTypes(), queryExtras.getSizeBytes());
     }
 
     @Override
@@ -69,7 +68,7 @@
         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 6931cd6..59c5582 100644
--- a/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java
+++ b/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java
@@ -343,7 +343,12 @@
         try (Cursor cr = mDataLayer.fetchAlbums(defaultQueryArgs)) {
             assertThat(cr.getCount()).isEqualTo(4);
 
-            assertAlbumCursor(cr, ALBUM_ID_FAVORITES, LOCAL_PROVIDER_AUTHORITY);
+            // Most recent media item marked as favorite will be the cover of the Favorites album.
+            // In this scenario, Favorites album cover was generated with cloud authority, so the
+            // Favorites album authority should be cloud provider authority. Similarly, the most
+            // recent video was generated with local authority, so the Videos album authority should
+            // be local provider authority.
+            assertAlbumCursor(cr, ALBUM_ID_FAVORITES, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
             assertAlbumCursor(cr, ALBUM_ID_VIDEOS, LOCAL_PROVIDER_AUTHORITY);
             assertAlbumCursor(cr, ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY);
             assertAlbumCursor(cr, ALBUM_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
@@ -504,7 +509,10 @@
         try (Cursor cr = mDataLayer.fetchAlbums(mimeTypeAndSizeQueryArgs)) {
             assertWithMessage("Merged and Local album count").that(cr.getCount()).isEqualTo(3);
 
-            assertAlbumCursor(cr, ALBUM_ID_VIDEOS, LOCAL_PROVIDER_AUTHORITY);
+            // Most recent video will be the cover of the Videos album. In this scenario, Videos
+            // album cover was generated with cloud authority, so the Videos album authority should
+            // be cloud provider authority.
+            assertAlbumCursor(cr, ALBUM_ID_VIDEOS, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
             assertAlbumCursor(cr, ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY);
             assertAlbumCursor(cr, ALBUM_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
         }
@@ -559,7 +567,9 @@
     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 53d6837..5f3ddcc 100644
--- a/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
+++ b/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
@@ -658,13 +658,33 @@
     }
 
     @Test
-    public void testSelectDefaultCloudProivder_DefaultAuthoritySet() {
+    public void testSelectDefaultCloudProvider_defaultAuthoritySet() {
         PickerSyncController controller = createControllerWithDefaultProvider(
                 CLOUD_PRIMARY_PROVIDER_AUTHORITY);
         assertThat(controller.getCloudProvider()).isEqualTo(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
     }
 
     @Test
+    public void testSelectDefaultCloudProvider_userAwareAboutCloudMediaAppSettings() {
+        PickerSyncController controller = createControllerWithDefaultProvider(
+                CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+
+        assertThat(controller.getDefaultCloudProviderInfo(null, false).isEmpty()).isFalse();
+        assertThat(controller.getDefaultCloudProviderInfo(null, true).isEmpty()).isTrue();
+    }
+
+    @Test
+    public void testNotifyUserCloudMediaAware() {
+        assertThat(mController.isUserAwareAboutCloudMediaAppSettings()).isFalse();
+
+        mController.notifyUserCloudMediaAware();
+
+        assertThat(mController.isUserAwareAboutCloudMediaAppSettings()).isTrue();
+
+        mController.clearUserAwareAboutCloudMediaAppSettingsFlag();
+    }
+
+    @Test
     public void testIsProviderAuthorityEnabled() {
         assertThat(mController.isProviderEnabled(LOCAL_PROVIDER_AUTHORITY)).isTrue();
         assertThat(mController.isProviderEnabled(CLOUD_PRIMARY_PROVIDER_AUTHORITY)).isFalse();
@@ -1052,8 +1072,10 @@
 
         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/SafetyProtectionUtilsTest.java b/tests/src/com/android/providers/media/photopicker/SafetyProtectionUtilsTest.java
new file mode 100644
index 0000000..1c15d97
--- /dev/null
+++ b/tests/src/com/android/providers/media/photopicker/SafetyProtectionUtilsTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+package com.android.providers.media.photopicker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.DeviceConfig;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.providers.media.photopicker.util.SafetyProtectionUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SafetyProtectionUtilsTest {
+    private final Context mContext = InstrumentationRegistry.getTargetContext();
+    private static final String SAFETY_PROTECTION_RESOURCES_ENABLED = "safety_protection_enabled";
+    private static final String TRUE_STRING = "true";
+    private static final String FALSE_STRING = "false";
+    private String mOriginalSafetyProtectionResourcesFlagStatus = "false";
+
+    @Before
+    public void setUp() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            mOriginalSafetyProtectionResourcesFlagStatus = DeviceConfig.getProperty(
+                    DeviceConfig.NAMESPACE_PRIVACY, SAFETY_PROTECTION_RESOURCES_ENABLED);
+        });
+    }
+
+    @After
+    public void tearDown() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                    SAFETY_PROTECTION_RESOURCES_ENABLED,
+                    mOriginalSafetyProtectionResourcesFlagStatus, false);
+        });
+    }
+
+    @Test
+    public void testShouldNotUseSafetyProtectionResourcesWhenSOrBelow() {
+        assumeFalse(SdkLevel.isAtLeastT());
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                    SAFETY_PROTECTION_RESOURCES_ENABLED, TRUE_STRING, false);
+            assertThat(SafetyProtectionUtils.shouldShowSafetyProtectionResources(mContext))
+                    .isFalse();
+        });
+    }
+
+    @Test
+    public void testWhetherShouldUseSafetyProtectionResourcesWhenTOrAboveAndFeatureFlagOn() {
+        assumeTrue(SdkLevel.isAtLeastT());
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                    SAFETY_PROTECTION_RESOURCES_ENABLED, TRUE_STRING, false);
+            assertThat(SafetyProtectionUtils.shouldShowSafetyProtectionResources(mContext))
+                    .isEqualTo(isSafetyProtectionConfigEnabled());
+        });
+    }
+
+    @Test
+    public void testWhetherShouldUseSafetyProtectionResourcesWhenTOrAboveAndFeatureFlagOff() {
+        assumeTrue(SdkLevel.isAtLeastT());
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                    SAFETY_PROTECTION_RESOURCES_ENABLED, FALSE_STRING, false);
+            assertThat(SafetyProtectionUtils.shouldShowSafetyProtectionResources(mContext))
+                    .isFalse();
+        });
+    }
+
+    protected boolean isSafetyProtectionConfigEnabled() {
+        try {
+            return mContext.getResources().getBoolean(
+                    Resources.getSystem()
+                            .getIdentifier("config_safetyProtectionEnabled", "bool",
+                            "android"));
+        } catch (Resources.NotFoundException e) {
+            return false;
+        }
+    }
+}
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 3a37efa..3e07a2e 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 @@
     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 @@
             }
 
             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 @@
             }
 
             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 @@
             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 @@
                 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 264e9bd..0305bbb 100644
--- a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
@@ -23,6 +23,7 @@
 
 import static org.junit.Assert.assertThrows;
 
+import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.MatrixCursor;
@@ -49,7 +50,9 @@
     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 +612,7 @@
     }
 
     @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 +625,12 @@
 
         // 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 +642,14 @@
         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 +661,14 @@
         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 +678,7 @@
     }
 
     @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 +691,14 @@
 
         // 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 +885,7 @@
     }
 
     @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 +936,7 @@
                     /* 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 +948,7 @@
                     /* 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 +967,7 @@
                     /* count */ 2);
         }
 
-        qfb.setMimeType("foo");
+        qfb.setMimeTypes(new String[]{"foo"});
         try (Cursor cr = mFacade.getMergedAlbums(qfb.build())) {
             assertThat(cr.getCount()).isEqualTo(0);
         }
@@ -1012,6 +1015,68 @@
         }
     }
 
+    @Test
+    public void testUpdateMediaSuccess() throws Exception {
+        Cursor localCursor = getMediaCursor(LOCAL_ID, DATE_TAKEN_MS, GENERATION_MODIFIED,
+                /* mediaStoreUri */ null, SIZE_BYTES, VIDEO_MIME_TYPE,
+                STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
+        try (PickerDbFacade.DbWriteOperation operation =
+                     mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
+            operation.execute(localCursor);
+            operation.setSuccess();
+        }
+
+        try (PickerDbFacade.UpdateMediaOperation operation =
+                     mFacade.beginUpdateMediaOperation(LOCAL_PROVIDER)) {
+            ContentValues values = new ContentValues();
+            values.put(PickerDbFacade.KEY_STANDARD_MIME_TYPE_EXTENSION,
+                    MediaColumns.STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP);
+            assertThat(operation.execute(LOCAL_ID, values)).isTrue();
+            operation.setSuccess();
+        }
+
+        try (Cursor cursor = queryMediaAll()) {
+            assertThat(cursor.getCount()).isEqualTo(1);
+
+            // Assert that STANDARD_MIME_TYPE_EXTENSION has been updated
+            cursor.moveToFirst();
+            assertThat(cursor.getInt(cursor.getColumnIndex(
+                    MediaColumns.STANDARD_MIME_TYPE_EXTENSION)))
+                    .isEqualTo(MediaColumns.STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP);
+        }
+    }
+
+    @Test
+    public void testUpdateMediaFailure() throws Exception {
+        Cursor localCursor = getMediaCursor(LOCAL_ID, DATE_TAKEN_MS, GENERATION_MODIFIED,
+                /* mediaStoreUri */ null, SIZE_BYTES, VIDEO_MIME_TYPE,
+                STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
+        try (PickerDbFacade.DbWriteOperation operation =
+                     mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
+            operation.execute(localCursor);
+            operation.setSuccess();
+        }
+
+        try (PickerDbFacade.UpdateMediaOperation operation =
+                     mFacade.beginUpdateMediaOperation(LOCAL_PROVIDER)) {
+            ContentValues values = new ContentValues();
+            values.put(PickerDbFacade.KEY_STANDARD_MIME_TYPE_EXTENSION,
+                    MediaColumns.STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP);
+            assertThat(operation.execute(CLOUD_ID, values)).isFalse();
+            operation.setSuccess();
+        }
+
+        try (Cursor cursor = queryMediaAll()) {
+            assertThat(cursor.getCount()).isEqualTo(1);
+
+            // Assert that STANDARD_MIME_TYPE_EXTENSION is same as before
+            cursor.moveToFirst();
+            assertThat(cursor.getInt(cursor.getColumnIndex(
+                    MediaColumns.STANDARD_MIME_TYPE_EXTENSION)))
+                    .isEqualTo(STANDARD_MIME_TYPE_EXTENSION);
+        }
+    }
+
     private Cursor queryMediaAll() {
         return mFacade.queryMediaForUi(
                 new PickerDbFacade.QueryFilterBuilder(1000).build());
@@ -1113,7 +1178,8 @@
     }
 
     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/data/model/CategoryTest.java b/tests/src/com/android/providers/media/photopicker/data/model/CategoryTest.java
index 808f70b..930a5b7 100644
--- a/tests/src/com/android/providers/media/photopicker/data/model/CategoryTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/model/CategoryTest.java
@@ -23,8 +23,8 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.database.MatrixCursor;
-import android.net.Uri;
-import android.provider.CloudMediaProviderContract;
+import android.os.Bundle;
+import android.provider.MediaStore;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -34,11 +34,11 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.List;
-
 @RunWith(AndroidJUnit4.class)
 public class CategoryTest {
 
+    private static final String LOCAL_PROVIDER_AUTHORITY =
+            "com.android.providers.media.photopicker";
     @Test
     public void testConstructor() {
         final Context context = InstrumentationRegistry.getTargetContext();
@@ -54,10 +54,11 @@
         final Category category = Category.fromCursor(cursor, UserId.CURRENT_USER);
 
         assertThat(category.getDisplayName(context)).isEqualTo(categoryName);
+        assertThat(category.getAuthority()).isEqualTo(LOCAL_PROVIDER_AUTHORITY);
         assertThat(category.isLocal()).isEqualTo(categoryIsLocal);
         assertThat(category.getItemCount()).isEqualTo(itemCount);
         assertThat(category.getCoverUri()).isEqualTo(ItemsProvider.getItemsUri(coverId,
-                        /* authority */ "foo", UserId.CURRENT_USER));
+                        /* authority */ LOCAL_PROVIDER_AUTHORITY, UserId.CURRENT_USER));
         assertThat(category.getId()).isEqualTo(categoryId);
     }
 
@@ -65,7 +66,10 @@
             String coverId, int itemCount, boolean isLocal) {
         final MatrixCursor cursor = new MatrixCursor(AlbumColumns.ALL_PROJECTION);
         cursor.addRow(new Object[] {categoryId, 1, categoryName, coverId, itemCount,
-                                    "foo"});
+                LOCAL_PROVIDER_AUTHORITY});
+        Bundle extras = new Bundle();
+        extras.putString(MediaStore.EXTRA_LOCAL_PROVIDER, LOCAL_PROVIDER_AUTHORITY);
+        cursor.setExtras(extras);
         return cursor;
     }
 }
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerBaseTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerBaseTest.java
index f2c80e7..91c309b 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerBaseTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerBaseTest.java
@@ -155,7 +155,8 @@
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
                         Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
-                        Manifest.permission.INTERACT_ACROSS_USERS);
+                        Manifest.permission.INTERACT_ACROSS_USERS,
+                        Manifest.permission.READ_DEVICE_CONFIG);
 
         sIsolatedContext = new IsolatedContext(getTargetContext(), "modern",
                 /* asFuseThread */ false);
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 2c98c3f..966f0ed 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 @@
 
 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 @@
         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 @@
         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 @@
     }
 
     @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(MimeFilterUtils.getMimeTypeFilter(intent).equals("video/*")).isTrue();
+        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.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 ec4afa1..ab50696 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 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.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 @@
 
         @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 @@
         }
 
         @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 @@
     }
 
     @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 testParseValuesFromIntent_ignoreInvalidMimeType() {
-        final Intent intent = new Intent();
-        intent.setType("audio/*");
+    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.hasMimeTypeFilter()).isFalse();
+        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 testParseValuesFromGetContentIntent_invalidExtraMimeType() {
+        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"audio/*", "video/*"});
+
+        mPickerViewModel.parseValuesFromIntent(intent);
+
+        // 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/ModernMediaScannerTest.java b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
index 0450c1c..e3c56f1 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 @@
         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 266c144..8c3d5d4 100644
--- a/tests/src/com/android/providers/media/util/FileUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/FileUtilsTest.java
@@ -59,10 +59,12 @@
 import static com.android.providers.media.util.FileUtils.translateModeStringToPosix;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 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;
 
@@ -70,6 +72,7 @@
 import android.os.Environment;
 import android.os.SystemProperties;
 import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.AudioColumns;
 import android.provider.MediaStore.MediaColumns;
 import android.text.TextUtils;
 
@@ -89,7 +92,9 @@
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
 
@@ -1021,11 +1026,16 @@
         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 +1210,64 @@
         assertTrue(values.containsKey(MediaColumns.BUCKET_DISPLAY_NAME));
         assertNull(values.get(MediaColumns.BUCKET_DISPLAY_NAME));
     }
+
+    @Test
+    public void testComputeAudioTypeValuesFromData() {
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Ringtones/a.mp3",
+                AudioColumns.IS_RINGTONE);
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Notifications/a.mp3",
+                AudioColumns.IS_NOTIFICATION);
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Alarms/a.mp3",
+                AudioColumns.IS_ALARM);
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Podcasts/a.mp3",
+                AudioColumns.IS_PODCAST);
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Audiobooks/a.mp3",
+                AudioColumns.IS_AUDIOBOOK);
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Recordings/a.mp3",
+                AudioColumns.IS_RECORDING);
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Music/a.mp3",
+                AudioColumns.IS_MUSIC);
+
+        // Categorized as music if it doesn't match any other category
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/a/a.mp3", AudioColumns.IS_MUSIC);
+
+        // All matches are case-insensitive
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/ringtones/a.mp3",
+                AudioColumns.IS_RINGTONE);
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/a/ringtones/a.mp3",
+                AudioColumns.IS_RINGTONE);
+    }
+
+    @Test
+    public void testComputeAudioTypeValuesFromData_multiple() {
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Ringtones/Recordings/a.mp3",
+                Arrays.asList(AudioColumns.IS_RINGTONE, AudioColumns.IS_RECORDING));
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Alarms/Notifications/a.mp3",
+                Arrays.asList(AudioColumns.IS_ALARM, AudioColumns.IS_NOTIFICATION));
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Audiobooks/Podcasts/a.mp3",
+                Arrays.asList(AudioColumns.IS_AUDIOBOOK, AudioColumns.IS_PODCAST));
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Audiobooks/Ringtones/a.mp3",
+                Arrays.asList(AudioColumns.IS_AUDIOBOOK, AudioColumns.IS_RINGTONE));
+        testComputeAudioTypeValuesFromData("/storage/emulated/0/Music/Ringtones/a.mp3",
+                Arrays.asList(AudioColumns.IS_MUSIC, AudioColumns.IS_RINGTONE));
+    }
+
+    private void testComputeAudioTypeValuesFromData(String path, String expectedColumn) {
+        testComputeAudioTypeValuesFromData(path, Collections.singletonList(expectedColumn));
+    }
+
+    private void testComputeAudioTypeValuesFromData(String path, List<String> expectedColumns) {
+        final ContentValues values = new ContentValues();
+        FileUtils.computeAudioTypeValuesFromData(path, values::put);
+
+        for (String column : FileUtils.sAudioTypes.values()) {
+            if (expectedColumns.contains(column)) {
+                assertWithMessage("Expected " + column + " to be set for " + path)
+                        .that(values.get(column)).isEqualTo(1);
+            } else {
+                assertWithMessage("Expected " + column + " to be unset for " + path)
+                        .that(values.get(column)).isEqualTo(0);
+            }
+        }
+    }
 }
diff --git a/tests/src/com/android/providers/media/util/IsoInterfaceTest.java b/tests/src/com/android/providers/media/util/IsoInterfaceTest.java
index 7783957..37dd48f 100644
--- a/tests/src/com/android/providers/media/util/IsoInterfaceTest.java
+++ b/tests/src/com/android/providers/media/util/IsoInterfaceTest.java
@@ -73,6 +73,19 @@
     }
 
     @Test
+    public void testGpsInIlst() throws Exception {
+        final File file = stageFile(R.raw.test_video_gps_ilst_tag);
+        final IsoInterface mp4 = IsoInterface.fromFile(file);
+
+        final long[] ranges = mp4.getBoxRanges(0xa978797a); // ?xyz
+        assertEquals(4, ranges.length);
+        assertEquals(2267, ranges[0]);
+        assertEquals(2267, ranges[1]);
+        assertEquals(2275, ranges[2]);
+        assertEquals(2309, ranges[3]);
+    }
+
+    @Test
     public void testXmp() throws Exception {
         final File file = stageFile(R.raw.test_video_xmp);
         final IsoInterface mp4 = IsoInterface.fromFile(file);
diff --git a/tests/src/com/android/providers/media/util/LoggingTest.java b/tests/src/com/android/providers/media/util/LoggingTest.java
index bb2905e..b5301d9 100644
--- a/tests/src/com/android/providers/media/util/LoggingTest.java
+++ b/tests/src/com/android/providers/media/util/LoggingTest.java
@@ -55,6 +55,18 @@
         final String nonce = String.valueOf(System.nanoTime());
         Logging.logPersistent(nonce);
 
+        assertLoggedMessageContains(nonce);
+    }
+
+    @Test
+    public void testFormat() throws Exception {
+        final long nonce = System.nanoTime();
+        Logging.logPersistent("%d", nonce);
+
+        assertLoggedMessageContains(String.valueOf(nonce));
+    }
+
+    private void assertLoggedMessageContains(String nonce) {
         final ByteArrayOutputStream os = new ByteArrayOutputStream();
         final PrintWriter pw = new PrintWriter(os, true);
         Logging.dumpPersistent(pw);