Merge "Apps can define a prewarming service for ACTION_REVIEW"
diff --git a/Android.bp b/Android.bp
index 65996bd..8db7943 100644
--- a/Android.bp
+++ b/Android.bp
@@ -68,6 +68,7 @@
         "src/com/android/providers/media/util/FileUtils.java",
         "src/com/android/providers/media/util/HandlerExecutor.java",
         "src/com/android/providers/media/util/Logging.java",
+        "src/com/android/providers/media/util/MimeUtils.java",
     ],
 }
 
diff --git a/apex/apex_manifest.json b/apex/apex_manifest.json
index 6bf4be1..ffef8fb 100644
--- a/apex/apex_manifest.json
+++ b/apex/apex_manifest.json
@@ -1,4 +1,4 @@
 {
   "name": "com.android.mediaprovider",
-  "version": 1
+  "version": 300000000
 }
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index fca3251..cf1e481 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -1514,7 +1514,7 @@
             public static final String TITLE = "title";
 
             /**
-             * The media type (audio, video, image or playlist)
+             * The media type (audio, video, image, document, playlist or subtitle)
              * of the file, or 0 for not a media file
              */
             @Column(Cursor.FIELD_TYPE_INTEGER)
@@ -1522,7 +1522,7 @@
 
             /**
              * Constant for the {@link #MEDIA_TYPE} column indicating that file
-             * is not an audio, image, video, playlist, or subtitles file.
+             * is not an audio, image, video, document, playlist, or subtitles file.
              */
             public static final int MEDIA_TYPE_NONE = 0;
 
@@ -1555,6 +1555,11 @@
              * is a subtitles or lyrics file.
              */
             public static final int MEDIA_TYPE_SUBTITLE = 5;
+
+            /**
+             * Constant for the {@link #MEDIA_TYPE} column indicating that file is a document file.
+             */
+            public static final int MEDIA_TYPE_DOCUMENT = 6;
         }
     }
 
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 8c96088..35a0c85 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -98,6 +98,19 @@
 constexpr size_t MAX_READ_SIZE = 128 * 1024;
 // Stolen from: UserHandle#getUserId
 constexpr int PER_USER_RANGE = 100000;
