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"));