+// Cache inode attributes for a 'short' time so that performance is decent and last modified time
+// stamps are not too stale
+constexpr double DEFAULT_ATTR_TIMEOUT_SECONDS = 10;
+// Ensure the VFS does not cache dentries, if it caches, the following scenario could occur:
+// 1. Process A has access to file A and does a lookup
+// 2. Process B does not have access to file A and does a lookup
+// (2) will succeed because the lookup request will not be sent from kernel to the FUSE daemon
+// and the kernel will respond from cache. Even if this by itself is not a security risk
+// because subsequent FUSE requests will fail if B does not have access to the resource.
+// It does cause indeterministic behavior because whether (2) succeeds or not depends on if
+// (1) occurred.
+// We prevent this caching by setting the entry_timeout value to 0.
+constexpr double DEFAULT_ENTRY_TIMEOUT_SECONDS = 0;
 
 /*
  * In order to avoid double caching with fuse, call fadvise on the file handles
@@ -368,6 +381,30 @@
     return reinterpret_cast<struct fuse*>(fuse_req_userdata(req));
 }
 
+static bool is_android_path(const string& path, const string& fuse_path, uid_t uid) {
+    int user_id = uid / PER_USER_RANGE;
+    const std::string android_path = fuse_path + "/" + std::to_string(user_id) + "/Android";
+    return path.rfind(android_path, 0) == 0;
+}
+
+static double get_attr_timeout(const string& path, uid_t uid, struct fuse* fuse, node* parent) {
+    if (fuse->IsRoot(parent) || is_android_path(path, fuse->path, uid)) {
+        // The /0 and /0/Android attrs can be always cached, as they never change
+        return DBL_MAX;
+    } else {
+        return DEFAULT_ATTR_TIMEOUT_SECONDS;
+    }
+}
+
+static double get_entry_timeout(const string& path, uid_t uid, struct fuse* fuse, node* parent) {
+    if (fuse->IsRoot(parent) || is_android_path(path, fuse->path, uid)) {
+        // The /0 and /0/Android dentries can be always cached, as they are visible to all apps
+        return DBL_MAX;
+    } else {
+        return DEFAULT_ENTRY_TIMEOUT_SECONDS;
+    }
+}
+
 static node* make_node_entry(fuse_req_t req, node* parent, const string& name, const string& path,
                              struct fuse_entry_param* e, int* error_code) {
     struct fuse* fuse = get_fuse(req);
@@ -388,31 +425,14 @@
         fuse->NodeCreated(node);
     }
 
-    // Manipulate attr here if needed
-    e->attr_timeout = 10;
-    int user_id = ctx->uid / PER_USER_RANGE;
-    const std::string android_path = fuse->path + "/" + std::to_string(user_id) + "/Android";
-    // Ensure the VFS does not cache dentries, if it caches, the following scenario could occur:
-    // 1. Process A has access to file A and does a lookup
-    // 2. Process B does not have access to file A and does a lookup
-    // (2) will succeed because the lookup request will not be sent from kernel to the FUSE daemon
-    // and the kernel will respond from cache. Even if this by itself is not a security risk
-    // because subsequent FUSE requests will fail if B does not have access to the resource.
-    // It does cause indeterministic behavior because whether (2) succeeds or not depends on if
-    // (1) occurred.
-    // We prevent this caching by setting the entry_timeout value to 0.
-    // The /0 and /0/Android paths are exempt, as they are visible to all apps.
-    if (fuse->IsRoot(parent) || path.rfind(android_path, 0) == 0) {
-        e->entry_timeout = 10;
-    } else {
-        e->entry_timeout = 0;
-    }
-    e->ino = fuse->ToInode(node);
     // This FS is not being exported via NFS so just a fixed generation number
     // for now. If we do need this, we need to increment the generation ID each
     // time the fuse daemon restarts because that's what it takes for us to
     // reuse inode numbers.
     e->generation = 0;
+    e->ino = fuse->ToInode(node);
+    e->entry_timeout = get_entry_timeout(path, ctx->uid, fuse, parent);
+    e->attr_timeout = get_attr_timeout(path, ctx->uid, fuse, parent);
 
     return node;
 }
@@ -535,7 +555,7 @@
     if (lstat(path.c_str(), &s) < 0) {
         fuse_reply_err(req, errno);
     } else {
-        fuse_reply_attr(req, &s, 10);
+        fuse_reply_attr(req, &s, get_attr_timeout(path, ctx->uid, fuse, nullptr));
     }
 }
 
@@ -601,7 +621,7 @@
     }
 
     lstat(path.c_str(), attr);
-    fuse_reply_attr(req, attr, 10);
+    fuse_reply_attr(req, attr, get_attr_timeout(path, ctx->uid, fuse, nullptr));
 }
 
 static void pf_canonical_path(fuse_req_t req, fuse_ino_t ino)
diff --git a/legacy/src/com/android/providers/media/LegacyMediaProvider.java b/legacy/src/com/android/providers/media/LegacyMediaProvider.java
index 4085a3a..93d1b8e 100644
--- a/legacy/src/com/android/providers/media/LegacyMediaProvider.java
+++ b/legacy/src/com/android/providers/media/LegacyMediaProvider.java
@@ -68,9 +68,9 @@
         Logging.initPersistent(persistentDir);
 
         mInternalDatabase = new DatabaseHelper(context, INTERNAL_DATABASE_NAME,
-                true, false, true, null, null);
+                true, false, true, null, null, null);
         mExternalDatabase = new DatabaseHelper(context, EXTERNAL_DATABASE_NAME,
-                false, false, true, null, null);
+                false, false, true, null, null, null);
 
         return true;
     }
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 3690a11..4d6d9b6 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Plaaslike berging"</string>
     <string name="app_label" msgid="9035307001052716210">"Mediaberging"</string>
     <string name="artist_label" msgid="8105600993099120273">"Kunstenaar"</string>
+    <string name="unknown" msgid="2059049215682829375">"Onbekend"</string>
     <string name="root_images" msgid="5861633549189045666">"Prente"</string>
     <string name="root_videos" msgid="8792703517064649453">"Video\'s"</string>
     <string name="root_audio" msgid="3505830755201326018">"Oudio"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 6ba8c4a..5038025 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"አካባቢያዊ ማከማቻ"</string>
     <string name="app_label" msgid="9035307001052716210">"ማህደረ መረጃ ማከማቻ"</string>
     <string name="artist_label" msgid="8105600993099120273">"አርቲስት"</string>
+    <string name="unknown" msgid="2059049215682829375">"የማይታወቅ"</string>
     <string name="root_images" msgid="5861633549189045666">"ምስሎች"</string>
     <string name="root_videos" msgid="8792703517064649453">"ቪዲዮዎች"</string>
     <string name="root_audio" msgid="3505830755201326018">"ኦዲዮ"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 32619cc..c773f4e 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"التخزين المحلي"</string>
     <string name="app_label" msgid="9035307001052716210">"تخزين الوسائط"</string>
     <string name="artist_label" msgid="8105600993099120273">"الفنان"</string>
+    <string name="unknown" msgid="2059049215682829375">"غير معروف"</string>
     <string name="root_images" msgid="5861633549189045666">"الصور"</string>
     <string name="root_videos" msgid="8792703517064649453">"الفيديوهات"</string>
     <string name="root_audio" msgid="3505830755201326018">"الصوت"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index a44fa8f..ccad175 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"স্থানীয় সঞ্চয়াগাৰ"</string>
     <string name="app_label" msgid="9035307001052716210">"মিডিয়া সঞ্চয়াগাৰ"</string>
     <string name="artist_label" msgid="8105600993099120273">"শিল্পী"</string>
+    <string name="unknown" msgid="2059049215682829375">"অজ্ঞাত"</string>
     <string name="root_images" msgid="5861633549189045666">"প্ৰতিচ্ছবিসমূহ"</string>
     <string name="root_videos" msgid="8792703517064649453">"ভিডিঅ\'সমূহ"</string>
     <string name="root_audio" msgid="3505830755201326018">"অডিঅ’"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index e6e2233..0128090 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Yerli yaddaş"</string>
     <string name="app_label" msgid="9035307001052716210">"Media Yaddaşı"</string>
     <string name="artist_label" msgid="8105600993099120273">"Sənətçi"</string>
+    <string name="unknown" msgid="2059049215682829375">"Naməlum"</string>
     <string name="root_images" msgid="5861633549189045666">"Təsvirlər"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videolar"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 2362706..9b9338f 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Lokalni memorijski prostor"</string>
     <string name="app_label" msgid="9035307001052716210">"Memorijski prostor za medije"</string>
     <string name="artist_label" msgid="8105600993099120273">"Izvođač"</string>
+    <string name="unknown" msgid="2059049215682829375">"Nepoznato"</string>
     <string name="root_images" msgid="5861633549189045666">"Slike"</string>
     <string name="root_videos" msgid="8792703517064649453">"Video snimci"</string>
     <string name="root_audio" msgid="3505830755201326018">"Zvuk"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index aa5a46d..f00785a 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Лакальнае сховішча"</string>
     <string name="app_label" msgid="9035307001052716210">"Медыясховішча"</string>
     <string name="artist_label" msgid="8105600993099120273">"Выканаўца"</string>
+    <string name="unknown" msgid="2059049215682829375">"Невядома"</string>
     <string name="root_images" msgid="5861633549189045666">"Відарысы"</string>
     <string name="root_videos" msgid="8792703517064649453">"Відэа"</string>
     <string name="root_audio" msgid="3505830755201326018">"Аўдыя"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index a3a9ae3..d968074 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Локално хранилище"</string>
     <string name="app_label" msgid="9035307001052716210">"Мултимедийно хранилище"</string>
     <string name="artist_label" msgid="8105600993099120273">"Изпълнител"</string>
+    <string name="unknown" msgid="2059049215682829375">"Неизвестно"</string>
     <string name="root_images" msgid="5861633549189045666">"Изображения"</string>
     <string name="root_videos" msgid="8792703517064649453">"Видеоклипове"</string>
     <string name="root_audio" msgid="3505830755201326018">"Аудио"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 4a0e5ec..fce9819 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"স্থানীয় স্টোরেজ"</string>
     <string name="app_label" msgid="9035307001052716210">"মিডিয়া স্টোরেজ"</string>
     <string name="artist_label" msgid="8105600993099120273">"শিল্পী"</string>
+    <string name="unknown" msgid="2059049215682829375">"অজানা"</string>
     <string name="root_images" msgid="5861633549189045666">"ছবি"</string>
     <string name="root_videos" msgid="8792703517064649453">"ভিডিও"</string>
     <string name="root_audio" msgid="3505830755201326018">"অডিও"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index da8c0e5..bf06a23 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Lokalna pohrana"</string>
     <string name="app_label" msgid="9035307001052716210">"Medijska pohrana"</string>
     <string name="artist_label" msgid="8105600993099120273">"Umjetnik"</string>
+    <string name="unknown" msgid="2059049215682829375">"Nepoznato"</string>
     <string name="root_images" msgid="5861633549189045666">"Slike"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videozapisi"</string>
     <string name="root_audio" msgid="3505830755201326018">"Zvuk"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 7ca2e87..00f7aa1 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Emmagatzematge local"</string>
     <string name="app_label" msgid="9035307001052716210">"Emmagatzematge multimèdia"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artista"</string>
+    <string name="unknown" msgid="2059049215682829375">"Desconegut"</string>
     <string name="root_images" msgid="5861633549189045666">"Imatges"</string>
     <string name="root_videos" msgid="8792703517064649453">"Vídeos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Àudio"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 152850a..44466a0 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Místní úložiště"</string>
     <string name="app_label" msgid="9035307001052716210">"Úložiště médií"</string>
     <string name="artist_label" msgid="8105600993099120273">"Interpret"</string>
+    <string name="unknown" msgid="2059049215682829375">"Neznámý"</string>
     <string name="root_images" msgid="5861633549189045666">"Obrázky"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videa"</string>
     <string name="root_audio" msgid="3505830755201326018">"Zvuk"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index e834ac8..8217551 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Lokalt lager"</string>
     <string name="app_label" msgid="9035307001052716210">"Medielagring"</string>
     <string name="artist_label" msgid="8105600993099120273">"Musiker"</string>
+    <string name="unknown" msgid="2059049215682829375">"Ukendt"</string>
     <string name="root_images" msgid="5861633549189045666">"Billeder"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videoer"</string>
     <string name="root_audio" msgid="3505830755201326018">"Lyd"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index f086bcb..fc44c40 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Lokaler Speicher"</string>
     <string name="app_label" msgid="9035307001052716210">"Medienspeicher"</string>
     <string name="artist_label" msgid="8105600993099120273">"Interpret"</string>
+    <string name="unknown" msgid="2059049215682829375">"Unbekannt"</string>
     <string name="root_images" msgid="5861633549189045666">"Bilder"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 5c3e42f..b0e9cab 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Τοπικός χώρος αποθήκευσης"</string>
     <string name="app_label" msgid="9035307001052716210">"Αποθηκευτικός χώρος μέσων"</string>
     <string name="artist_label" msgid="8105600993099120273">"Καλλιτέχνης"</string>
+    <string name="unknown" msgid="2059049215682829375">"Άγνωστο"</string>
     <string name="root_images" msgid="5861633549189045666">"Εικόνες"</string>
     <string name="root_videos" msgid="8792703517064649453">"Βίντεο"</string>
     <string name="root_audio" msgid="3505830755201326018">"Ήχος"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 1015704..abcc17f 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Local storage"</string>
     <string name="app_label" msgid="9035307001052716210">"Media Storage"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artist"</string>
+    <string name="unknown" msgid="2059049215682829375">"Unknown"</string>
     <string name="root_images" msgid="5861633549189045666">"Images"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 1015704..abcc17f 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Local storage"</string>
     <string name="app_label" msgid="9035307001052716210">"Media Storage"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artist"</string>
+    <string name="unknown" msgid="2059049215682829375">"Unknown"</string>
     <string name="root_images" msgid="5861633549189045666">"Images"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 1015704..abcc17f 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Local storage"</string>
     <string name="app_label" msgid="9035307001052716210">"Media Storage"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artist"</string>
+    <string name="unknown" msgid="2059049215682829375">"Unknown"</string>
     <string name="root_images" msgid="5861633549189045666">"Images"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 1015704..abcc17f 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Local storage"</string>
     <string name="app_label" msgid="9035307001052716210">"Media Storage"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artist"</string>
+    <string name="unknown" msgid="2059049215682829375">"Unknown"</string>
     <string name="root_images" msgid="5861633549189045666">"Images"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index a5240ac..c150a1c 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‎‎‏‎‏‏‎Local storage‎‏‎‎‏‎"</string>
     <string name="app_label" msgid="9035307001052716210">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎Media Storage‎‏‎‎‏‎"</string>
     <string name="artist_label" msgid="8105600993099120273">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‏‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎Artist‎‏‎‎‏‎"</string>
+    <string name="unknown" msgid="2059049215682829375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‎Unknown‎‏‎‎‏‎"</string>
     <string name="root_images" msgid="5861633549189045666">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‏‎‎‎‏‎‎Images‎‏‎‎‏‎"</string>
     <string name="root_videos" msgid="8792703517064649453">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‎Videos‎‏‎‎‏‎"</string>
     <string name="root_audio" msgid="3505830755201326018">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‎‎Audio‎‏‎‎‏‎"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index f30b6c9..88a59f4 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Almacenamiento local"</string>
     <string name="app_label" msgid="9035307001052716210">"Almacenamiento multimedia"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artista"</string>
+    <string name="unknown" msgid="2059049215682829375">"Desconocido"</string>
     <string name="root_images" msgid="5861633549189045666">"Imágenes"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index bef47a7..e4fabd3 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Almacenamiento local"</string>
     <string name="app_label" msgid="9035307001052716210">"Almacenamiento multimedia"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artista"</string>
+    <string name="unknown" msgid="2059049215682829375">"Desconocido"</string>
     <string name="root_images" msgid="5861633549189045666">"Imágenes"</string>
     <string name="root_videos" msgid="8792703517064649453">"Vídeos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index b7d8dcd..d933192 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Kohalik salvestusruum"</string>
     <string name="app_label" msgid="9035307001052716210">"Meediumi salvestusruum"</string>
     <string name="artist_label" msgid="8105600993099120273">"Esitaja"</string>
+    <string name="unknown" msgid="2059049215682829375">"Teadmata"</string>
     <string name="root_images" msgid="5861633549189045666">"Pildid"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videod"</string>
     <string name="root_audio" msgid="3505830755201326018">"Heli"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index a6969d9..ba7ae97 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Biltegi lokala"</string>
     <string name="app_label" msgid="9035307001052716210">"Multimediaren memoria-unitatea"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artista"</string>
+    <string name="unknown" msgid="2059049215682829375">"Ezezaguna"</string>
     <string name="root_images" msgid="5861633549189045666">"Irudiak"</string>
     <string name="root_videos" msgid="8792703517064649453">"Bideoak"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audioa"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index f0f6250..8d62ec1 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"فضای ذخیره‌سازی محلی"</string>
     <string name="app_label" msgid="9035307001052716210">"فضای ذخیره‌سازی رسانه"</string>
     <string name="artist_label" msgid="8105600993099120273">"هنرمند"</string>
+    <string name="unknown" msgid="2059049215682829375">"نامشخص"</string>
     <string name="root_images" msgid="5861633549189045666">"تصویر"</string>
     <string name="root_videos" msgid="8792703517064649453">"ویدئو"</string>
     <string name="root_audio" msgid="3505830755201326018">"صوت"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 38995c2..6692732 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Paikallinen tallennustila"</string>
     <string name="app_label" msgid="9035307001052716210">"Median tallennustila"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artisti"</string>
+    <string name="unknown" msgid="2059049215682829375">"Tuntematon"</string>
     <string name="root_images" msgid="5861633549189045666">"Kuvat"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videot"</string>
     <string name="root_audio" msgid="3505830755201326018">"Ääni"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 2043874..2a72071 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Stockage local"</string>
     <string name="app_label" msgid="9035307001052716210">"Stockage multimédia"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artiste"</string>
+    <string name="unknown" msgid="2059049215682829375">"Inconnu"</string>
     <string name="root_images" msgid="5861633549189045666">"Images"</string>
     <string name="root_videos" msgid="8792703517064649453">"Vidéos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 0cfe9ff..116b9af 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Stockage local"</string>
     <string name="app_label" msgid="9035307001052716210">"Stockage multimédia"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artiste"</string>
+    <string name="unknown" msgid="2059049215682829375">"Inconnu"</string>
     <string name="root_images" msgid="5861633549189045666">"Images"</string>
     <string name="root_videos" msgid="8792703517064649453">"Vidéos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Fichiers audio"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 2401716..2747a1b 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Almacenamento local"</string>
     <string name="app_label" msgid="9035307001052716210">"Almacenamento multimedia"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artista"</string>
+    <string name="unknown" msgid="2059049215682829375">"Descoñecida"</string>
     <string name="root_images" msgid="5861633549189045666">"Imaxes"</string>
     <string name="root_videos" msgid="8792703517064649453">"Vídeos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 73a4b34..b45ad32 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"સ્થાનિક સ્ટોરેજ"</string>
     <string name="app_label" msgid="9035307001052716210">"મીડિયા સ્ટોરેજ"</string>
     <string name="artist_label" msgid="8105600993099120273">"કલાકાર"</string>
+    <string name="unknown" msgid="2059049215682829375">"અજાણ"</string>
     <string name="root_images" msgid="5861633549189045666">"છબીઓ"</string>
     <string name="root_videos" msgid="8792703517064649453">"વિડિઓઝ"</string>
     <string name="root_audio" msgid="3505830755201326018">"ઑડિઓ"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 0cf5f2e..198d41c 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"स्थानीय जगह"</string>
     <string name="app_label" msgid="9035307001052716210">"मीडिया मेमोरी"</string>
     <string name="artist_label" msgid="8105600993099120273">"कलाकार"</string>
+    <string name="unknown" msgid="2059049215682829375">"अज्ञात"</string>
     <string name="root_images" msgid="5861633549189045666">"चित्र"</string>
     <string name="root_videos" msgid="8792703517064649453">"वीडियो"</string>
     <string name="root_audio" msgid="3505830755201326018">"ऑडियो"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 184a457..e6e1e2e 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Lokalna pohrana"</string>
     <string name="app_label" msgid="9035307001052716210">"Pohranjivanje na mediju"</string>
     <string name="artist_label" msgid="8105600993099120273">"Izvođač"</string>
+    <string name="unknown" msgid="2059049215682829375">"Nepoznato"</string>
     <string name="root_images" msgid="5861633549189045666">"Slike"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videozapisi"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audiozapisi"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 6c5c804..e604b39 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Helyi tárhely"</string>
     <string name="app_label" msgid="9035307001052716210">"Médiatároló"</string>
     <string name="artist_label" msgid="8105600993099120273">"Előadó"</string>
+    <string name="unknown" msgid="2059049215682829375">"Ismeretlen"</string>
     <string name="root_images" msgid="5861633549189045666">"Képek"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videók"</string>
     <string name="root_audio" msgid="3505830755201326018">"Hang"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 0e2b6f1..5dd473c 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Սարքի հիշողություն"</string>
     <string name="app_label" msgid="9035307001052716210">"Մեդիա կրիչ"</string>
     <string name="artist_label" msgid="8105600993099120273">"Կատարող"</string>
+    <string name="unknown" msgid="2059049215682829375">"Անհայտ"</string>
     <string name="root_images" msgid="5861633549189045666">"Պատկերներ"</string>
     <string name="root_videos" msgid="8792703517064649453">"Տեսանյութեր"</string>
     <string name="root_audio" msgid="3505830755201326018">"Աուդիո"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 82a188c..8cb130a 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Penyimpanan lokal"</string>
     <string name="app_label" msgid="9035307001052716210">"Penyimpanan Media"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artis"</string>
+    <string name="unknown" msgid="2059049215682829375">"Tidak diketahui"</string>
     <string name="root_images" msgid="5861633549189045666">"Gambar"</string>
     <string name="root_videos" msgid="8792703517064649453">"Video"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 29b94d4..66ecd8d 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Staðbundin vistun"</string>
     <string name="app_label" msgid="9035307001052716210">"Efnisgeymsla"</string>
     <string name="artist_label" msgid="8105600993099120273">"Flytjandi"</string>
+    <string name="unknown" msgid="2059049215682829375">"Óþekkt"</string>
     <string name="root_images" msgid="5861633549189045666">"Myndir"</string>
     <string name="root_videos" msgid="8792703517064649453">"Myndskeið"</string>
     <string name="root_audio" msgid="3505830755201326018">"Hljóð"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index e7c17bc..59ea81e 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Archiviazione locale"</string>
     <string name="app_label" msgid="9035307001052716210">"Media Storage"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artista"</string>
+    <string name="unknown" msgid="2059049215682829375">"Sconosciuto"</string>
     <string name="root_images" msgid="5861633549189045666">"Immagini"</string>
     <string name="root_videos" msgid="8792703517064649453">"Video"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 0a61b16..b1a944f 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"אחסון מקומי"</string>
     <string name="app_label" msgid="9035307001052716210">"אחסון מדיה"</string>
     <string name="artist_label" msgid="8105600993099120273">"אמן"</string>
+    <string name="unknown" msgid="2059049215682829375">"לא ידוע"</string>
     <string name="root_images" msgid="5861633549189045666">"תמונות"</string>
     <string name="root_videos" msgid="8792703517064649453">"סרטונים"</string>
     <string name="root_audio" msgid="3505830755201326018">"אודיו"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 9f2b76b..26e1024 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"ローカル ストレージ"</string>
     <string name="app_label" msgid="9035307001052716210">"メディア ストレージ"</string>
     <string name="artist_label" msgid="8105600993099120273">"アーティスト"</string>
+    <string name="unknown" msgid="2059049215682829375">"不明"</string>
     <string name="root_images" msgid="5861633549189045666">"画像"</string>
     <string name="root_videos" msgid="8792703517064649453">"動画"</string>
     <string name="root_audio" msgid="3505830755201326018">"オーディオ"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 549d967..f682cea 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"ადგილობრივი მეხსიერება"</string>
     <string name="app_label" msgid="9035307001052716210">"მედიის საცავი"</string>
     <string name="artist_label" msgid="8105600993099120273">"შემსრულებელი"</string>
+    <string name="unknown" msgid="2059049215682829375">"უცნობი"</string>
     <string name="root_images" msgid="5861633549189045666">"სურათები"</string>
     <string name="root_videos" msgid="8792703517064649453">"ვიდეოები"</string>
     <string name="root_audio" msgid="3505830755201326018">"აუდიო"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index c249cf8..8948093 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Жергілікті жад"</string>
     <string name="app_label" msgid="9035307001052716210">"Мультимедиа қоймасы"</string>
     <string name="artist_label" msgid="8105600993099120273">"Орындаушы"</string>
+    <string name="unknown" msgid="2059049215682829375">"Белгісіз"</string>
     <string name="root_images" msgid="5861633549189045666">"Кескіндер"</string>
     <string name="root_videos" msgid="8792703517064649453">"Бейнелер"</string>
     <string name="root_audio" msgid="3505830755201326018">"Aудио"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index e26b67c..8693094 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"ទំហំផ្ទុកមូលដ្ឋាន"</string>
     <string name="app_label" msgid="9035307001052716210">"ទំហំផ្ទុកមេឌៀ"</string>
     <string name="artist_label" msgid="8105600993099120273">"សិល្បករ"</string>
+    <string name="unknown" msgid="2059049215682829375">"មិនស្គាល់"</string>
     <string name="root_images" msgid="5861633549189045666">"រូបភាព"</string>
     <string name="root_videos" msgid="8792703517064649453">"វីដេអូ"</string>
     <string name="root_audio" msgid="3505830755201326018">"សំឡេង"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index d7cd85d..abdb978 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"ಸ್ಥಳೀಯ ಸಂಗ್ರಹಣೆ"</string>
     <string name="app_label" msgid="9035307001052716210">"ಮಾಧ್ಯಮ ಸಂಗ್ರಹಣೆ"</string>
     <string name="artist_label" msgid="8105600993099120273">"ಕಲಾವಿದರು"</string>
+    <string name="unknown" msgid="2059049215682829375">"ಅಪರಿಚಿತ"</string>
     <string name="root_images" msgid="5861633549189045666">"ಚಿತ್ರಗಳು"</string>
     <string name="root_videos" msgid="8792703517064649453">"ವೀಡಿಯೊಗಳು"</string>
     <string name="root_audio" msgid="3505830755201326018">"ಆಡಿಯೊ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index f148c88..d2b70df 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"로컬 저장소"</string>
     <string name="app_label" msgid="9035307001052716210">"미디어 저장소"</string>
     <string name="artist_label" msgid="8105600993099120273">"아티스트"</string>
+    <string name="unknown" msgid="2059049215682829375">"알 수 없음"</string>
     <string name="root_images" msgid="5861633549189045666">"이미지"</string>
     <string name="root_videos" msgid="8792703517064649453">"동영상"</string>
     <string name="root_audio" msgid="3505830755201326018">"오디오"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 80cbbcd..93b0e2d 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Жергиликтүү сактагыч"</string>
     <string name="app_label" msgid="9035307001052716210">"Медиа сактагыч"</string>
     <string name="artist_label" msgid="8105600993099120273">"Аткаруучу"</string>
+    <string name="unknown" msgid="2059049215682829375">"Белгисиз"</string>
     <string name="root_images" msgid="5861633549189045666">"Сүрөттөр"</string>
     <string name="root_videos" msgid="8792703517064649453">"Видеолор"</string>
     <string name="root_audio" msgid="3505830755201326018">"Аудио"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index da398c4..a3553df 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"ບ່ອນຈັດເກັບຂໍ້ມູນໃນເຄື່ອງ"</string>
     <string name="app_label" msgid="9035307001052716210">"ພື້ນທີ່ຈັດເກັບຂໍ້ມູນມີເດຍ"</string>
     <string name="artist_label" msgid="8105600993099120273">"ສິນລະປິນ"</string>
+    <string name="unknown" msgid="2059049215682829375">"ບໍ່ຮູ້ຈັກ"</string>
     <string name="root_images" msgid="5861633549189045666">"ຮູບພາບ"</string>
     <string name="root_videos" msgid="8792703517064649453">"ວິດີໂອ"</string>
     <string name="root_audio" msgid="3505830755201326018">"ສຽງ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 58acc0b..5628209 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Vietinė saugykla"</string>
     <string name="app_label" msgid="9035307001052716210">"Medijos saugykla"</string>
     <string name="artist_label" msgid="8105600993099120273">"Atlikėjas"</string>
+    <string name="unknown" msgid="2059049215682829375">"Nežinoma"</string>
     <string name="root_images" msgid="5861633549189045666">"Vaizdai"</string>
     <string name="root_videos" msgid="8792703517064649453">"Vaizdo įrašai"</string>
     <string name="root_audio" msgid="3505830755201326018">"Garsas"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 9b8bb11..4aebb4c 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Lokālā krātuve"</string>
     <string name="app_label" msgid="9035307001052716210">"Multivides krātuve"</string>
     <string name="artist_label" msgid="8105600993099120273">"Izpildītājs"</string>
+    <string name="unknown" msgid="2059049215682829375">"Nezināms"</string>
     <string name="root_images" msgid="5861633549189045666">"Attēli"</string>
     <string name="root_videos" msgid="8792703517064649453">"Video"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index cbf1932..702715b 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Локална меморија"</string>
     <string name="app_label" msgid="9035307001052716210">"Капацитет за аудио-визуелни содржини"</string>
     <string name="artist_label" msgid="8105600993099120273">"Изведувач"</string>
+    <string name="unknown" msgid="2059049215682829375">"Непознат"</string>
     <string name="root_images" msgid="5861633549189045666">"Слики"</string>
     <string name="root_videos" msgid="8792703517064649453">"Видеа"</string>
     <string name="root_audio" msgid="3505830755201326018">"Аудио"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index d2c484a..805db55 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"ലോക്കൽ സ്റ്റോറേജ്"</string>
     <string name="app_label" msgid="9035307001052716210">"മീഡിയ സ്റ്റോറേജ്"</string>
     <string name="artist_label" msgid="8105600993099120273">"ആർട്ടിസ്‌റ്റ്"</string>
+    <string name="unknown" msgid="2059049215682829375">"അജ്ഞാതം"</string>
     <string name="root_images" msgid="5861633549189045666">"ചിത്രങ്ങൾ"</string>
     <string name="root_videos" msgid="8792703517064649453">"വീഡിയോകൾ"</string>
     <string name="root_audio" msgid="3505830755201326018">"ഓഡിയോ"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index b68060a..006edff 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Дотоод сан"</string>
     <string name="app_label" msgid="9035307001052716210">"Медиа санах ой"</string>
     <string name="artist_label" msgid="8105600993099120273">"Уран бүтээлч"</string>
+    <string name="unknown" msgid="2059049215682829375">"Тодорхойгүй"</string>
     <string name="root_images" msgid="5861633549189045666">"Зураг"</string>
     <string name="root_videos" msgid="8792703517064649453">"Бичлэг"</string>
     <string name="root_audio" msgid="3505830755201326018">"Аудио"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 1bcef16..29efbc1 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"स्थानिक स्टोरेज"</string>
     <string name="app_label" msgid="9035307001052716210">"मीडिया स्टोरेज"</string>
     <string name="artist_label" msgid="8105600993099120273">"कलाकार"</string>
+    <string name="unknown" msgid="2059049215682829375">"अज्ञात"</string>
     <string name="root_images" msgid="5861633549189045666">"इमेज"</string>
     <string name="root_videos" msgid="8792703517064649453">"व्हिडिओ"</string>
     <string name="root_audio" msgid="3505830755201326018">"ऑडिओ"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 185fa0c..d8176dc 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Storan setempat"</string>
     <string name="app_label" msgid="9035307001052716210">"Storan Media"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artis"</string>
+    <string name="unknown" msgid="2059049215682829375">"Tidak diketahui"</string>
     <string name="root_images" msgid="5861633549189045666">"Imej"</string>
     <string name="root_videos" msgid="8792703517064649453">"Video"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 1aac838..e84b389 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"စက်တွင်း သိုလှောင်ခန်း"</string>
     <string name="app_label" msgid="9035307001052716210">"မီဒီယာ သိုလှောင်ခန်း"</string>
     <string name="artist_label" msgid="8105600993099120273">"အနုပညာရှင်"</string>
+    <string name="unknown" msgid="2059049215682829375">"အမျိုးအမည်မသိ"</string>
     <string name="root_images" msgid="5861633549189045666">"ပုံများ"</string>
     <string name="root_videos" msgid="8792703517064649453">"ဗီဒီယိုများ"</string>
     <string name="root_audio" msgid="3505830755201326018">"အသံ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index cc227e2..c3b3e86 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Lokal lagring"</string>
     <string name="app_label" msgid="9035307001052716210">"Medielagring"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artist"</string>
+    <string name="unknown" msgid="2059049215682829375">"Ukjent"</string>
     <string name="root_images" msgid="5861633549189045666">"Bilder"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videoer"</string>
     <string name="root_audio" msgid="3505830755201326018">"Lyd"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 27cb12c..cbaeef0 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"स्थानीय भण्डारण"</string>
     <string name="app_label" msgid="9035307001052716210">"मिडिया भण्डारण"</string>
     <string name="artist_label" msgid="8105600993099120273">"कलाकार"</string>
+    <string name="unknown" msgid="2059049215682829375">"अज्ञात"</string>
     <string name="root_images" msgid="5861633549189045666">"छविहरू"</string>
     <string name="root_videos" msgid="8792703517064649453">"भिडियोहरू"</string>
     <string name="root_audio" msgid="3505830755201326018">"अडियो"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index e0b0696..738bf10 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Lokale opslag"</string>
     <string name="app_label" msgid="9035307001052716210">"Mediaopslag"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artiest"</string>
+    <string name="unknown" msgid="2059049215682829375">"Onbekend"</string>
     <string name="root_images" msgid="5861633549189045666">"Afbeeldingen"</string>
     <string name="root_videos" msgid="8792703517064649453">"Video\'s"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 95bd684..c8ae574 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"ଲୋକାଲ୍‍ ଷ୍ଟୋରେଜ୍‍"</string>
     <string name="app_label" msgid="9035307001052716210">"ମିଡିଆ ଷ୍ଟୋରେଜ୍"</string>
     <string name="artist_label" msgid="8105600993099120273">"କଳାକାର"</string>
+    <string name="unknown" msgid="2059049215682829375">"ଅଜଣା"</string>
     <string name="root_images" msgid="5861633549189045666">"ଇମେଜ୍‌"</string>
     <string name="root_videos" msgid="8792703517064649453">"ଭିଡିଓ"</string>
     <string name="root_audio" msgid="3505830755201326018">"ଅଡିଓ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index b53b255..3843a43 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"ਸਥਾਨਕ ਸਟੋਰੇਜ"</string>
     <string name="app_label" msgid="9035307001052716210">"ਮੀਡੀਆ ਸਟੋਰੇਜ"</string>
     <string name="artist_label" msgid="8105600993099120273">"ਕਲਾਕਾਰ"</string>
+    <string name="unknown" msgid="2059049215682829375">"ਅਗਿਆਤ"</string>
     <string name="root_images" msgid="5861633549189045666">"ਚਿਤਰ"</string>
     <string name="root_videos" msgid="8792703517064649453">"ਵੀਡੀਓ"</string>
     <string name="root_audio" msgid="3505830755201326018">" ਆਡੀਓ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 8ae25d3..fbf722f 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Pamięć lokalna"</string>
     <string name="app_label" msgid="9035307001052716210">"Przechowywanie multimediów"</string>
     <string name="artist_label" msgid="8105600993099120273">"Wykonawca"</string>
+    <string name="unknown" msgid="2059049215682829375">"Nieznany"</string>
     <string name="root_images" msgid="5861633549189045666">"Grafika"</string>
     <string name="root_videos" msgid="8792703517064649453">"Filmy"</string>
     <string name="root_audio" msgid="3505830755201326018">"Dźwięk"</string>
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
index 535ef31..3c9ebad 100644
--- a/res/values-pt-rBR/strings.xml
+++ b/res/values-pt-rBR/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Armazenamento local"</string>
     <string name="app_label" msgid="9035307001052716210">"Armazenamento de mídia"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artista"</string>
+    <string name="unknown" msgid="2059049215682829375">"Desconhecido"</string>
     <string name="root_images" msgid="5861633549189045666">"Imagens"</string>
     <string name="root_videos" msgid="8792703517064649453">"Vídeos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Áudio"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 2f36357..a5f149d 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Armazenamento local"</string>
     <string name="app_label" msgid="9035307001052716210">"Armazenamento de multimédia"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artista"</string>
+    <string name="unknown" msgid="2059049215682829375">"Desconhecido"</string>
     <string name="root_images" msgid="5861633549189045666">"Imagens"</string>
     <string name="root_videos" msgid="8792703517064649453">"Vídeos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Áudio"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 535ef31..3c9ebad 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Armazenamento local"</string>
     <string name="app_label" msgid="9035307001052716210">"Armazenamento de mídia"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artista"</string>
+    <string name="unknown" msgid="2059049215682829375">"Desconhecido"</string>
     <string name="root_images" msgid="5861633549189045666">"Imagens"</string>
     <string name="root_videos" msgid="8792703517064649453">"Vídeos"</string>
     <string name="root_audio" msgid="3505830755201326018">"Áudio"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index c128d84..42f5420 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Stocare locală"</string>
     <string name="app_label" msgid="9035307001052716210">"Stocarea conținutului media"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artist"</string>
+    <string name="unknown" msgid="2059049215682829375">"Necunoscut"</string>
     <string name="root_images" msgid="5861633549189045666">"Imagini"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videoclipuri"</string>
     <string name="root_audio" msgid="3505830755201326018">"Conținut audio"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 87c7a25..8a454a8 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Локальное хранилище"</string>
     <string name="app_label" msgid="9035307001052716210">"Хранилище мультимедиа"</string>
     <string name="artist_label" msgid="8105600993099120273">"Исполнитель"</string>
+    <string name="unknown" msgid="2059049215682829375">"Неизвестно"</string>
     <string name="root_images" msgid="5861633549189045666">"Изображения"</string>
     <string name="root_videos" msgid="8792703517064649453">"Видео"</string>
     <string name="root_audio" msgid="3505830755201326018">"Аудио"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 100194e..2515a5e 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"පෙදෙසි ආචයනය"</string>
     <string name="app_label" msgid="9035307001052716210">"මාධ්‍ය ගබඩාව"</string>
     <string name="artist_label" msgid="8105600993099120273">"කලාකරු"</string>
+    <string name="unknown" msgid="2059049215682829375">"නොදනී"</string>
     <string name="root_images" msgid="5861633549189045666">"රූප"</string>
     <string name="root_videos" msgid="8792703517064649453">"වීඩියෝ"</string>
     <string name="root_audio" msgid="3505830755201326018">"ශ්‍රව්‍යය"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 6b64622..06c9dad 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Miestne úložisko"</string>
     <string name="app_label" msgid="9035307001052716210">"Úložisko médií"</string>
     <string name="artist_label" msgid="8105600993099120273">"Interpret"</string>
+    <string name="unknown" msgid="2059049215682829375">"Neznáme"</string>
     <string name="root_images" msgid="5861633549189045666">"Obrázky"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videá"</string>
     <string name="root_audio" msgid="3505830755201326018">"Zvuk"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 2f08c9a..4685a70 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Lokalna shramba"</string>
     <string name="app_label" msgid="9035307001052716210">"Shramba za predstavnost"</string>
     <string name="artist_label" msgid="8105600993099120273">"Izvajalec"</string>
+    <string name="unknown" msgid="2059049215682829375">"Neznano"</string>
     <string name="root_images" msgid="5861633549189045666">"Slike"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videoposnetki"</string>
     <string name="root_audio" msgid="3505830755201326018">"Zvočni posnetki"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 2e339e8..511b37e 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Hapësira ruajtëse lokale"</string>
     <string name="app_label" msgid="9035307001052716210">"Hapësira ruajtëse e medias"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artisti"</string>
+    <string name="unknown" msgid="2059049215682829375">"I panjohur"</string>
     <string name="root_images" msgid="5861633549189045666">"Fotografitë"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videot"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audioja"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 08f34dd..999713d 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Локални меморијски простор"</string>
     <string name="app_label" msgid="9035307001052716210">"Меморијски простор за медије"</string>
     <string name="artist_label" msgid="8105600993099120273">"Извођач"</string>
+    <string name="unknown" msgid="2059049215682829375">"Непознато"</string>
     <string name="root_images" msgid="5861633549189045666">"Слике"</string>
     <string name="root_videos" msgid="8792703517064649453">"Видео снимци"</string>
     <string name="root_audio" msgid="3505830755201326018">"Звук"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 93f21bc..28c3393 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Lokal lagring"</string>
     <string name="app_label" msgid="9035307001052716210">"Medialagring"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artist"</string>
+    <string name="unknown" msgid="2059049215682829375">"Okänd"</string>
     <string name="root_images" msgid="5861633549189045666">"Bilder"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videoklipp"</string>
     <string name="root_audio" msgid="3505830755201326018">"Ljud"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index c88fbe3..b298cfd 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Hifadhi ya ndani"</string>
     <string name="app_label" msgid="9035307001052716210">"Hifadhi ya Maudhui"</string>
     <string name="artist_label" msgid="8105600993099120273">"Msanii"</string>
+    <string name="unknown" msgid="2059049215682829375">"Isiyojulikana"</string>
     <string name="root_images" msgid="5861633549189045666">"Picha"</string>
     <string name="root_videos" msgid="8792703517064649453">"Video"</string>
     <string name="root_audio" msgid="3505830755201326018">"Sauti"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 0724035..bb71e79 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"சாதனச் சேமிப்பகம்"</string>
     <string name="app_label" msgid="9035307001052716210">"மீடியா சேமிப்பிடம்"</string>
     <string name="artist_label" msgid="8105600993099120273">"கலைஞர்"</string>
+    <string name="unknown" msgid="2059049215682829375">"அறியாதது"</string>
     <string name="root_images" msgid="5861633549189045666">"படங்கள்"</string>
     <string name="root_videos" msgid="8792703517064649453">"வீடியோக்கள்"</string>
     <string name="root_audio" msgid="3505830755201326018">"ஆடியோ"</string>
@@ -28,41 +29,96 @@
     <string name="grant_dialog_button_allow" msgid="1644287024501033471">"அனுமதி"</string>
     <string name="grant_dialog_button_deny" msgid="6190589471415815741">"நிராகரி"</string>
     <!-- no translation found for permission_more_thumb (3530243737498196228) -->
-    <!-- no translation found for permission_more_text (7291997297174507324) -->
-    <!-- no translation found for permission_write_audio (3539998638571517689) -->
-    <!-- no translation found for permission_write_video (8695335317588947410) -->
-    <!-- no translation found for permission_write_image (9032317900030650266) -->
-    <!-- no translation found for permission_write_generic (3099002288826692797) -->
-    <!-- no translation found for permission_write_grant (3160858917750790130) -->
-    <skip />
-    <!-- no translation found for permission_write_deny (7090074081877311071) -->
-    <skip />
-    <!-- no translation found for permission_trash_audio (6116371056718108592) -->
-    <!-- no translation found for permission_trash_audio_info (5002847308948758655) -->
-    <!-- no translation found for permission_trash_video (7900111984425589714) -->
-    <!-- no translation found for permission_trash_video_info (4604871492287117394) -->
-    <!-- no translation found for permission_trash_image (9204660448046457869) -->
-    <!-- no translation found for permission_trash_image_info (4214797662365755925) -->
-    <!-- no translation found for permission_trash_generic (427752775725644743) -->
-    <!-- no translation found for permission_trash_generic_info (7932965324027540546) -->
-    <!-- no translation found for permission_trash_grant (2279489962040272178) -->
-    <skip />
-    <!-- no translation found for permission_trash_deny (2447949475239137483) -->
-    <skip />
-    <!-- no translation found for permission_untrash_audio (5716379492864435914) -->
-    <!-- no translation found for permission_untrash_video (4554487646802678525) -->
-    <!-- no translation found for permission_untrash_image (3209306923499767662) -->
-    <!-- no translation found for permission_untrash_generic (4607666277638602132) -->
-    <!-- no translation found for permission_untrash_grant (8438940480373451790) -->
-    <skip />
-    <!-- no translation found for permission_untrash_deny (8008055757469903604) -->
-    <skip />
-    <!-- no translation found for permission_delete_audio (6390202287788379608) -->
-    <!-- no translation found for permission_delete_video (1577978206881284398) -->
-    <!-- no translation found for permission_delete_image (8435429587272893063) -->
-    <!-- no translation found for permission_delete_generic (5949614272661654178) -->
-    <!-- no translation found for permission_delete_grant (8682518049167813926) -->
-    <skip />
-    <!-- no translation found for permission_delete_deny (3742780367403897552) -->
-    <skip />
+    <plurals name="permission_more_text" formatted="false" msgid="7291997297174507324">
+      <item quantity="other">அத்துடன் கூடுதலாக <xliff:g id="COUNT_1">^1</xliff:g></item>
+      <item quantity="one">அத்துடன் கூடுதலாக <xliff:g id="COUNT_0">^1</xliff:g></item>
+    </plurals>
+    <plurals name="permission_write_audio" formatted="false" msgid="3539998638571517689">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> ஆடியோ ஃபைல்களை மாற்ற <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்த ஆடியோ ஃபைலை மாற்ற <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_write_video" formatted="false" msgid="8695335317588947410">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> வீடியோக்களை மாற்ற <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்த வீடியோவை மாற்ற <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_write_image" formatted="false" msgid="9032317900030650266">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> படங்களை மாற்ற <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்தப் படத்தை மாற்ற <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_write_generic" formatted="false" msgid="3099002288826692797">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> ஐயும் மாற்ற <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இதை மாற்ற <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <string name="permission_write_grant" msgid="3160858917750790130">"மாற்று"</string>
+    <string name="permission_write_deny" msgid="7090074081877311071">"வேண்டாம்"</string>
+    <plurals name="permission_trash_audio" formatted="false" msgid="6116371056718108592">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> ஆடியோ ஃபைல்களைக் குப்பைக்கு நகர்த்த <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்த ஆடியோ ஃபைலைக் குப்பைக்கு நகர்த்த <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_trash_audio_info" formatted="false" msgid="5002847308948758655">
+      <item quantity="other"><xliff:g id="DURATION_1">^3</xliff:g> நாட்களுக்குப் பிறகு இந்த ஆடியோ ஃபைல்கள் நிரந்தரமாக நீக்கப்படும்</item>
+      <item quantity="one"><xliff:g id="DURATION_0">^3</xliff:g> நாட்களுக்குப் பிறகு இந்த ஆடியோ ஃபைல் நிரந்தரமாக நீக்கப்படும்</item>
+    </plurals>
+    <plurals name="permission_trash_video" formatted="false" msgid="7900111984425589714">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> வீடியோக்களைக் குப்பைக்கு நகர்த்த <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்த வீடியோவைக் குப்பைக்கு நகர்த்த <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_trash_video_info" formatted="false" msgid="4604871492287117394">
+      <item quantity="other">இந்த வீடியோக்கள் <xliff:g id="DURATION_1">^3</xliff:g> நாட்களுக்குப் பிறகு நிரந்தரமாக நீக்கப்படும்</item>
+      <item quantity="one">இந்த வீடியோ <xliff:g id="DURATION_0">^3</xliff:g> நாட்களுக்குப் பிறகு நிரந்தரமாக நீக்கப்படும்</item>
+    </plurals>
+    <plurals name="permission_trash_image" formatted="false" msgid="9204660448046457869">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> படங்களைக் குப்பைக்கு நகர்த்த <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்தப் படத்தைக் குப்பைக்கு நகர்த்த <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_trash_image_info" formatted="false" msgid="4214797662365755925">
+      <item quantity="other">இந்தப் படங்கள் <xliff:g id="DURATION_1">^3</xliff:g> நாட்களுக்குப் பிறகு நிரந்தரமாக நீக்கப்படும்</item>
+      <item quantity="one">இந்தப் படம் <xliff:g id="DURATION_0">^3</xliff:g> நாட்களுக்குப் பிறகு நிரந்தரமாக நீக்கப்படும்</item>
+    </plurals>
+    <plurals name="permission_trash_generic" formatted="false" msgid="427752775725644743">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> ஐயும் குப்பைக்கு நகர்த்த <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இதைக் குப்பைக்கு நகர்த்த <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_trash_generic_info" formatted="false" msgid="7932965324027540546">
+      <item quantity="other">இவை <xliff:g id="DURATION_1">^3</xliff:g> நாட்களுக்குப் பிறகு நிரந்தரமாக நீக்கப்படும்</item>
+      <item quantity="one">இது <xliff:g id="DURATION_0">^3</xliff:g> நாட்களுக்குப் பிறகு நிரந்தரமாக நீக்கப்படும்</item>
+    </plurals>
+    <string name="permission_trash_grant" msgid="2279489962040272178">"குப்பைக்கு நகர்த்து"</string>
+    <string name="permission_trash_deny" msgid="2447949475239137483">"வேண்டாம்"</string>
+    <plurals name="permission_untrash_audio" formatted="false" msgid="5716379492864435914">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> ஆடியோ ஃபைல்களைக் குப்பையிலிருந்து நகர்த்த <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்த ஆடியோ ஃபைலைக் குப்பையிலிருந்து நகர்த்த <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_untrash_video" formatted="false" msgid="4554487646802678525">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> வீடியோக்களைக் குப்பையிலிருந்து நகர்த்த <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்த வீடியோவைக் குப்பையிலிருந்து நகர்த்த <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_untrash_image" formatted="false" msgid="3209306923499767662">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> படங்களைக் குப்பையிலிருந்து நகர்த்த <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்தப் படத்தைக் குப்பையிலிருந்து நகர்த்த <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_untrash_generic" formatted="false" msgid="4607666277638602132">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> ஐயும் குப்பையிலிருந்து நகர்த்த <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இதைக் குப்பையிலிருந்து நகர்த்த <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <string name="permission_untrash_grant" msgid="8438940480373451790">"குப்பையிலிருந்து நகர்த்து"</string>
+    <string name="permission_untrash_deny" msgid="8008055757469903604">"வேண்டாம்"</string>
+    <plurals name="permission_delete_audio" formatted="false" msgid="6390202287788379608">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> ஆடியோ ஃபைல்களை நீக்க <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்த ஆடியோ ஃபைலை நீக்க <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_delete_video" formatted="false" msgid="1577978206881284398">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> வீடியோக்களை நீக்க <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்த வீடியோவை நீக்க <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_delete_image" formatted="false" msgid="8435429587272893063">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> படங்களை நீக்க <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இந்தப் படத்தை நீக்க <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <plurals name="permission_delete_generic" formatted="false" msgid="5949614272661654178">
+      <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> ஐயும் நீக்க <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+      <item quantity="one">இதை நீக்க <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
+    </plurals>
+    <string name="permission_delete_grant" msgid="8682518049167813926">"நீக்கு"</string>
+    <string name="permission_delete_deny" msgid="3742780367403897552">"வேண்டாம்"</string>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 66cd15c..8221306 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"స్థానిక నిల్వ"</string>
     <string name="app_label" msgid="9035307001052716210">"మీడియా నిల్వ"</string>
     <string name="artist_label" msgid="8105600993099120273">"కళాకారుడు"</string>
+    <string name="unknown" msgid="2059049215682829375">"తెలియదు"</string>
     <string name="root_images" msgid="5861633549189045666">"చిత్రాలు"</string>
     <string name="root_videos" msgid="8792703517064649453">"వీడియోలు"</string>
     <string name="root_audio" msgid="3505830755201326018">"ఆడియో"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index c7dfe87..962de15 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"พื้นที่เก็บข้อมูลในเครื่อง"</string>
     <string name="app_label" msgid="9035307001052716210">"พื้นที่เก็บข้อมูลสื่อ"</string>
     <string name="artist_label" msgid="8105600993099120273">"ศิลปิน"</string>
+    <string name="unknown" msgid="2059049215682829375">"ไม่ทราบ"</string>
     <string name="root_images" msgid="5861633549189045666">"รูปภาพ"</string>
     <string name="root_videos" msgid="8792703517064649453">"วิดีโอ"</string>
     <string name="root_audio" msgid="3505830755201326018">"เสียง"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index d23a929..a2eeda4 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Lokal na storage"</string>
     <string name="app_label" msgid="9035307001052716210">"Storage ng Media"</string>
     <string name="artist_label" msgid="8105600993099120273">"Artist"</string>
+    <string name="unknown" msgid="2059049215682829375">"Hindi alam"</string>
     <string name="root_images" msgid="5861633549189045666">"Mga Larawan"</string>
     <string name="root_videos" msgid="8792703517064649453">"Mga Video"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 6983e5e..40d21a0 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Yerel depolama"</string>
     <string name="app_label" msgid="9035307001052716210">"Medya Deposu"</string>
     <string name="artist_label" msgid="8105600993099120273">"Sanatçı"</string>
+    <string name="unknown" msgid="2059049215682829375">"Bilinmiyor"</string>
     <string name="root_images" msgid="5861633549189045666">"Resimler"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videolar"</string>
     <string name="root_audio" msgid="3505830755201326018">"Ses"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 74b7ab3..17c95fb 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Локальна пам’ять"</string>
     <string name="app_label" msgid="9035307001052716210">"Сховище медіа-файлів"</string>
     <string name="artist_label" msgid="8105600993099120273">"Виконавець"</string>
+    <string name="unknown" msgid="2059049215682829375">"Невідомо"</string>
     <string name="root_images" msgid="5861633549189045666">"Зображення"</string>
     <string name="root_videos" msgid="8792703517064649453">"Відео"</string>
     <string name="root_audio" msgid="3505830755201326018">"Аудіо"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 3e5bae1..4827e4b 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"مقامی اسٹوریج"</string>
     <string name="app_label" msgid="9035307001052716210">"میڈیا اسٹوریج"</string>
     <string name="artist_label" msgid="8105600993099120273">"فنکار"</string>
+    <string name="unknown" msgid="2059049215682829375">"نامعلوم"</string>
     <string name="root_images" msgid="5861633549189045666">"تصاوير"</string>
     <string name="root_videos" msgid="8792703517064649453">"ویڈیوز"</string>
     <string name="root_audio" msgid="3505830755201326018">"آڈیو"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index aabf687..d2631d7 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Mahalliy xotira"</string>
     <string name="app_label" msgid="9035307001052716210">"Multimedia xotirasi"</string>
     <string name="artist_label" msgid="8105600993099120273">"Ijrochi"</string>
+    <string name="unknown" msgid="2059049215682829375">"Noaniq"</string>
     <string name="root_images" msgid="5861633549189045666">"Rasmlar"</string>
     <string name="root_videos" msgid="8792703517064649453">"Videolar"</string>
     <string name="root_audio" msgid="3505830755201326018">"Audio"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 2619806..2a14663 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Bộ nhớ cục bộ"</string>
     <string name="app_label" msgid="9035307001052716210">"Bộ nhớ phương tiện"</string>
     <string name="artist_label" msgid="8105600993099120273">"Nghệ sĩ"</string>
+    <string name="unknown" msgid="2059049215682829375">"Không xác định"</string>
     <string name="root_images" msgid="5861633549189045666">"Hình ảnh"</string>
     <string name="root_videos" msgid="8792703517064649453">"Video"</string>
     <string name="root_audio" msgid="3505830755201326018">"Âm thanh"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 6134dde..6fbfaae 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"本地存储空间"</string>
     <string name="app_label" msgid="9035307001052716210">"媒体存储设备"</string>
     <string name="artist_label" msgid="8105600993099120273">"音乐人"</string>
+    <string name="unknown" msgid="2059049215682829375">"未知"</string>
     <string name="root_images" msgid="5861633549189045666">"图片"</string>
     <string name="root_videos" msgid="8792703517064649453">"视频"</string>
     <string name="root_audio" msgid="3505830755201326018">"音频"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 5ad86fd..6861970 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"本機儲存空間"</string>
     <string name="app_label" msgid="9035307001052716210">"媒體儲存空間"</string>
     <string name="artist_label" msgid="8105600993099120273">"歌手"</string>
+    <string name="unknown" msgid="2059049215682829375">"不明"</string>
     <string name="root_images" msgid="5861633549189045666">"相片"</string>
     <string name="root_videos" msgid="8792703517064649453">"影片"</string>
     <string name="root_audio" msgid="3505830755201326018">"音訊"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 878b235..ae83cca 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"本機儲存空間"</string>
     <string name="app_label" msgid="9035307001052716210">"媒體儲存空間"</string>
     <string name="artist_label" msgid="8105600993099120273">"演出者"</string>
+    <string name="unknown" msgid="2059049215682829375">"不明"</string>
     <string name="root_images" msgid="5861633549189045666">"圖片"</string>
     <string name="root_videos" msgid="8792703517064649453">"影片"</string>
     <string name="root_audio" msgid="3505830755201326018">"音訊"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index bbf7481..75d03a3 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -20,6 +20,7 @@
     <string name="storage_description" msgid="4081716890357580107">"Isitoreji sasendaweni"</string>
     <string name="app_label" msgid="9035307001052716210">"Isitoreji Semidiya"</string>
     <string name="artist_label" msgid="8105600993099120273">"Umculi"</string>
+    <string name="unknown" msgid="2059049215682829375">"Akwaziwa"</string>
     <string name="root_images" msgid="5861633549189045666">"Izithombe"</string>
     <string name="root_videos" msgid="8792703517064649453">"Amavidiyo"</string>
     <string name="root_audio" msgid="3505830755201326018">"Umsindo"</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 92e98f9..0c35d75 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -36,6 +36,8 @@
     <string name="root_videos">Videos</string>
     <!-- Title for documents backend that offers audio. [CHAR LIMIT=24] -->
     <string name="root_audio">Audio</string>
+    <!-- Title for documents backend that offers documents. [CHAR LIMIT=24] -->
+    <string name="root_documents">Documents</string>
 
     <!-- Message telling users that the app must request permission before working with a media item. [CHAR LIMIT=128] -->
     <string name="permission_required">Permission required to modify or delete this item.</string>
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index c579e80..3a4f6a2 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -54,12 +54,14 @@
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.providers.media.util.BackgroundThread;
 import com.android.providers.media.util.DatabaseUtils;
 import com.android.providers.media.util.FileUtils;
 import com.android.providers.media.util.Logging;
+import com.android.providers.media.util.MimeUtils;
 
 import java.io.File;
 import java.io.FilenameFilter;
@@ -96,27 +98,44 @@
     final boolean mInternal;  // True if this is the internal database
     final boolean mEarlyUpgrade;
     final boolean mLegacyProvider;
-    final Class<? extends Annotation> mColumnAnnotation;
-    final OnSchemaChangeListener mListener;
+    final @Nullable Class<? extends Annotation> mColumnAnnotation;
+    final @Nullable OnSchemaChangeListener mSchemaListener;
+    final @Nullable OnFilesChangeListener mFilesListener;
     final Set<String> mFilterVolumeNames = new ArraySet<>();
     long mScanStartTime;
     long mScanStopTime;
+    /** Flag indicating if a schema change is in progress */
+    boolean mSchemaChanging;
 
     public interface OnSchemaChangeListener {
         public void onSchemaChange(@NonNull String volumeName, int versionFrom, int versionTo,
                 long itemCount, long durationMillis);
     }
 
+    public interface OnFilesChangeListener {
+        public void onInsert(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
+                int mediaType, boolean isDownload);
+        public void onUpdate(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
+                int oldMediaType, boolean oldIsDownload,
+                int newMediaType, boolean newIsDownload);
+        public void onDelete(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
+                int mediaType, boolean isDownload);
+    }
+
     public DatabaseHelper(Context context, String name,
             boolean internal, boolean earlyUpgrade, boolean legacyProvider,
-            Class<? extends Annotation> columnAnnotation, OnSchemaChangeListener listener) {
-        this(context, name, getDatabaseVersion(context),
-                internal, earlyUpgrade, legacyProvider, columnAnnotation, listener);
+            @Nullable Class<? extends Annotation> columnAnnotation,
+            @Nullable OnSchemaChangeListener schemaListener,
+            @Nullable OnFilesChangeListener filesListener) {
+        this(context, name, getDatabaseVersion(context), internal, earlyUpgrade, legacyProvider,
+                columnAnnotation, schemaListener, filesListener);
     }
 
     public DatabaseHelper(Context context, String name, int version,
             boolean internal, boolean earlyUpgrade, boolean legacyProvider,
-            Class<? extends Annotation> columnAnnotation, OnSchemaChangeListener listener) {
+            @Nullable Class<? extends Annotation> columnAnnotation,
+            @Nullable OnSchemaChangeListener schemaListener,
+            @Nullable OnFilesChangeListener filesListener) {
         super(context, name, null, version);
         mContext = context;
         mName = name;
@@ -126,7 +145,8 @@
         mEarlyUpgrade = earlyUpgrade;
         mLegacyProvider = legacyProvider;
         mColumnAnnotation = columnAnnotation;
-        mListener = listener;
+        mSchemaListener = schemaListener;
+        mFilesListener = filesListener;
 
         // Configure default filters until we hear differently
         if (mInternal) {
@@ -185,21 +205,79 @@
     }
 
     @Override
+    public void onConfigure(SQLiteDatabase db) {
+        db.setCustomScalarFunction("_INSERT", (arg) -> {
+            if (arg != null && mFilesListener != null && !mSchemaChanging) {
+                final String[] split = arg.split(":");
+                final String volumeName = split[0];
+                final long id = Long.parseLong(split[1]);
+                final int mediaType = Integer.parseInt(split[2]);
+                final boolean isDownload = Integer.parseInt(split[3]) != 0;
+
+                mFilesListener.onInsert(DatabaseHelper.this, volumeName, id, mediaType, isDownload);
+            }
+            return null;
+        });
+        db.setCustomScalarFunction("_UPDATE", (arg) -> {
+            if (arg != null && mFilesListener != null && !mSchemaChanging) {
+                final String[] split = arg.split(":");
+                final String volumeName = split[0];
+                final long id = Long.parseLong(split[1]);
+                final int oldMediaType = Integer.parseInt(split[2]);
+                final boolean oldIsDownload = Integer.parseInt(split[3]) != 0;
+                final int newMediaType = Integer.parseInt(split[4]);
+                final boolean newIsDownload = Integer.parseInt(split[5]) != 0;
+
+                mFilesListener.onUpdate(DatabaseHelper.this, volumeName, id,
+                        oldMediaType, oldIsDownload, newMediaType, newIsDownload);
+            }
+            return null;
+        });
+        db.setCustomScalarFunction("_DELETE", (arg) -> {
+            if (arg != null && mFilesListener != null && !mSchemaChanging) {
+                final String[] split = arg.split(":");
+                final String volumeName = split[0];
+                final long id = Long.parseLong(split[1]);
+                final int mediaType = Integer.parseInt(split[2]);
+                final boolean isDownload = Integer.parseInt(split[3]) != 0;
+
+                mFilesListener.onDelete(DatabaseHelper.this, volumeName, id, mediaType, isDownload);
+            }
+            return null;
+        });
+    }
+
+    @Override
     public void onCreate(final SQLiteDatabase db) {
         Log.v(TAG, "onCreate() for " + mName);
-        updateDatabase(db, 0, mVersion);
+        mSchemaChanging = true;
+        try {
+            updateDatabase(db, 0, mVersion);
+        } finally {
+            mSchemaChanging = false;
+        }
     }
 
     @Override
     public void onUpgrade(final SQLiteDatabase db, final int oldV, final int newV) {
         Log.v(TAG, "onUpgrade() for " + mName + " from " + oldV + " to " + newV);
-        updateDatabase(db, oldV, newV);
+        mSchemaChanging = true;
+        try {
+            updateDatabase(db, oldV, newV);
+        } finally {
+            mSchemaChanging = false;
+        }
     }
 
     @Override
     public void onDowngrade(final SQLiteDatabase db, final int oldV, final int newV) {
         Log.v(TAG, "onDowngrade() for " + mName + " from " + oldV + " to " + newV);
-        downgradeDatabase(db, oldV, newV);
+        mSchemaChanging = true;
+        try {
+            downgradeDatabase(db, oldV, newV);
+        } finally {
+            mSchemaChanging = false;
+        }
     }
 
     @GuardedBy("mProjectionMapCache")
@@ -237,31 +315,64 @@
     }
 
     /**
-     * List of {@link Uri} that would have been sent directly via
-     * {@link ContentResolver#notifyChange}, but are instead being collected
-     * due to an ongoing transaction.
+     * Local state related to any transaction currently active on a specific
+     * thread, such as collecting the set of {@link Uri} that should be notified
+     * upon transaction success.
      */
-    private final ThreadLocal<List<Uri>> mNotifyChanges = new ThreadLocal<>();
+    private final ThreadLocal<TransactionState> mTransactionState = new ThreadLocal<>();
+
+    private static class TransactionState {
+        /**
+         * Flag indicating if this transaction has been marked as being
+         * successful.
+         */
+        public boolean successful;
+
+        /**
+         * List of {@link Uri} that would have been sent directly via
+         * {@link ContentResolver#notifyChange}, but are instead being collected
+         * due to this ongoing transaction.
+         */
+        public final List<Uri> notifyChanges = new ArrayList<>();
+    }
 
     public void beginTransaction() {
-        getWritableDatabase().beginTransaction();
-        getWritableDatabase().execSQL("UPDATE local_metadata SET generation=generation+1;");
-        mNotifyChanges.set(new ArrayList<>());
+        if (mTransactionState.get() != null) {
+            throw new IllegalStateException("Nested transactions not supported");
+        }
+        mTransactionState.set(new TransactionState());
+
+        final SQLiteDatabase db = getWritableDatabase();
+        db.beginTransaction();
+        db.execSQL("UPDATE local_metadata SET generation=generation+1;");
     }
 
     public void setTransactionSuccessful() {
-        getWritableDatabase().setTransactionSuccessful();
+        final TransactionState state = mTransactionState.get();
+        if (state == null) {
+            throw new IllegalStateException("No transaction in progress");
+        }
+        state.successful = true;
+
+        final SQLiteDatabase db = getWritableDatabase();
+        db.setTransactionSuccessful();
     }
 
     public void endTransaction() {
-        getWritableDatabase().endTransaction();
-        final List<Uri> uris = mNotifyChanges.get();
-        if (uris != null) {
+        final TransactionState state = mTransactionState.get();
+        if (state == null) {
+            throw new IllegalStateException("No transaction in progress");
+        }
+        mTransactionState.remove();
+
+        final SQLiteDatabase db = getWritableDatabase();
+        db.endTransaction();
+
+        if (state.successful) {
             BackgroundThread.getExecutor().execute(() -> {
-                notifyChangeInternal(uris);
+                notifyChangeInternal(state.notifyChanges);
             });
         }
-        mNotifyChanges.remove();
     }
 
     /**
@@ -270,7 +381,7 @@
      * runnable inside a new transaction.
      */
     public long runWithTransaction(@NonNull LongSupplier s) {
-        if (mNotifyChanges.get() != null) {
+        if (mTransactionState.get() != null) {
             // Already inside a transaction, so we can run directly
             return s.getAsLong();
         } else {
@@ -291,11 +402,11 @@
      * notification if currently inside a transaction, and they'll be
      * clustered and sent when the transaction completes.
      */
-    public void notifyChange(Uri uri) {
+    public void notifyChange(@NonNull Uri uri) {
         if (LOGV) Log.v(TAG, "Notifying " + uri);
-        final List<Uri> uris = mNotifyChanges.get();
-        if (uris != null) {
-            uris.add(uri);
+        final TransactionState state = mTransactionState.get();
+        if (state != null) {
+            state.notifyChanges.add(uri);
         } else {
             BackgroundThread.getExecutor().execute(() -> {
                 notifySingleChangeInternal(uri);
@@ -672,6 +783,21 @@
 
     private static void createLatestTriggers(SQLiteDatabase db, boolean internal) {
         makePristineTriggers(db);
+
+        final String insertArg =
+                "new.volume_name||':'||new._id||':'||new.media_type||':'||new.is_download";
+        final String updateArg =
+                "old.volume_name||':'||old._id||':'||old.media_type||':'||old.is_download"
+                        + "||':'||new.media_type||':'||new.is_download";
+        final String deleteArg =
+                "old.volume_name||':'||old._id||':'||old.media_type||':'||old.is_download";
+
+        db.execSQL("CREATE TRIGGER files_insert AFTER INSERT ON files"
+                + " BEGIN SELECT _INSERT(" + insertArg + "); END");
+        db.execSQL("CREATE TRIGGER files_update AFTER UPDATE ON files"
+                + " BEGIN SELECT _UPDATE(" + updateArg + "); END");
+        db.execSQL("CREATE TRIGGER files_delete AFTER DELETE ON files"
+                + " BEGIN SELECT _DELETE(" + deleteArg + "); END");
     }
 
     private static void updateCollationKeys(SQLiteDatabase db) {
@@ -880,6 +1006,33 @@
         }
     }
 
+    private static void recomputeMediaTypeValues(SQLiteDatabase db) {
+        // Only update the files with MEDIA_TYPE_NONE.
+        final String selection = FileColumns.MEDIA_TYPE + "=?";
+        final String[] selectionArgs = new String[]{String.valueOf(FileColumns.MEDIA_TYPE_NONE)};
+
+        try (Cursor c = db.query("files", new String[] { FileColumns._ID, FileColumns.MIME_TYPE },
+                selection, selectionArgs, null, null, null, null)) {
+            Log.d(TAG, "Recomputing " + c.getCount() + " MediaType values");
+
+            final ContentValues values = new ContentValues();
+            while (c.moveToNext()) {
+                values.clear();
+                final long id = c.getLong(0);
+                final String mimeType = c.getString(1);
+                // Only update Document and Subtitle media type
+                if (MimeUtils.isDocumentMimeType(mimeType)) {
+                    values.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_DOCUMENT);
+                } else if (MimeUtils.isSubtitleMimeType(mimeType)) {
+                    values.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_SUBTITLE);
+                }
+                if (!values.isEmpty()) {
+                    db.update("files", values, "_id=" + id, null);
+                }
+            }
+        }
+    }
+
     static final int VERSION_J = 509;
     static final int VERSION_K = 700;
     static final int VERSION_L = 700;
@@ -888,7 +1041,7 @@
     static final int VERSION_O = 800;
     static final int VERSION_P = 900;
     static final int VERSION_Q = 1023;
-    static final int VERSION_R = 1109;
+    static final int VERSION_R = 1111;
     static final int VERSION_LATEST = VERSION_R;
 
     /**
@@ -1016,6 +1169,12 @@
             if (fromVersion < 1109) {
                 updateAddGeneration(db, internal);
             }
+            if (fromVersion < 1110) {
+                // Empty version bump to ensure triggers are recreated
+            }
+            if (fromVersion < 1111) {
+                recomputeMediaTypeValues(db);
+            }
 
             if (recomputeDataValues) {
                 recomputeDataValues(db, internal);
@@ -1030,8 +1189,8 @@
         getOrCreateUuid(db);
 
         final long elapsedMillis = (SystemClock.elapsedRealtime() - startTime);
-        if (mListener != null) {
-            mListener.onSchemaChange(mVolumeName, fromVersion, toVersion,
+        if (mSchemaListener != null) {
+            mSchemaListener.onSchemaChange(mVolumeName, fromVersion, toVersion,
                     getItemCount(db), elapsedMillis);
         }
     }
@@ -1043,8 +1202,8 @@
         createLatestSchema(db);
 
         final long elapsedMillis = (SystemClock.elapsedRealtime() - startTime);
-        if (mListener != null) {
-            mListener.onSchemaChange(mVolumeName, fromVersion, toVersion,
+        if (mSchemaListener != null) {
+            mSchemaListener.onSchemaChange(mVolumeName, fromVersion, toVersion,
                     getItemCount(db), elapsedMillis);
         }
     }
diff --git a/src/com/android/providers/media/MediaDocumentsProvider.java b/src/com/android/providers/media/MediaDocumentsProvider.java
index 8576f2e..faadee2 100644
--- a/src/com/android/providers/media/MediaDocumentsProvider.java
+++ b/src/com/android/providers/media/MediaDocumentsProvider.java
@@ -53,6 +53,7 @@
 import android.provider.MediaStore.Audio.ArtistColumns;
 import android.provider.MediaStore.Audio.Artists;
 import android.provider.MediaStore.Audio.AudioColumns;
+import android.provider.MediaStore.Files;
 import android.provider.MediaStore.Files.FileColumns;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Images.ImageColumns;
@@ -112,6 +113,8 @@
     private static final String AUDIO_MIME_TYPES = joinNewline(
             "audio/*", "application/ogg", "application/x-flac");
 
+    private static final String DOCUMENT_MIME_TYPES = joinNewline("*/*");
+
     static final String TYPE_IMAGES_ROOT = "images_root";
     static final String TYPE_IMAGES_BUCKET = "images_bucket";
     static final String TYPE_IMAGE = "image";
@@ -125,9 +128,14 @@
     static final String TYPE_ARTIST = "artist";
     static final String TYPE_ALBUM = "album";
 
+    static final String TYPE_DOCUMENTS_ROOT = "documents_root";
+    static final String TYPE_DOCUMENTS_BUCKET = "documents_bucket";
+    static final String TYPE_DOCUMENT = "document";
+
     private static boolean sReturnedImagesEmpty = false;
     private static boolean sReturnedVideosEmpty = false;
     private static boolean sReturnedAudioEmpty = false;
+    private static boolean sReturnedDocumentsEmpty = false;
 
     private static String joinNewline(String... args) {
         return TextUtils.join("\n", args);
@@ -141,7 +149,7 @@
     public static final String METADATA_VIDEO_LONGITUTE = "android.media.metadata.video:longitude";
 
     /*
-     * A mapping between media colums and metadata tag names. These keys of the
+     * A mapping between media columns and metadata tag names. These keys of the
      * map form the projection for queries against the media store database.
      */
     private static final Map<String, String> IMAGE_COLUMN_MAP = new HashMap<>();
@@ -219,6 +227,9 @@
             } else if (type == FileColumns.MEDIA_TYPE_AUDIO && sReturnedAudioEmpty) {
                 sReturnedAudioEmpty = false;
                 notifyRootsChanged(context);
+            } else if (type == FileColumns.MEDIA_TYPE_DOCUMENT && sReturnedDocumentsEmpty) {
+                sReturnedDocumentsEmpty = false;
+                notifyRootsChanged(context);
             }
         });
     }
@@ -245,6 +256,11 @@
                         AUTHORITY, getDocIdForIdent(TYPE_AUDIO, id));
                 context.revokeUriPermission(uri, ~0);
                 notifyRootsChanged(context);
+            } else if (type == FileColumns.MEDIA_TYPE_DOCUMENT) {
+                final Uri uri = DocumentsContract.buildDocumentUri(
+                        AUTHORITY, getDocIdForIdent(TYPE_DOCUMENT, id));
+                context.revokeUriPermission(uri, ~0);
+                notifyRootsChanged(context);
             }
         });
     }
@@ -279,11 +295,11 @@
         return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
     }
 
-    private static Pair<String, String[]> buildSearchSelection(String displayName,
+    static Pair<String, String[]> buildSearchSelection(String displayName,
             String[] mimeTypes, long lastModifiedAfter, long fileSizeOver, String columnDisplayName,
             String columnMimeType, String columnLastModified, String columnFileSize) {
         StringBuilder selection = new StringBuilder();
-        final ArrayList<String> selectionArgs = new ArrayList<>();
+        final List<String> selectionArgs = new ArrayList<>();
 
         if (!displayName.isEmpty()) {
             selection.append(columnDisplayName + " LIKE ?");
@@ -309,24 +325,66 @@
         }
 
         if (mimeTypes != null && mimeTypes.length > 0) {
-            for (int i = 0; i < mimeTypes.length; i++) {
-                final String type = mimeTypes[i];
-                if (i == 0) {
-                    if (selection.length() > 0) {
-                        selection.append(" AND ");
-                    }
-                    selection.append(columnMimeType + " IN ( ?");
-                } else {
-                    selection.append(", ?");
-                }
-                selectionArgs.add(type);
+            if (selection.length() > 0) {
+                selection.append(" AND ");
             }
-            selection.append(" )");
+
+            selection.append("(");
+            final List<String> tempSelectionArgs = new ArrayList<>();
+            final StringBuilder tempSelection = new StringBuilder();
+            List<String> wildcardMimeTypeList = new ArrayList<>();
+            for (int i = 0; i < mimeTypes.length; ++i) {
+                final String mimeType = mimeTypes[i];
+                if (!TextUtils.isEmpty(mimeType) && mimeType.endsWith("/*")) {
+                    wildcardMimeTypeList.add(mimeType);
+                    continue;
+                }
+
+                if (tempSelectionArgs.size() > 0) {
+                    tempSelection.append(",");
+                }
+                tempSelection.append("?");
+                tempSelectionArgs.add(mimeType);
+            }
+
+            for (int i = 0; i < wildcardMimeTypeList.size(); i++) {
+                selection.append(columnMimeType + " LIKE ?")
+                        .append((i != wildcardMimeTypeList.size() - 1) ? " OR " : "");
+                final String mimeType = wildcardMimeTypeList.get(i);
+                selectionArgs.add(mimeType.substring(0, mimeType.length() - 1) + "%");
+            }
+
+            if (tempSelectionArgs.size() > 0) {
+                if (wildcardMimeTypeList.size() > 0) {
+                    selection.append(" OR ");
+                }
+                selection.append(columnMimeType + " IN (")
+                        .append(tempSelection.toString())
+                        .append(")");
+                selectionArgs.addAll(tempSelectionArgs);
+            }
+
+            selection.append(")");
         }
 
         return new Pair<>(selection.toString(), selectionArgs.toArray(new String[0]));
     }
 
+    static Pair<String, String[]> addDocumentSelection(String selection,
+            String[] selectionArgs) {
+        String retSelection = "";
+        final List<String> retSelectionArgs = new ArrayList<>();
+        if (!TextUtils.isEmpty(selection) && selectionArgs != null) {
+            retSelection = selection + " AND ";
+            for (int i = 0; i < selectionArgs.length; i++) {
+                retSelectionArgs.add(selectionArgs[i]);
+            }
+        }
+        retSelection += FileColumns.MEDIA_TYPE + "=?";
+        retSelectionArgs.add("" + FileColumns.MEDIA_TYPE_DOCUMENT);
+        return new Pair<>(retSelection, retSelectionArgs.toArray(new String[0]));
+    }
+
     /**
      * Check whether filter mime type and get the matched mime types.
      * If we don't need to filter mime type, the matchedMimeTypes will be empty.
@@ -372,6 +430,9 @@
         } else if (TYPE_AUDIO.equals(ident.type) && ident.id != -1) {
             return ContentUris.withAppendedId(
                     Audio.Media.EXTERNAL_CONTENT_URI, ident.id);
+        } else if (TYPE_DOCUMENT.equals(ident.type) && ident.id != -1) {
+            return ContentUris.withAppendedId(
+                    Files.EXTERNAL_CONTENT_URI, ident.id);
         } else {
             throw new UnsupportedOperationException("Unsupported document " + docId);
         }
@@ -514,6 +575,7 @@
         includeImagesRoot(result);
         includeVideosRoot(result);
         includeAudioRoot(result);
+        includeDocumentsRoot(result);
         return result;
     }
 
@@ -600,6 +662,27 @@
                 if (cursor.moveToFirst()) {
                     includeAudio(result, cursor);
                 }
+            }  else if (TYPE_DOCUMENTS_ROOT.equals(ident.type)) {
+                // single root
+                includeDocumentsRootDocument(result);
+            } else if (TYPE_DOCUMENTS_BUCKET.equals(ident.type)) {
+                // single bucket
+                final Pair<String, String[]> selectionPair = addDocumentSelection(
+                        FileColumns.BUCKET_ID + "=?", queryArgs);
+                cursor = resolver.query(Files.EXTERNAL_CONTENT_URI, DocumentsBucketQuery.PROJECTION,
+                        selectionPair.first, selectionPair.second, DocumentsBucketQuery.SORT_ORDER);
+                copyNotificationUri(result, cursor);
+                if (cursor.moveToFirst()) {
+                    includeDocumentsBucket(result, cursor);
+                }
+            } else if (TYPE_DOCUMENT.equals(ident.type)) {
+                // single document
+                cursor = resolver.query(Files.EXTERNAL_CONTENT_URI, DocumentQuery.PROJECTION,
+                        FileColumns._ID + "=?", queryArgs, null);
+                copyNotificationUri(result, cursor);
+                if (cursor.moveToFirst()) {
+                    includeDocument(result, cursor);
+                }
             } else {
                 throw new UnsupportedOperationException("Unsupported document " + docId);
             }
@@ -692,6 +775,30 @@
                 while (cursor.moveToNext()) {
                     includeAudio(result, cursor);
                 }
+            } else if (TYPE_DOCUMENTS_ROOT.equals(ident.type)) {
+                // include all unique buckets
+                final Pair<String, String[]> selectionPair = addDocumentSelection(null, null);
+                cursor = resolver.query(Files.EXTERNAL_CONTENT_URI, DocumentsBucketQuery.PROJECTION,
+                        selectionPair.first, selectionPair.second, DocumentsBucketQuery.SORT_ORDER);
+                copyNotificationUri(result, cursor);
+                long lastId = Long.MIN_VALUE;
+                while (cursor.moveToNext()) {
+                    final long id = cursor.getLong(DocumentsBucketQuery.BUCKET_ID);
+                    if (lastId != id) {
+                        includeDocumentsBucket(result, cursor);
+                        lastId = id;
+                    }
+                }
+            } else if (TYPE_DOCUMENTS_BUCKET.equals(ident.type)) {
+                // include documents under bucket
+                final Pair<String, String[]> selectionPair = addDocumentSelection(
+                        FileColumns.BUCKET_ID + "=?", queryArgs);
+                cursor = resolver.query(Files.EXTERNAL_CONTENT_URI, DocumentQuery.PROJECTION,
+                        selectionPair.first, selectionPair.second, null);
+                copyNotificationUri(result, cursor);
+                while (cursor.moveToNext()) {
+                    includeDocument(result, cursor);
+                }
             } else {
                 throw new UnsupportedOperationException("Unsupported document " + docId);
             }
@@ -746,6 +853,16 @@
                 while (cursor.moveToNext() && result.getCount() < limit) {
                     includeVideo(result, cursor);
                 }
+            } else if (TYPE_DOCUMENTS_ROOT.equals(rootId)) {
+                // include all unique buckets
+                final Pair<String, String[]> selectionPair = addDocumentSelection(null, null);
+                cursor = resolver.query(Files.EXTERNAL_CONTENT_URI, DocumentQuery.PROJECTION,
+                        selectionPair.first, selectionPair.second,
+                        FileColumns.DATE_MODIFIED + " DESC");
+                copyNotificationUri(result, cursor);
+                while (cursor.moveToNext() && result.getCount() < limit) {
+                    includeDocument(result, cursor);
+                }
             } else {
                 throw new UnsupportedOperationException("Unsupported root " + rootId);
             }
@@ -838,6 +955,21 @@
                         includeAudio(result, cursor);
                     }
                 }
+            } else if (TYPE_DOCUMENTS_ROOT.equals(rootId)) {
+                final Pair<String, String[]> initialSelectionPair = buildSearchSelection(
+                        displayName, mimeTypes, lastModifiedAfter, fileSizeOver,
+                        FileColumns.DISPLAY_NAME, FileColumns.MIME_TYPE, FileColumns.DATE_MODIFIED,
+                        FileColumns.SIZE);
+                final Pair<String, String[]> selectionPair = addDocumentSelection(
+                        initialSelectionPair.first, initialSelectionPair.second);
+
+                cursor = resolver.query(Files.EXTERNAL_CONTENT_URI, DocumentQuery.PROJECTION,
+                        selectionPair.first, selectionPair.second,
+                        FileColumns.DATE_MODIFIED + " DESC");
+                copyNotificationUri(result, cursor);
+                while (cursor.moveToNext()) {
+                    includeDocument(result, cursor);
+                }
             } else {
                 throw new UnsupportedOperationException("Unsupported root " + rootId);
             }
@@ -992,6 +1124,22 @@
         row.add(Root.COLUMN_QUERY_ARGS, SUPPORTED_QUERY_ARGS);
     }
 
+    private void includeDocumentsRoot(MatrixCursor result) {
+        int flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_SEARCH;
+        if (isEmpty(Files.EXTERNAL_CONTENT_URI)) {
+            flags |= Root.FLAG_EMPTY;
+            sReturnedDocumentsEmpty = true;
+        }
+
+        final RowBuilder row = result.newRow();
+        row.add(Root.COLUMN_ROOT_ID, TYPE_DOCUMENTS_ROOT);
+        row.add(Root.COLUMN_FLAGS, flags);
+        row.add(Root.COLUMN_TITLE, getContext().getString(R.string.root_documents));
+        row.add(Root.COLUMN_DOCUMENT_ID, TYPE_DOCUMENTS_ROOT);
+        row.add(Root.COLUMN_MIME_TYPES, DOCUMENT_MIME_TYPES);
+        row.add(Root.COLUMN_QUERY_ARGS, SUPPORTED_QUERY_ARGS);
+    }
+
     private void includeImagesRootDocument(MatrixCursor result) {
         final RowBuilder row = result.newRow();
         row.add(Document.COLUMN_DOCUMENT_ID, TYPE_IMAGES_ROOT);
@@ -1017,6 +1165,15 @@
         row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
     }
 
+    private void includeDocumentsRootDocument(MatrixCursor result) {
+        final RowBuilder row = result.newRow();
+        row.add(Document.COLUMN_DOCUMENT_ID, TYPE_DOCUMENTS_ROOT);
+        row.add(Document.COLUMN_DISPLAY_NAME, getContext().getString(R.string.root_documents));
+        row.add(Document.COLUMN_FLAGS,
+                Document.FLAG_DIR_PREFERS_GRID | Document.FLAG_DIR_PREFERS_LAST_MODIFIED);
+        row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
+    }
+
     private interface ImagesBucketQuery {
         final String[] PROJECTION = new String[] {
                 ImageColumns.BUCKET_ID,
@@ -1137,6 +1294,63 @@
                     | Document.FLAG_SUPPORTS_METADATA);
     }
 
+    private interface DocumentsBucketQuery {
+        final String[] PROJECTION = new String[] {
+                FileColumns.BUCKET_ID,
+                FileColumns.BUCKET_DISPLAY_NAME,
+                FileColumns.DATE_MODIFIED };
+        final String SORT_ORDER = FileColumns.BUCKET_ID + ", " + FileColumns.DATE_MODIFIED
+                + " DESC";
+
+        final int BUCKET_ID = 0;
+        final int BUCKET_DISPLAY_NAME = 1;
+        final int DATE_MODIFIED = 2;
+    }
+
+    private void includeDocumentsBucket(MatrixCursor result, Cursor cursor) {
+        final long id = cursor.getLong(DocumentsBucketQuery.BUCKET_ID);
+        final String docId = getDocIdForIdent(TYPE_DOCUMENTS_BUCKET, id);
+
+        final RowBuilder row = result.newRow();
+        row.add(Document.COLUMN_DOCUMENT_ID, docId);
+        row.add(Document.COLUMN_DISPLAY_NAME,
+                cleanUpMediaBucketName(cursor.getString(DocumentsBucketQuery.BUCKET_DISPLAY_NAME)));
+        row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
+        row.add(Document.COLUMN_LAST_MODIFIED,
+                cursor.getLong(DocumentsBucketQuery.DATE_MODIFIED) * DateUtils.SECOND_IN_MILLIS);
+        row.add(Document.COLUMN_FLAGS,
+                Document.FLAG_DIR_PREFERS_GRID | Document.FLAG_DIR_PREFERS_LAST_MODIFIED);
+    }
+
+    private interface DocumentQuery {
+        final String[] PROJECTION = new String[] {
+                FileColumns._ID,
+                FileColumns.DISPLAY_NAME,
+                FileColumns.MIME_TYPE,
+                FileColumns.SIZE,
+                FileColumns.DATE_MODIFIED };
+
+        final int _ID = 0;
+        final int DISPLAY_NAME = 1;
+        final int MIME_TYPE = 2;
+        final int SIZE = 3;
+        final int DATE_MODIFIED = 4;
+    }
+
+    private void includeDocument(MatrixCursor result, Cursor cursor) {
+        final long id = cursor.getLong(DocumentQuery._ID);
+        final String docId = getDocIdForIdent(TYPE_DOCUMENT, id);
+
+        final RowBuilder row = result.newRow();
+        row.add(Document.COLUMN_DOCUMENT_ID, docId);
+        row.add(Document.COLUMN_DISPLAY_NAME, cursor.getString(DocumentQuery.DISPLAY_NAME));
+        row.add(Document.COLUMN_SIZE, cursor.getLong(DocumentQuery.SIZE));
+        row.add(Document.COLUMN_MIME_TYPE, cursor.getString(DocumentQuery.MIME_TYPE));
+        row.add(Document.COLUMN_LAST_MODIFIED,
+                cursor.getLong(DocumentQuery.DATE_MODIFIED) * DateUtils.SECOND_IN_MILLIS);
+        row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_DELETE);
+    }
+
     private interface ArtistQuery {
         final String[] PROJECTION = new String[] {
                 BaseColumns._ID,
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 5e3fcf9..9e65c70 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -159,6 +159,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.providers.media.DatabaseHelper.OnFilesChangeListener;
 import com.android.providers.media.fuse.ExternalStorageServiceImpl;
 import com.android.providers.media.fuse.FuseDaemon;
 import com.android.providers.media.scan.MediaScanner;
@@ -427,98 +428,100 @@
         }
     };
 
+    private final OnFilesChangeListener mFilesListener = new OnFilesChangeListener() {
+        @Override
+        public void onInsert(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
+                int mediaType, boolean isDownload) {
+            acceptWithExpansion(helper::notifyChange, volumeName, id, mediaType, isDownload);
+
+            // Tell our SAF provider so it knows when views are no longer empty
+            MediaDocumentsProvider.onMediaStoreInsert(getContext(), volumeName, mediaType, id);
+        }
+
+        @Override
+        public void onUpdate(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
+                int oldMediaType, boolean oldIsDownload,
+                int newMediaType, boolean newIsDownload) {
+            final boolean isDownload = oldIsDownload || newIsDownload;
+            acceptWithExpansion(helper::notifyChange, volumeName, id, oldMediaType, isDownload);
+
+            // When media type changes, notify both old and new collections and
+            // invalidate any thumbnails
+            if (newMediaType != oldMediaType) {
+                acceptWithExpansion(helper::notifyChange, volumeName, id, newMediaType, isDownload);
+                invalidateThumbnails(MediaStore.Files.getContentUri(volumeName, id));
+            }
+        }
+
+        @Override
+        public void onDelete(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
+                int mediaType, boolean isDownload) {
+            // Both notify apps and revoke any outstanding permission grants
+            final Context context = getContext();
+            acceptWithExpansion((uri) -> {
+                helper.notifyChange(uri);
+                context.revokeUriPermission(uri, ~0);
+            }, volumeName, id, mediaType, isDownload);
+
+            // Invalidate any thumbnails now that media is gone
+            invalidateThumbnails(MediaStore.Files.getContentUri(volumeName, id));
+
+            // Tell our SAF provider so it can revoke too
+            MediaDocumentsProvider.onMediaStoreDelete(getContext(), volumeName, mediaType, id);
+        }
+    };
+
     /**
-     * Apply {@link Consumer#accept} to the given {@link Uri}.
+     * Apply {@link Consumer#accept} to the given item.
      * <p>
      * Since media items can be exposed through multiple collections or views,
      * this method expands the single item being accepted to also accept all
      * relevant views.
      */
-    public void acceptWithExpansion(Consumer<Uri> consumer, Uri uri) {
-        final int match = matchUri(uri, true);
-        acceptWithExpansionInternal(consumer, uri, match);
+    private void acceptWithExpansion(@NonNull Consumer<Uri> consumer, @NonNull String volumeName,
+            long id, int mediaType, boolean isDownload) {
+        switch (mediaType) {
+            case FileColumns.MEDIA_TYPE_AUDIO:
+                consumer.accept(MediaStore.Audio.Media.getContentUri(volumeName, id));
 
-        try {
-            // When targeting a specific volume, we need to expand to also
-            // notify the top-level view
-            final String volumeName = getVolumeName(uri);
-            switch (volumeName) {
-                case MediaStore.VOLUME_INTERNAL:
-                case MediaStore.VOLUME_EXTERNAL:
-                    // Already a top-level view, no need to expand
-                    break;
-                default:
-                    final List<String> segments = new ArrayList<>(uri.getPathSegments());
-                    segments.set(0, MediaStore.VOLUME_EXTERNAL);
-                    final Uri.Builder builder = uri.buildUpon().path(null);
-                    for (String segment : segments) {
-                        builder.appendPath(segment);
-                    }
-                    acceptWithExpansionInternal(consumer, builder.build(), match);
-                    break;
-            }
-        } catch (IllegalArgumentException ignored) {
-        }
-    }
-
-    private static void acceptWithExpansionInternal(Consumer<Uri> consumer, Uri uri, int match) {
-        // Start by always notifying the base item
-        consumer.accept(uri);
-
-        // Some items can be exposed through multiple collections,
-        // so we need to notify all possible views of those items
-        switch (match) {
-            case AUDIO_MEDIA_ID:
-            case VIDEO_MEDIA_ID:
-            case IMAGES_MEDIA_ID: {
-                final String volumeName = getVolumeName(uri);
-                final long id = ContentUris.parseId(uri);
-                consumer.accept(Files.getContentUri(volumeName, id));
-                consumer.accept(Downloads.getContentUri(volumeName, id));
-                break;
-            }
-            case AUDIO_MEDIA:
-            case VIDEO_MEDIA:
-            case IMAGES_MEDIA: {
-                final String volumeName = getVolumeName(uri);
-                consumer.accept(Files.getContentUri(volumeName));
-                consumer.accept(Downloads.getContentUri(volumeName));
-                break;
-            }
-            case FILES_ID:
-            case DOWNLOADS_ID: {
-                final String volumeName = getVolumeName(uri);
-                final long id = ContentUris.parseId(uri);
-                consumer.accept(Audio.Media.getContentUri(volumeName, id));
-                consumer.accept(Video.Media.getContentUri(volumeName, id));
-                consumer.accept(Images.Media.getContentUri(volumeName, id));
-                break;
-            }
-            case FILES:
-            case DOWNLOADS: {
-                final String volumeName = getVolumeName(uri);
-                consumer.accept(Audio.Media.getContentUri(volumeName));
-                consumer.accept(Video.Media.getContentUri(volumeName));
-                consumer.accept(Images.Media.getContentUri(volumeName));
-                break;
-            }
-        }
-
-        // Any changing audio items mean we probably need to invalidate all
-        // indexed views built from that media
-        switch (match) {
-            case AUDIO_MEDIA:
-            case AUDIO_MEDIA_ID: {
-                final String volumeName = getVolumeName(uri);
+                // Any changing audio items mean we probably need to invalidate all
+                // indexed views built from that media
                 consumer.accept(Audio.Genres.getContentUri(volumeName));
                 consumer.accept(Audio.Playlists.getContentUri(volumeName));
                 consumer.accept(Audio.Artists.getContentUri(volumeName));
                 consumer.accept(Audio.Albums.getContentUri(volumeName));
                 break;
-            }
+
+            case FileColumns.MEDIA_TYPE_VIDEO:
+                consumer.accept(MediaStore.Video.Media.getContentUri(volumeName, id));
+                break;
+
+            case FileColumns.MEDIA_TYPE_IMAGE:
+                consumer.accept(MediaStore.Images.Media.getContentUri(volumeName, id));
+                break;
+        }
+
+        // Also notify through any generic views
+        consumer.accept(MediaStore.Files.getContentUri(volumeName, id));
+        if (isDownload) {
+            consumer.accept(MediaStore.Downloads.getContentUri(volumeName, id));
+        }
+
+        // Rinse and repeat through any synthetic views
+        switch (volumeName) {
+            case MediaStore.VOLUME_INTERNAL:
+            case MediaStore.VOLUME_EXTERNAL:
+                // Already a top-level view, no need to expand
+                break;
+            default:
+                acceptWithExpansion(consumer, MediaStore.VOLUME_EXTERNAL,
+                        id, mediaType, isDownload);
+                break;
         }
     }
 
+
+
     private static final String[] sDefaultFolderNames = {
         Environment.DIRECTORY_MUSIC,
         Environment.DIRECTORY_PODCASTS,
@@ -602,9 +605,11 @@
         }
 
         mInternalDatabase = new DatabaseHelper(context, INTERNAL_DATABASE_NAME,
-                true, false, mLegacyProvider, Column.class, Metrics::logSchemaChange);
+                true, false, mLegacyProvider, Column.class,
+                Metrics::logSchemaChange, mFilesListener);
         mExternalDatabase = new DatabaseHelper(context, EXTERNAL_DATABASE_NAME,
-                false, false, mLegacyProvider, Column.class, Metrics::logSchemaChange);
+                false, false, mLegacyProvider, Column.class,
+                Metrics::logSchemaChange, mFilesListener);
 
         final IntentFilter filter = new IntentFilter();
         filter.setPriority(10);
@@ -1129,62 +1134,6 @@
     }
 
     /**
-     * Process metadata after renaming file/directory. This method does post processing in
-     * background thread so that rename is not blocked on post processing and also any error
-     * occurred while post processing is not reported as rename error.
-     */
-    private void postProcessMetadataForFuseRename(String oldPath, String newPath) {
-        final LocalCallingIdentity token = clearLocalCallingIdentity();
-        try {
-            BackgroundThread.getExecutor().execute(() -> {
-                Uri uri = Files.getContentUriForPath(newPath);
-                final DatabaseHelper helper;
-                try {
-                    helper = getDatabaseForUri(uri);
-                } catch (VolumeNotFoundException e) {
-                    Log.w("Volume not found while trying to process metadata for rename of "
-                            + oldPath + " to " + newPath, e);
-                    return;
-                }
-
-                if (new File(newPath).isDirectory()) {
-                    final String selection = MediaColumns.RELATIVE_PATH + " REGEXP '^" +
-                            extractRelativePathForDirectory(newPath) +
-                            "/?.*' and mime_type not like 'null'";
-                    try (Cursor c = query(uri, new String[] {MediaColumns._ID}, selection, null,
-                            null)) {
-                        while(c.moveToNext()) {
-                            final Uri contentUri = ContentUris.withAppendedId(uri, c.getInt(0));
-                            acceptWithExpansion(helper::notifyChange, contentUri);
-                        }
-                    }
-                } else {
-                    try (Cursor c = queryForSingleItem(uri, new String [] {MediaColumns._ID},
-                            MediaColumns.DATA + " =? ", new String[]{newPath}, null)) {
-                        c.moveToFirst();
-                        uri = ContentUris.withAppendedId(uri, c.getInt(0));
-                    } catch(FileNotFoundException e) {
-                        Log.w("Failed to process metadata after renaming " +  oldPath, e);
-                        return;
-                    }
-                    final String oldMimeType = MimeUtils.resolveMimeType(new File(oldPath));
-                    final String newMimeType = MimeUtils.resolveMimeType(new File(newPath));
-                    if (!oldMimeType.equals(newMimeType)) {
-                        invalidateThumbnails(uri);
-                        int mediaType = MimeUtils.resolveMediaType(newMimeType);
-                        // If we're changing media types, invalidate any cached "empty answers for
-                        // the new collection type.
-                        MediaDocumentsProvider.onMediaStoreInsert(getContext(), getVolumeName(uri),
-                                mediaType, -1);
-                    }
-                    acceptWithExpansion(helper::notifyChange, uri);
-                }
-            });
-        } finally {
-            restoreLocalCallingIdentity(token);
-        }
-    }
-    /**
      * Gets files in the given {@code path} and subdirectories of the given {@code path} for which
      * calling package has write permissions.
      *
@@ -1314,8 +1263,6 @@
         } finally {
             helper.endTransaction();
         }
-        // Process metadata in background thread.
-        postProcessMetadataForFuseRename(oldPath, newPath);
         return 0;
     }
 
@@ -1370,8 +1317,6 @@
         } finally {
             helper.endTransaction();
         }
-        // Process metadata in background thread.
-        postProcessMetadataForFuseRename(oldPath, newPath);
         return 0;
     }
 
@@ -2545,8 +2490,6 @@
                 rowId = insertFile(qb, helper, match, uri, extras, initialValues,
                         FileColumns.MEDIA_TYPE_IMAGE, true);
                 if (rowId > 0) {
-                    MediaDocumentsProvider.onMediaStoreInsert(
-                            getContext(), resolvedVolumeName, FileColumns.MEDIA_TYPE_IMAGE, rowId);
                     newUri = ContentUris.withAppendedId(
                             Images.Media.getContentUri(originalVolumeName), rowId);
                 }
@@ -2603,8 +2546,6 @@
                 rowId = insertFile(qb, helper, match, uri, extras, initialValues,
                         FileColumns.MEDIA_TYPE_AUDIO, true);
                 if (rowId > 0) {
-                    MediaDocumentsProvider.onMediaStoreInsert(
-                            getContext(), resolvedVolumeName, FileColumns.MEDIA_TYPE_AUDIO, rowId);
                     newUri = ContentUris.withAppendedId(
                             Audio.Media.getContentUri(originalVolumeName), rowId);
                 }
@@ -2688,8 +2629,6 @@
                 rowId = insertFile(qb, helper, match, uri, extras, initialValues,
                         FileColumns.MEDIA_TYPE_VIDEO, true);
                 if (rowId > 0) {
-                    MediaDocumentsProvider.onMediaStoreInsert(
-                            getContext(), resolvedVolumeName, FileColumns.MEDIA_TYPE_VIDEO, rowId);
                     newUri = ContentUris.withAppendedId(
                             Video.Media.getContentUri(originalVolumeName), rowId);
                 }
@@ -2713,11 +2652,14 @@
             case FILES: {
                 maybePut(initialValues, FileColumns.OWNER_PACKAGE_NAME, ownerPackageName);
                 final boolean isDownload = maybeMarkAsDownload(initialValues);
+                final boolean isDocumentType = MimeUtils.isDocumentMimeType(
+                        initialValues.getAsString((MediaColumns.MIME_TYPE)));
+                final int mediaType = isDocumentType ? FileColumns.MEDIA_TYPE_DOCUMENT
+                        : FileColumns.MEDIA_TYPE_NONE;
                 rowId = insertFile(qb, helper, match, uri, extras, initialValues,
-                        FileColumns.MEDIA_TYPE_NONE, true);
+                        mediaType, true);
+
                 if (rowId > 0) {
-                    MediaDocumentsProvider.onMediaStoreInsert(
-                            getContext(), resolvedVolumeName, FileColumns.MEDIA_TYPE_NONE, rowId);
                     newUri = Files.getContentUri(originalVolumeName, rowId);
                 }
                 break;
@@ -2730,8 +2672,6 @@
                         FileColumns.MEDIA_TYPE_NONE, false);
                 if (rowId > 0) {
                     final int mediaType = initialValues.getAsInteger(FileColumns.MEDIA_TYPE);
-                    MediaDocumentsProvider.onMediaStoreInsert(
-                            getContext(), resolvedVolumeName, mediaType, rowId);
                     newUri = ContentUris.withAppendedId(
                         MediaStore.Downloads.getContentUri(originalVolumeName), rowId);
                 }
@@ -2749,9 +2689,6 @@
             mMediaScanner.scanFile(new File(path).getParentFile(), REASON_DEMAND);
         }
 
-        if (newUri != null) {
-            acceptWithExpansion(helper::notifyChange, newUri);
-        }
         return newUri;
     }
 
@@ -3520,15 +3457,6 @@
                             // Forget that caller is owner of this item
                             mCallingIdentity.get().setOwned(id, false);
 
-                            // Invalidate thumbnails and revoke all outstanding grants
-                            final Uri deletedUri = Files.getContentUri(volumeName, id);
-                            invalidateThumbnails(deletedUri);
-                            acceptWithExpansion((expandedUri) -> {
-                                getContext().revokeUriPermission(expandedUri,
-                                        Intent.FLAG_GRANT_READ_URI_PERMISSION
-                                                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-                            }, deletedUri);
-
                             deleteIfAllowed(uri, extras, data);
 
                             // Only need to inform DownloadProvider about the downloads deleted on
@@ -3536,17 +3464,8 @@
                             if (isDownload == 1) {
                                 deletedDownloadIds.put(id, mimeType);
                             }
-                            if (mediaType == FileColumns.MEDIA_TYPE_IMAGE) {
-                                MediaDocumentsProvider.onMediaStoreDelete(getContext(),
-                                        volumeName, FileColumns.MEDIA_TYPE_IMAGE, id);
-                            } else if (mediaType == FileColumns.MEDIA_TYPE_VIDEO) {
-                                MediaDocumentsProvider.onMediaStoreDelete(getContext(),
-                                        volumeName, FileColumns.MEDIA_TYPE_VIDEO, id);
-                            } else if (mediaType == FileColumns.MEDIA_TYPE_AUDIO) {
+                            if (mediaType == FileColumns.MEDIA_TYPE_AUDIO) {
                                 if (!helper.mInternal) {
-                                    MediaDocumentsProvider.onMediaStoreDelete(getContext(),
-                                            volumeName, FileColumns.MEDIA_TYPE_AUDIO, id);
-
                                     idvalue[0] = String.valueOf(id);
                                     // for each playlist that the item appears in, move
                                     // all the items behind it forward by one
@@ -3570,9 +3489,6 @@
                                         FileUtils.closeQuietly(cc);
                                     }
                                 }
-                            } else if (isDownload == 1) {
-                                MediaDocumentsProvider.onMediaStoreDelete(getContext(),
-                                        volumeName, mediaType, id);
                             } else if (mediaType == FileColumns.MEDIA_TYPE_PLAYLIST) {
                                 // TODO, maybe: remove the audio_playlists_cleanup trigger and
                                 // implement functionality here (clean up the playlist map)
@@ -3638,9 +3554,6 @@
             }
         }
 
-        if (count > 0) {
-            acceptWithExpansion(helper::notifyChange, uri);
-        }
         return count;
     }
 
@@ -3655,19 +3568,15 @@
         synchronized (mDirectoryCache) {
             mDirectoryCache.clear();
 
-            helper.beginTransaction();
-            try {
+            return (int) helper.runWithTransaction(() -> {
                 int n = 0;
                 int total = 0;
                 do {
                     n = qb.delete(helper, userWhere, userWhereArgs);
                     total += n;
                 } while (n > 0);
-                helper.setTransactionSuccessful();
                 return total;
-            } finally {
-                helper.endTransaction();
-            }
+            });
         }
     }
 
@@ -4001,12 +3910,23 @@
 
         public File ensureThumbnail(Uri uri, CancellationSignal signal) throws IOException {
             final File thumbFile = getThumbnailFile(uri);
-            thumbFile.getParentFile().mkdirs();
+            final File thumbDir = thumbFile.getParentFile();
+            thumbDir.mkdirs();
             if (!thumbFile.exists()) {
+                // When multiple threads race for the same thumbnail, the second
+                // thread could return a file with a thumbnail still in
+                // progress. We could add heavy per-ID locking to mitigate this
+                // rare race condition, but it's simpler to have both threads
+                // generate the same thumbnail using temporary files and rename
+                // them into place once finished.
+                final File thumbTempFile = File.createTempFile("thumb", null, thumbDir);
                 final Bitmap thumbnail = getThumbnailBitmap(uri, signal);
-                try (OutputStream out = new FileOutputStream(thumbFile)) {
+                try (OutputStream out = new FileOutputStream(thumbTempFile)) {
                     thumbnail.compress(Bitmap.CompressFormat.JPEG, 90, out);
                 }
+                if (!thumbTempFile.renameTo(thumbFile)) {
+                    thumbTempFile.delete();
+                }
             }
             return thumbFile;
         }
@@ -4335,11 +4255,6 @@
         if (initialValues.containsKey(FileColumns.MEDIA_TYPE)) {
             final int newMediaType = initialValues.getAsInteger(FileColumns.MEDIA_TYPE);
 
-            // If we're changing media types, invalidate any cached "empty"
-            // answers for the new collection type.
-            MediaDocumentsProvider.onMediaStoreInsert(
-                    getContext(), volumeName, newMediaType, -1);
-
             // If we're changing media types, invalidate any thumbnails
             triggerInvalidate = true;
         }
@@ -4453,9 +4368,6 @@
             }
         }
 
-        if (count > 0) {
-            acceptWithExpansion(helper::notifyChange, uri);
-        }
         return count;
     }
 
@@ -5936,6 +5848,10 @@
         return false;
     }
 
+    private @NonNull Uri getBaseContentUri(@NonNull String volumeName) {
+        return MediaStore.AUTHORITY_URI.buildUpon().appendPath(volumeName).build();
+    }
+
     private void attachVolume(Uri uri) {
         attachVolume(MediaStore.getVolumeName(uri));
     }
@@ -5963,13 +5879,18 @@
             mAttachedVolumeNames.add(volume);
         }
 
-        final Uri uri = MediaStore.AUTHORITY_URI.buildUpon().appendPath(volume).build();
-        final DatabaseHelper helper = MediaStore.VOLUME_INTERNAL.equals(volume)
-                ? mInternalDatabase : mExternalDatabase;
-        acceptWithExpansion(helper::notifyChange, uri);
+        final ContentResolver resolver = getContext().getContentResolver();
+        final Uri uri = getBaseContentUri(volume);
+        resolver.notifyChange(getBaseContentUri(volume), null);
+
         if (LOGV) Log.v(TAG, "Attached volume: " + volume);
         if (!MediaStore.VOLUME_INTERNAL.equals(volume)) {
+            // Also notify on synthetic view of all devices
+            resolver.notifyChange(getBaseContentUri(MediaStore.VOLUME_EXTERNAL), null);
+
             BackgroundThread.getExecutor().execute(() -> {
+                final DatabaseHelper helper = MediaStore.VOLUME_INTERNAL.equals(volume)
+                        ? mInternalDatabase : mExternalDatabase;
                 ensureDefaultFolders(volume, helper);
             });
         }
@@ -6001,10 +5922,15 @@
             mAttachedVolumeNames.remove(volume);
         }
 
-        final Uri uri = MediaStore.AUTHORITY_URI.buildUpon().appendPath(volume).build();
-        final DatabaseHelper helper = MediaStore.VOLUME_INTERNAL.equals(volume)
-                ? mInternalDatabase : mExternalDatabase;
-        acceptWithExpansion(helper::notifyChange, uri);
+        final ContentResolver resolver = getContext().getContentResolver();
+        final Uri uri = getBaseContentUri(volume);
+        resolver.notifyChange(getBaseContentUri(volume), null);
+
+        if (!MediaStore.VOLUME_INTERNAL.equals(volume)) {
+            // Also notify on synthetic view of all devices
+            resolver.notifyChange(getBaseContentUri(MediaStore.VOLUME_EXTERNAL), null);
+        }
+
         if (LOGV) Log.v(TAG, "Detached volume: " + volume);
     }
 
diff --git a/src/com/android/providers/media/MediaUpgradeReceiver.java b/src/com/android/providers/media/MediaUpgradeReceiver.java
index cc39935..ee3cb68 100644
--- a/src/com/android/providers/media/MediaUpgradeReceiver.java
+++ b/src/com/android/providers/media/MediaUpgradeReceiver.java
@@ -69,7 +69,7 @@
                     try {
                         DatabaseHelper helper = new DatabaseHelper(
                                 context, file, MediaProvider.isInternalMediaDatabaseName(file),
-                                false, false, Column.class, Metrics::logSchemaChange);
+                                false, false, Column.class, Metrics::logSchemaChange, null);
                         db = helper.getWritableDatabase();
                     } catch (Throwable t) {
                         Log.wtf(TAG, "Error during upgrade of media db " + file, t);
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index aef7376..18efb48 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -144,6 +144,7 @@
     // TODO: deprecate playlist editing
     // TODO: deprecate PARENT column, since callers can't see directories
 
+    @GuardedBy("sDateFormat")
     private static final SimpleDateFormat sDateFormat;
 
     static {
@@ -691,6 +692,8 @@
                 return scanItemPlaylist(existingId, file, attrs, mimeType, volumeName);
             case FileColumns.MEDIA_TYPE_SUBTITLE:
                 return scanItemSubtitle(existingId, file, attrs, mimeType, volumeName);
+            case FileColumns.MEDIA_TYPE_DOCUMENT:
+                return scanItemDocument(existingId, file, attrs, mimeType, volumeName);
             default:
                 return scanItemFile(existingId, file, attrs, mimeType, volumeName);
         }
@@ -902,6 +905,15 @@
         return op;
     }
 
+    private static @NonNull ContentProviderOperation.Builder scanItemDocument(long existingId,
+            File file, BasicFileAttributes attrs, String mimeType, String volumeName) {
+        final ContentProviderOperation.Builder op = newUpsert(
+                MediaStore.Files.getContentUri(volumeName), existingId);
+        withGenericValues(op, file, attrs, mimeType);
+
+        return op;
+    }
+
     private static @NonNull ContentProviderOperation.Builder scanItemVideo(long existingId,
             File file, BasicFileAttributes attrs, String mimeType, String volumeName) {
         final ContentProviderOperation.Builder op = newUpsert(
@@ -1146,8 +1158,10 @@
     private static @NonNull Optional<Long> parseOptionalDate(@Nullable String date) {
         if (TextUtils.isEmpty(date)) return Optional.empty();
         try {
-            final long value = sDateFormat.parse(date).getTime();
-            return (value > 0) ? Optional.of(value) : Optional.empty();
+            synchronized (sDateFormat) {
+                final long value = sDateFormat.parse(date).getTime();
+                return (value > 0) ? Optional.of(value) : Optional.empty();
+            }
         } catch (ParseException e) {
             return Optional.empty();
         }
diff --git a/src/com/android/providers/media/util/MimeUtils.java b/src/com/android/providers/media/util/MimeUtils.java
index 168756f..6dd029a 100644
--- a/src/com/android/providers/media/util/MimeUtils.java
+++ b/src/com/android/providers/media/util/MimeUtils.java
@@ -59,6 +59,8 @@
             return FileColumns.MEDIA_TYPE_VIDEO;
         } else if (isImageMimeType(mimeType)) {
             return FileColumns.MEDIA_TYPE_IMAGE;
+        } else if (isDocumentMimeType(mimeType)) {
+            return FileColumns.MEDIA_TYPE_DOCUMENT;
         } else {
             return FileColumns.MEDIA_TYPE_NONE;
         }
@@ -138,4 +140,74 @@
                 return false;
         }
     }
+
+    public static boolean isDocumentMimeType(@Nullable String mimeType) {
+        if (mimeType == null) return false;
+
+        if (mimeType.startsWith("text/")) {
+            return true;
+        }
+
+        switch (mimeType) {
+            case "application/epub+zip":
+            case "application/msword":
+            case "application/pdf":
+            case "application/rtf":
+            case "application/vnd.ms-excel":
+            case "application/vnd.ms-excel.addin.macroEnabled.12":
+            case "application/vnd.ms-excel.sheet.binary.macroEnabled.12":
+            case "application/vnd.ms-excel.sheet.macroEnabled.12":
+            case "application/vnd.ms-excel.template.macroEnabled.12":
+            case "application/vnd.ms-powerpoint":
+            case "application/vnd.ms-powerpoint.addin.macroEnabled.12":
+            case "application/vnd.ms-powerpoint.presentation.macroEnabled.12":
+            case "application/vnd.ms-powerpoint.slideshow.macroEnabled.12":
+            case "application/vnd.ms-powerpoint.template.macroEnabled.12":
+            case "application/vnd.ms-word.document.macroEnabled.12":
+            case "application/vnd.ms-word.template.macroEnabled.12":
+            case "application/vnd.oasis.opendocument.chart":
+            case "application/vnd.oasis.opendocument.database":
+            case "application/vnd.oasis.opendocument.formula":
+            case "application/vnd.oasis.opendocument.graphics":
+            case "application/vnd.oasis.opendocument.graphics-template":
+            case "application/vnd.oasis.opendocument.presentation":
+            case "application/vnd.oasis.opendocument.presentation-template":
+            case "application/vnd.oasis.opendocument.spreadsheet":
+            case "application/vnd.oasis.opendocument.spreadsheet-template":
+            case "application/vnd.oasis.opendocument.text":
+            case "application/vnd.oasis.opendocument.text-master":
+            case "application/vnd.oasis.opendocument.text-template":
+            case "application/vnd.oasis.opendocument.text-web":
+            case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
+            case "application/vnd.openxmlformats-officedocument.presentationml.slideshow":
+            case "application/vnd.openxmlformats-officedocument.presentationml.template":
+            case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
+            case "application/vnd.openxmlformats-officedocument.spreadsheetml.template":
+            case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
+            case "application/vnd.openxmlformats-officedocument.wordprocessingml.template":
+            case "application/vnd.stardivision.calc":
+            case "application/vnd.stardivision.chart":
+            case "application/vnd.stardivision.draw":
+            case "application/vnd.stardivision.impress":
+            case "application/vnd.stardivision.impress-packed":
+            case "application/vnd.stardivision.mail":
+            case "application/vnd.stardivision.math":
+            case "application/vnd.stardivision.writer":
+            case "application/vnd.stardivision.writer-global":
+            case "application/vnd.sun.xml.calc":
+            case "application/vnd.sun.xml.calc.template":
+            case "application/vnd.sun.xml.draw":
+            case "application/vnd.sun.xml.draw.template":
+            case "application/vnd.sun.xml.impress":
+            case "application/vnd.sun.xml.impress.template":
+            case "application/vnd.sun.xml.math":
+            case "application/vnd.sun.xml.writer":
+            case "application/vnd.sun.xml.writer.global":
+            case "application/vnd.sun.xml.writer.template":
+            case "application/x-mspublisher":
+                return true;
+            default:
+                return false;
+        }
+    }
 }
diff --git a/tests/src/com/android/providers/media/DatabaseHelperTest.java b/tests/src/com/android/providers/media/DatabaseHelperTest.java
index a3936e5..5923573 100644
--- a/tests/src/com/android/providers/media/DatabaseHelperTest.java
+++ b/tests/src/com/android/providers/media/DatabaseHelperTest.java
@@ -425,7 +425,8 @@
 
     private static class DatabaseHelperO extends DatabaseHelper {
         public DatabaseHelperO(Context context, String name) {
-            super(context, name, DatabaseHelper.VERSION_O, false, false, true, Column.class, null);
+            super(context, name, DatabaseHelper.VERSION_O,
+                    false, false, true, Column.class, null, null);
         }
 
         @Override
@@ -436,7 +437,8 @@
 
     private static class DatabaseHelperP extends DatabaseHelper {
         public DatabaseHelperP(Context context, String name) {
-            super(context, name, DatabaseHelper.VERSION_P, false, false, true, Column.class, null);
+            super(context, name, DatabaseHelper.VERSION_P,
+                    false, false, true, Column.class, null, null);
         }
 
         @Override
@@ -447,7 +449,8 @@
 
     private static class DatabaseHelperQ extends DatabaseHelper {
         public DatabaseHelperQ(Context context, String name) {
-            super(context, name, DatabaseHelper.VERSION_Q, false, false, true, Column.class, null);
+            super(context, name, DatabaseHelper.VERSION_Q,
+                    false, false, true, Column.class, null, null);
         }
 
         @Override
@@ -458,7 +461,8 @@
 
     private static class DatabaseHelperR extends DatabaseHelper {
         public DatabaseHelperR(Context context, String name) {
-            super(context, name, DatabaseHelper.VERSION_R, false, false, true, Column.class, null);
+            super(context, name, DatabaseHelper.VERSION_R,
+                    false, false, true, Column.class, null, null);
         }
     }
 
diff --git a/tests/src/com/android/providers/media/MediaDocumentsProviderTest.java b/tests/src/com/android/providers/media/MediaDocumentsProviderTest.java
index dc1767f..84cc1ef 100644
--- a/tests/src/com/android/providers/media/MediaDocumentsProviderTest.java
+++ b/tests/src/com/android/providers/media/MediaDocumentsProviderTest.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.media;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
 import android.content.ContentResolver;
@@ -23,12 +24,15 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.providers.media.scan.MediaScannerTest.IsolatedContext;
 
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -48,6 +52,7 @@
                 MediaDocumentsProvider.TYPE_AUDIO_ROOT,
                 MediaDocumentsProvider.TYPE_VIDEOS_ROOT,
                 MediaDocumentsProvider.TYPE_IMAGES_ROOT,
+                MediaDocumentsProvider.TYPE_DOCUMENTS_ROOT,
         }) {
             assertProbe(resolver, "root", root, "search");
 
@@ -58,6 +63,7 @@
         for (String recent : new String[] {
                 MediaDocumentsProvider.TYPE_VIDEOS_ROOT,
                 MediaDocumentsProvider.TYPE_IMAGES_ROOT,
+                MediaDocumentsProvider.TYPE_DOCUMENTS_ROOT,
         }) {
             assertProbe(resolver, "root", recent, "recent");
         }
@@ -65,6 +71,7 @@
         for (String dir : new String[] {
                 MediaDocumentsProvider.TYPE_VIDEOS_BUCKET,
                 MediaDocumentsProvider.TYPE_IMAGES_BUCKET,
+                MediaDocumentsProvider.TYPE_DOCUMENTS_BUCKET,
         }) {
             assertProbe(resolver, "document", dir, "children");
         }
@@ -74,15 +81,59 @@
                 MediaDocumentsProvider.TYPE_ALBUM,
                 MediaDocumentsProvider.TYPE_VIDEOS_BUCKET,
                 MediaDocumentsProvider.TYPE_IMAGES_BUCKET,
+                MediaDocumentsProvider.TYPE_DOCUMENTS_BUCKET,
 
                 MediaDocumentsProvider.TYPE_AUDIO,
                 MediaDocumentsProvider.TYPE_VIDEO,
                 MediaDocumentsProvider.TYPE_IMAGE,
+                MediaDocumentsProvider.TYPE_DOCUMENT,
         }) {
                 assertProbe(resolver, "document", item);
         }
     }
 
+    @Test
+    public void testBuildSearchSelection() {
+        final String displayName = "foo";
+        final String[] mimeTypes = new String[] {"text/csv", "video/*", "image/png", "audio/*"};
+        final long lastModifiedAfter = 1000 * 1000;
+        final long fileSizeOver = 1000 * 1000;
+        final String columnDisplayName = "display";
+        final String columnMimeType = "mimeType";
+        final String columnLastModified = "lastModified";
+        final String columnFileSize = "fileSize";
+        final String resultSelection =
+                "display LIKE ? AND lastModified > 1000 AND fileSize > 1000000 AND (mimeType LIKE"
+                        + " ? OR mimeType LIKE ? OR mimeType IN (?,?))";
+
+        final Pair<String, String[]> selectionPair = MediaDocumentsProvider.buildSearchSelection(
+                displayName, mimeTypes, lastModifiedAfter, fileSizeOver, columnDisplayName,
+                columnMimeType, columnLastModified, columnFileSize);
+
+        assertEquals(resultSelection, selectionPair.first);
+        assertEquals(5, selectionPair.second.length);
+        assertEquals("%" + displayName + "%", selectionPair.second[0]);
+        assertMimeType(mimeTypes[1], selectionPair.second[1]);
+        assertMimeType(mimeTypes[3], selectionPair.second[2]);
+        assertMimeType(mimeTypes[0], selectionPair.second[3]);
+        assertMimeType(mimeTypes[2], selectionPair.second[4]);
+    }
+
+    @Test
+    public void testAddDocumentSelection() {
+        final String selection = "";
+        final String[] selectionArgs = new String[]{};
+        final String resultSelection = "media_type=?";
+
+        final Pair<String, String[]> selectionPair = MediaDocumentsProvider.addDocumentSelection(
+                selection, selectionArgs);
+
+        assertEquals(resultSelection, selectionPair.first);
+        assertEquals(1, selectionPair.second.length);
+        assertEquals(MediaStore.Files.FileColumns.MEDIA_TYPE_DOCUMENT,
+                Integer.parseInt(selectionPair.second[0]));
+    }
+
     private static void assertProbe(ContentResolver resolver, String... paths) {
         final Uri.Builder probe = Uri.parse("content://" + MediaDocumentsProvider.AUTHORITY)
                 .buildUpon();
@@ -93,4 +144,12 @@
             assertNotNull(Arrays.toString(paths), c);
         }
     }
+
+    private static void assertMimeType(String expected, String actual) {
+        if (expected.endsWith("/*")) {
+            assertEquals(expected.substring(0, expected.length() - 1) + "%", actual);
+        } else {
+            assertEquals(expected, actual);
+        }
+    }
 }
diff --git a/tests/src/com/android/providers/media/util/MimeUtilsTest.java b/tests/src/com/android/providers/media/util/MimeUtilsTest.java
index 7a13a38..e014582 100644
--- a/tests/src/com/android/providers/media/util/MimeUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/MimeUtilsTest.java
@@ -56,6 +56,16 @@
                 MimeUtils.resolveMediaType("video/mpeg"));
         assertEquals(FileColumns.MEDIA_TYPE_IMAGE,
                 MimeUtils.resolveMediaType("image/jpeg"));
+        assertEquals(FileColumns.MEDIA_TYPE_DOCUMENT,
+                MimeUtils.resolveMediaType("text/plain"));
+        assertEquals(FileColumns.MEDIA_TYPE_DOCUMENT,
+                MimeUtils.resolveMediaType("application/pdf"));
+        assertEquals(FileColumns.MEDIA_TYPE_DOCUMENT,
+                MimeUtils.resolveMediaType("application/msword"));
+        assertEquals(FileColumns.MEDIA_TYPE_DOCUMENT,
+                MimeUtils.resolveMediaType("application/vnd.ms-excel"));
+        assertEquals(FileColumns.MEDIA_TYPE_DOCUMENT,
+                MimeUtils.resolveMediaType("application/vnd.ms-powerpoint"));
         assertEquals(FileColumns.MEDIA_TYPE_NONE,
                 MimeUtils.resolveMediaType("application/x-does-not-exist"));