Merge "Don't overwrite is_download column" into rvc-dev
diff --git a/apex/framework/Android.bp b/apex/framework/Android.bp
index 7cb77f9..bf35418 100644
--- a/apex/framework/Android.bp
+++ b/apex/framework/Android.bp
@@ -51,10 +51,7 @@
stubs_defaults {
name: "framework-mediaprovider-stubs-srcs-defaults",
- srcs: [
- ":framework-mediaprovider-sources",
- ],
- sdk_version: "system_current",
+ srcs: [":framework-mediaprovider-sources"],
}
droidstubs {
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index 27e61ec..16a0892 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -896,11 +896,8 @@
final ContentValues values = new ContentValues();
if (value) {
values.put(MediaColumns.IS_TRASHED, 1);
- values.put(MediaColumns.DATE_EXPIRES,
- (System.currentTimeMillis() + DateUtils.WEEK_IN_MILLIS) / 1000);
} else {
values.put(MediaColumns.IS_TRASHED, 0);
- values.putNull(MediaColumns.DATE_EXPIRES);
}
return createRequest(resolver, CREATE_TRASH_REQUEST_CALL, uris, values);
}
@@ -1103,9 +1100,17 @@
* The time the media item should be considered expired. Typically only
* meaningful in the context of {@link #IS_PENDING} or
* {@link #IS_TRASHED}.
+ * <p>
+ * The value stored in this column is automatically calculated when
+ * {@link #IS_PENDING} or {@link #IS_TRASHED} is changed. The default
+ * pending expiration is typically 7 days, and the default trashed
+ * expiration is typically 30 days.
+ * <p>
+ * Expired media items are automatically deleted once their expiration
+ * time has passed, typically during during the next device idle period.
*/
@CurrentTimeSecondsLong
- @Column(Cursor.FIELD_TYPE_INTEGER)
+ @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
public static final String DATE_EXPIRES = "date_expires";
/**
@@ -2058,7 +2063,6 @@
values.put(MediaColumns.MIME_TYPE, "image/jpeg");
values.put(MediaColumns.DATE_ADDED, now / 1000);
values.put(MediaColumns.DATE_MODIFIED, now / 1000);
- values.put(MediaColumns.DATE_EXPIRES, (now + DateUtils.DAY_IN_MILLIS) / 1000);
values.put(MediaColumns.IS_PENDING, 1);
final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
@@ -2070,7 +2074,6 @@
// Everything went well above, publish it!
values.clear();
values.put(MediaColumns.IS_PENDING, 0);
- values.putNull(MediaColumns.DATE_EXPIRES);
cr.update(uri, values, null, null);
return uri.toString();
} catch (Exception e) {
diff --git a/jni/Android.bp b/jni/Android.bp
index 0ff937b..265f7fb 100644
--- a/jni/Android.bp
+++ b/jni/Android.bp
@@ -65,7 +65,14 @@
cc_test {
name: "fuse_node_test",
- test_suites: ["device-tests"],
+ test_suites: ["device-tests", "mts"],
+
+ compile_multilib: "both",
+ multilib: {
+ lib32: { suffix: "32", },
+ lib64: { suffix: "64", },
+ },
+
srcs: [
"node_test.cpp",
"node.cpp",
@@ -91,7 +98,14 @@
cc_test {
name: "RedactionInfoTest",
- test_suites: ["device-tests"],
+ test_suites: ["device-tests", "mts"],
+
+ compile_multilib: "both",
+ multilib: {
+ lib32: { suffix: "32", },
+ lib64: { suffix: "64", },
+ },
+
srcs: [
"RedactionInfoTest.cpp",
"RedactionInfo.cpp",
@@ -111,4 +125,4 @@
sdk_version: "current",
stl: "c++_static",
-}
\ No newline at end of file
+}
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index b9c3214..f1bc4e4 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -99,14 +99,6 @@
constexpr size_t MAX_READ_SIZE = 128 * 1024;
// Stolen from: UserHandle#getUserId
constexpr int PER_USER_RANGE = 100000;
-// Cache inode attributes forever to improve performance
-// Whenver attributes could have changed on the lower filesystem outside the FUSE driver, we call
-// fuse_invalidate_entry_cache
-constexpr double DEFAULT_ATTR_TIMEOUT_SECONDS = std::numeric_limits<double>::max();
-// Cache dentries forever to improve performance
-// Whenver attributes could have changed on the lower filesystem outside the FUSE driver, we call
-// fuse_invalidate_entry_cache
-constexpr double DEFAULT_ENTRY_TIMEOUT_SECONDS = std::numeric_limits<double>::max();
/*
* In order to avoid double caching with fuse, call fadvise on the file handles
@@ -354,30 +346,6 @@
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 std::numeric_limits<double>::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 std::numeric_limits<double>::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);
@@ -402,8 +370,8 @@
// 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);
+ e->entry_timeout = std::numeric_limits<double>::max();
+ e->attr_timeout = std::numeric_limits<double>::max();
return node;
}
@@ -525,7 +493,7 @@
if (lstat(path.c_str(), &s) < 0) {
fuse_reply_err(req, errno);
} else {
- fuse_reply_attr(req, &s, get_attr_timeout(path, ctx->uid, fuse, nullptr));
+ fuse_reply_attr(req, &s, std::numeric_limits<double>::max());
}
}
@@ -591,7 +559,7 @@
}
lstat(path.c_str(), attr);
- fuse_reply_attr(req, attr, get_attr_timeout(path, ctx->uid, fuse, nullptr));
+ fuse_reply_attr(req, attr, std::numeric_limits<double>::max());
}
static void pf_canonical_path(fuse_req_t req, fuse_ino_t ino)
@@ -1498,22 +1466,16 @@
void FuseDaemon::InvalidateFuseDentryCache(const std::string& path) {
LOG(VERBOSE) << "Invalidating FUSE dentry cache";
if (active.load(std::memory_order_acquire)) {
- string name;
- fuse_ino_t parent;
+ std::lock_guard<std::recursive_mutex> guard(fuse->lock);
- {
- std::lock_guard<std::recursive_mutex> guard(fuse->lock);
- const node* node = node::LookupAbsolutePath(fuse->root, path);
- if (node) {
- name = node->GetName();
- parent = fuse->ToInode(node->GetParent());
+ const node* node = node::LookupAbsolutePath(fuse->root, path);
+ if (node) {
+ string name = node->GetName();
+ fuse_ino_t parent = fuse->ToInode(node->GetParent());
+ if (fuse_lowlevel_notify_inval_entry(fuse->se, parent, name.c_str(), name.size())) {
+ LOG(WARNING) << "Failed to invalidate dentry for path";
}
}
-
- if (!name.empty() &&
- fuse_lowlevel_notify_inval_entry(fuse->se, parent, name.c_str(), name.size())) {
- LOG(WARNING) << "Failed to invalidate dentry for path";
- }
} else {
LOG(WARNING) << "FUSE daemon is inactive. Cannot invalidate dentry";
}
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 181ea3e..7d9b6d0 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -19,7 +19,7 @@
<string name="uid_label" msgid="8421971615411294156">"Medier"</string>
<string name="storage_description" msgid="4081716890357580107">"Lokalt lager"</string>
<string name="app_label" msgid="9035307001052716210">"Medielagring"</string>
- <string name="artist_label" msgid="8105600993099120273">"Musiker"</string>
+ <string name="artist_label" msgid="8105600993099120273">"Kunstner"</string>
<string name="unknown" msgid="2059049215682829375">"Ukendt"</string>
<string name="root_images" msgid="5861633549189045666">"Billeder"</string>
<string name="root_videos" msgid="8792703517064649453">"Videoer"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 00c1c13..b9352eb 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -42,67 +42,67 @@
<string name="allow" msgid="8885707816848569619">"Permitir"</string>
<string name="deny" msgid="6040983710442068936">"Recusar"</string>
<plurals name="permission_write_audio" formatted="false" msgid="8914759422381305478">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> modifique <xliff:g id="COUNT">^2</xliff:g> ficheiros de áudio?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> modifique este ficheiro de áudio?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> modifique <xliff:g id="COUNT">^2</xliff:g> ficheiros de áudio?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> modifique este ficheiro de áudio?</item>
</plurals>
<plurals name="permission_write_video" formatted="false" msgid="1098082003326873084">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> modifique <xliff:g id="COUNT">^2</xliff:g> vídeos?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> modifique este vídeo?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> modifique <xliff:g id="COUNT">^2</xliff:g> vídeos?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> modifique este vídeo?</item>
</plurals>
<plurals name="permission_write_image" formatted="false" msgid="748745548893845892">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> modifique <xliff:g id="COUNT">^2</xliff:g> fotos?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> modifique esta foto?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> modifique <xliff:g id="COUNT">^2</xliff:g> fotos?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> modifique esta foto?</item>
</plurals>
<plurals name="permission_write_generic" formatted="false" msgid="3270172714743671779">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> modifique <xliff:g id="COUNT">^2</xliff:g> itens?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> modifique este item?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> modifique <xliff:g id="COUNT">^2</xliff:g> itens?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> modifique este item?</item>
</plurals>
<plurals name="permission_trash_audio" formatted="false" msgid="8907813869381755423">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> mova <xliff:g id="COUNT">^2</xliff:g> ficheiros de áudio para o lixo?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> mova este ficheiro de áudio para o lixo?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> mova <xliff:g id="COUNT">^2</xliff:g> ficheiros de áudio para o lixo?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> mova este ficheiro de áudio para o lixo?</item>
</plurals>
<plurals name="permission_trash_video" formatted="false" msgid="4672871911555787438">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> mova <xliff:g id="COUNT">^2</xliff:g> vídeos para o lixo?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> mova este vídeo para o lixo?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> mova <xliff:g id="COUNT">^2</xliff:g> vídeos para o lixo?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> mova este vídeo para o lixo?</item>
</plurals>
<plurals name="permission_trash_image" formatted="false" msgid="6400475304599873227">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> mova <xliff:g id="COUNT">^2</xliff:g> fotos para o lixo?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> mova esta foto para o lixo?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> mova <xliff:g id="COUNT">^2</xliff:g> fotos para o lixo?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> mova esta foto para o lixo?</item>
</plurals>
<plurals name="permission_trash_generic" formatted="false" msgid="3814167365075039711">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> mova <xliff:g id="COUNT">^2</xliff:g> itens para o lixo?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> mova este item para o lixo?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> mova <xliff:g id="COUNT">^2</xliff:g> itens para o lixo?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> mova este item para o lixo?</item>
</plurals>
<plurals name="permission_untrash_audio" formatted="false" msgid="7795265980168966321">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> retire <xliff:g id="COUNT">^2</xliff:g> ficheiros de áudio do lixo?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> retire este ficheiro de áudio do lixo?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> retire <xliff:g id="COUNT">^2</xliff:g> ficheiros de áudio do lixo?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> retire este ficheiro de áudio do lixo?</item>
</plurals>
<plurals name="permission_untrash_video" formatted="false" msgid="332894888445508879">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> retire <xliff:g id="COUNT">^2</xliff:g> vídeos do lixo?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> retire este vídeo do lixo?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> retire <xliff:g id="COUNT">^2</xliff:g> vídeos do lixo?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> retire este vídeo do lixo?</item>
</plurals>
<plurals name="permission_untrash_image" formatted="false" msgid="7024071378733595056">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> retire <xliff:g id="COUNT">^2</xliff:g> fotos do lixo?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> retire esta foto do lixo?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> retire <xliff:g id="COUNT">^2</xliff:g> fotos do lixo?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> retire esta foto do lixo?</item>
</plurals>
<plurals name="permission_untrash_generic" formatted="false" msgid="6872817093731198374">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> retire <xliff:g id="COUNT">^2</xliff:g> itens do lixo?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> retire este item do lixo?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> retire <xliff:g id="COUNT">^2</xliff:g> itens do lixo?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> retire este item do lixo?</item>
</plurals>
<plurals name="permission_delete_audio" formatted="false" msgid="6848547621165184719">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> elimine <xliff:g id="COUNT">^2</xliff:g> ficheiros de áudio?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> elimine este ficheiro de áudio?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> elimine <xliff:g id="COUNT">^2</xliff:g> ficheiros de áudio?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> elimine este ficheiro de áudio?</item>
</plurals>
<plurals name="permission_delete_video" formatted="false" msgid="1251942606336748563">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> elimine <xliff:g id="COUNT">^2</xliff:g> vídeos?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> elimine este vídeo?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> elimine <xliff:g id="COUNT">^2</xliff:g> vídeos?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> elimine este vídeo?</item>
</plurals>
<plurals name="permission_delete_image" formatted="false" msgid="2303409455224710111">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> elimine <xliff:g id="COUNT">^2</xliff:g> fotos?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> elimine esta foto?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> elimine <xliff:g id="COUNT">^2</xliff:g> fotos?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> elimine esta foto?</item>
</plurals>
<plurals name="permission_delete_generic" formatted="false" msgid="1412218850351841181">
- <item quantity="other">Pretende permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> elimine <xliff:g id="COUNT">^2</xliff:g> itens?</item>
- <item quantity="one">Pretende permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> elimine este item?</item>
+ <item quantity="other">Permitir que a app <xliff:g id="APP_NAME_1">^1</xliff:g> elimine <xliff:g id="COUNT">^2</xliff:g> itens?</item>
+ <item quantity="one">Permitir que a app <xliff:g id="APP_NAME_0">^1</xliff:g> elimine este item?</item>
</plurals>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 51a5d6c..77cc361 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -22,7 +22,7 @@
<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_videos" msgid="8792703517064649453">"Videor"</string>
<string name="root_audio" msgid="3505830755201326018">"Ljud"</string>
<string name="root_documents" msgid="3829103301363849237">"Dokument"</string>
<string name="permission_required" msgid="1460820436132943754">"Behörighet krävs för att ändra eller radera objektet."</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 9fa73bf..5958e2c 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -58,36 +58,36 @@
<item quantity="one">இந்த ஃபைலில் மாற்றங்களைச் செய்ய <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
</plurals>
<plurals name="permission_trash_audio" formatted="false" msgid="8907813869381755423">
- <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>
+ <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" formatted="false" msgid="4672871911555787438">
- <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>
+ <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" formatted="false" msgid="6400475304599873227">
- <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>
+ <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" formatted="false" msgid="3814167365075039711">
- <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>
+ <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_audio" formatted="false" msgid="7795265980168966321">
- <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>
+ <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="332894888445508879">
- <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>
+ <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="7024071378733595056">
- <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>
+ <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="6872817093731198374">
- <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>
+ <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_audio" formatted="false" msgid="6848547621165184719">
<item quantity="other"><xliff:g id="COUNT">^2</xliff:g> ஆடியோ ஃபைல்களை நீக்க <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index 55ec897..afa3d63 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -135,7 +135,7 @@
public void onUpdate(@NonNull DatabaseHelper helper, @NonNull String volumeName,
long oldId, int oldMediaType, boolean oldIsDownload,
long newId, int newMediaType, boolean newIsDownload,
- String ownerPackage, String oldPath);
+ String oldOwnerPackage, String newOwnerPackage, String oldPath);
public void onDelete(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
int mediaType, boolean isDownload, String ownerPackage, String path);
}
@@ -269,15 +269,16 @@
final long newId = Long.parseLong(split[4]);
final int newMediaType = Integer.parseInt(split[5]);
final boolean newIsDownload = Integer.parseInt(split[6]) != 0;
- final String ownerPackage = split[7];
- // Path can include ':', assume rest of split[8..length] is path.
- final String oldPath = String.join(":", Arrays.copyOfRange(split, 8, split.length));
+ final String oldOwnerPackage = split[7];
+ final String newOwnerPackage = split[8];
+ // Path can include ':', assume rest of split[9..length] is path.
+ final String oldPath = String.join(":", Arrays.copyOfRange(split, 9, split.length));
Trace.beginSection("_UPDATE");
try {
mFilesListener.onUpdate(DatabaseHelper.this, volumeName, oldId,
oldMediaType, oldIsDownload, newId, newMediaType, newIsDownload,
- ownerPackage, oldPath);
+ oldOwnerPackage, newOwnerPackage, oldPath);
} finally {
Trace.endSection();
}
@@ -989,7 +990,8 @@
final String updateArg =
"old.volume_name||':'||old._id||':'||old.media_type||':'||old.is_download"
+ "||':'||new._id||':'||new.media_type||':'||new.is_download"
- + "||':'||ifnull(old.owner_package_name,'null')||':'||old._data";
+ + "||':'||ifnull(old.owner_package_name,'null')"
+ + "||':'||ifnull(new.owner_package_name,'null')||':'||old._data";
final String deleteArg =
"old.volume_name||':'||old._id||':'||old.media_type||':'||old.is_download"
+ "||':'||ifnull(old.owner_package_name,'null')||':'||old._data";
@@ -1247,7 +1249,7 @@
static final int VERSION_O = 800;
static final int VERSION_P = 900;
static final int VERSION_Q = 1023;
- static final int VERSION_R = 1113;
+ static final int VERSION_R = 1114;
static final int VERSION_LATEST = VERSION_R;
/**
@@ -1387,6 +1389,9 @@
if (fromVersion < 1113) {
// Empty version bump to ensure triggers are recreated
}
+ if (fromVersion < 1114) {
+ // Empty version bump to ensure triggers are recreated
+ }
if (recomputeDataValues) {
recomputeDataValues(db, internal);
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 627b752..37d1e17 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -60,6 +60,7 @@
import static com.android.providers.media.util.FileUtils.extractTopLevelDir;
import static com.android.providers.media.util.FileUtils.extractVolumeName;
import static com.android.providers.media.util.FileUtils.getAbsoluteSanitizedPath;
+import static com.android.providers.media.util.FileUtils.isDataOrObbPath;
import static com.android.providers.media.util.FileUtils.isDownload;
import static com.android.providers.media.util.FileUtils.sanitizePath;
import static com.android.providers.media.util.Logging.LOGV;
@@ -564,9 +565,10 @@
public void onUpdate(@NonNull DatabaseHelper helper, @NonNull String volumeName,
long oldId, int oldMediaType, boolean oldIsDownload,
long newId, int newMediaType, boolean newIsDownload,
- String ownerPackage, String oldPath) {
+ String oldOwnerPackage, String newOwnerPackage, String oldPath) {
final boolean isDownload = oldIsDownload || newIsDownload;
- handleUpdatedRowForFuse(oldPath, ownerPackage, oldId, newId);
+ handleUpdatedRowForFuse(oldPath, oldOwnerPackage, oldId, newId);
+ handleOwnerPackageNameChange(oldPath, oldOwnerPackage, newOwnerPackage);
acceptWithExpansion(helper::notifyUpdate, volumeName, oldId, oldMediaType, isDownload);
if (newMediaType != oldMediaType) {
@@ -1276,6 +1278,13 @@
return new String[] {""};
}
+ // Do not allow apps to list Android/data or Android/obb dirs. Installer and
+ // MOUNT_EXTERNAL_ANDROID_WRITABLE apps won't be blocked by this, as their OBB dirs
+ // are mounted to lowerfs directly.
+ if (isDataOrObbPath(path)) {
+ return new String[] {""};
+ }
+
if (shouldBypassFuseRestrictions(/*forWrite*/ false, path)) {
return new String[] {"/"};
}
@@ -1342,6 +1351,55 @@
mimeType.startsWith(supportedPrimaryMimeType));
}
+ /**
+ * Removes owner package for the renamed path if the calling package doesn't own the db row
+ *
+ * When oldPath is renamed to newPath, if newPath exists in the database, and caller is not the
+ * owner of the file, owner package is set to 'null'. This prevents previous owner of newPath
+ * from accessing renamed file.
+ * @return {@code true} if
+ * <ul>
+ * <li> there is no corresponding database row for given {@code path}
+ * <li> shared calling package is the owner of the database row
+ * <li> owner package name is already set to 'null'
+ * <li> updating owner package name to 'null' was successful.
+ * </ul>
+ * Returns {@code false} otherwise.
+ */
+ private boolean maybeRemoveOwnerPackageForFuseRename(@NonNull DatabaseHelper helper,
+ @NonNull String path) {
+
+ final Uri uri = Files.getContentUriForPath(path);
+ final int match = matchUri(uri, isCallingPackageAllowedHidden());
+ final String ownerPackageName;
+ final String selection = MediaColumns.DATA + " =? AND "
+ + MediaColumns.OWNER_PACKAGE_NAME + " != 'null'";
+ final String[] selectionArgs = new String[] {path};
+
+ final SQLiteQueryBuilder qbForQuery =
+ getQueryBuilder(TYPE_QUERY, match, uri, Bundle.EMPTY, null);
+ try (Cursor c = qbForQuery.query(helper, new String[] {FileColumns.OWNER_PACKAGE_NAME},
+ selection, selectionArgs, null, null, null, null, null)) {
+ if (!c.moveToFirst()) {
+ // We don't need to remove owner_package from db row if path doesn't exist in
+ // database or owner_package is already set to 'null'
+ return true;
+ }
+ ownerPackageName = c.getString(0);
+ if (isCallingIdentitySharedPackageName(ownerPackageName)) {
+ // We don't need to remove owner_package from db row if calling package is the owner
+ // of the database row
+ return true;
+ }
+ }
+
+ final SQLiteQueryBuilder qbForUpdate =
+ getQueryBuilder(TYPE_UPDATE, match, uri, Bundle.EMPTY, null);
+ ContentValues values = new ContentValues();
+ values.put(FileColumns.OWNER_PACKAGE_NAME, "null");
+ return qbForUpdate.update(helper, values, selection, selectionArgs) == 1;
+ }
+
private boolean updateDatabaseForFuseRename(@NonNull DatabaseHelper helper,
@NonNull String oldPath, @NonNull String newPath, @NonNull ContentValues values) {
return updateDatabaseForFuseRename(helper, oldPath, newPath, values, Bundle.EMPTY);
@@ -1623,26 +1681,34 @@
if (!isMimeTypeSupportedInPath(newPath, newMimeType)) {
return OsConstants.EPERM;
}
- return renameFileUncheckedForFuse(oldPath, newPath);
+ return renameFileForFuse(oldPath, newPath, /* bypassRestrictions */ false) ;
}
private int renameFileUncheckedForFuse(String oldPath, String newPath) {
- final String newMimeType = MimeUtils.resolveMimeType(new File(newPath));
+ return renameFileForFuse(oldPath, newPath, /* bypassRestrictions */ true) ;
+ }
+
+ private int renameFileForFuse(String oldPath, String newPath, boolean bypassRestrictions) {
final DatabaseHelper helper;
try {
helper = getDatabaseForUri(Files.getContentUriForPath(oldPath));
} catch (VolumeNotFoundException e) {
- throw new IllegalStateException("Volume not found while trying to update database for"
- + oldPath + ". Rename failed due to database update error", e);
+ throw new IllegalStateException("Failed to update database row with " + oldPath, e);
}
helper.beginTransaction();
try {
+ final String newMimeType = MimeUtils.resolveMimeType(new File(newPath));
final String oldMimeType = MimeUtils.resolveMimeType(new File(oldPath));
if (!updateDatabaseForFuseRename(helper, oldPath, newPath,
getContentValuesForFuseRename(newPath, oldMimeType, newMimeType))) {
- Log.e(TAG, "Calling package doesn't have write permission to rename file.");
- return OsConstants.EPERM;
+ if (!bypassRestrictions) {
+ Log.e(TAG, "Calling package doesn't have write permission to rename file.");
+ return OsConstants.EPERM;
+ } else if (!maybeRemoveOwnerPackageForFuseRename(helper, newPath)) {
+ Log.wtf(TAG, "Couldn't clear owner package name for " + newPath);
+ return OsConstants.EPERM;
+ }
}
// Try renaming oldPath to newPath in lower file system.
@@ -2679,8 +2745,7 @@
try {
return qb.insert(helper, values);
} catch (SQLiteConstraintException e) {
- final long rowId = getIdIfPathExistsForPackage(qb, helper, path,
- getCallingPackageOrSelf());
+ final long rowId = getIdIfPathExistsForCallingPackage(qb, helper, path);
// Apps sometimes create a file via direct path and then insert it into
// MediaStore via ContentResolver. The former should create a database entry,
// so we have to treat the latter as an upsert.
@@ -2696,21 +2761,21 @@
}
/**
- * @return row id of the entry with path {@code path} and owner {@code packageName}, if it
+ * @return row id of the entry with path {@code path} and owner as shared calling package, if it
* exists.
*/
- private long getIdIfPathExistsForPackage(@NonNull SQLiteQueryBuilder qb,
- @NonNull DatabaseHelper helper, String path, String packageName) {
- final String[] projection = new String[] {FileColumns._ID};
- final String selection = FileColumns.DATA + " LIKE ? AND " +
- FileColumns.OWNER_PACKAGE_NAME + " LIKE ? ";
+ private long getIdIfPathExistsForCallingPackage(@NonNull SQLiteQueryBuilder qb,
+ @NonNull DatabaseHelper helper, String path) {
+ final String[] projection = new String[] {FileColumns._ID, FileColumns.OWNER_PACKAGE_NAME};
+ final String selection = FileColumns.DATA + " =? ";
- // TODO(b:149842708) Handle sharedUid. Package name check will fail if FUSE didn't
- // use the right package name for sharedUid.
- try (Cursor c = qb.query(helper, projection, selection, new String[] {path, packageName},
- null, null, null, null, null)) {
+ try (Cursor c = qb.query(helper, projection, selection, new String[] {path}, null, null,
+ null, null, null)) {
if (c.moveToFirst()) {
- return c.getLong(0);
+ final String ownerPackage = c.getString(1);
+ if (ownerPackage != null && isCallingIdentitySharedPackageName(ownerPackage)) {
+ return c.getLong(0);
+ }
}
}
return -1;
@@ -2836,6 +2901,9 @@
// IDs are forever; nobody should be editing them
initialValues.remove(MediaColumns._ID);
+ // Expiration times are hard-coded; let's derive them
+ FileUtils.computeDateExpires(initialValues);
+
// Ignore or augment incoming raw filesystem paths
for (String column : sDataColumns.keySet()) {
if (!initialValues.containsKey(column)) continue;
@@ -3187,11 +3255,8 @@
// When caller is system, such as the media scanner, we're willing
// to let them access any columns they want
} else {
- // TODO: re-enable regardless of target SDK in b/146518586
- if (getCallingPackageTargetSdkVersion() >= Build.VERSION_CODES.R) {
- qb.setStrictColumns(true);
- qb.setStrictGrammar(true);
- }
+ qb.setStrictColumns(true);
+ qb.setStrictGrammar(true);
}
final String callingPackage = getCallingPackageOrSelf();
@@ -3910,7 +3975,7 @@
}
if (isFilesTable && !isCallingPackageSystem()) {
- Metrics.logDeletion(volumeName, System.currentTimeMillis(),
+ Metrics.logDeletion(volumeName, mCallingIdentity.get().uid,
getCallingPackageOrSelf(), count);
}
}
@@ -4159,8 +4224,7 @@
break;
case MediaStore.CREATE_TRASH_REQUEST_CALL:
allowedColumns = Arrays.asList(
- MediaColumns.IS_TRASHED,
- MediaColumns.DATE_EXPIRES);
+ MediaColumns.IS_TRASHED);
break;
default:
allowedColumns = Arrays.asList();
@@ -4544,6 +4608,9 @@
// IDs are forever; nobody should be editing them
initialValues.remove(MediaColumns._ID);
+ // Expiration times are hard-coded; let's derive them
+ FileUtils.computeDateExpires(initialValues);
+
// Ignore or augment incoming raw filesystem paths
for (String column : sDataColumns.keySet()) {
if (!initialValues.containsKey(column)) continue;
@@ -5209,6 +5276,16 @@
mCallingIdentity.get().addDeletedRowId(path, rowId);
}
+ private void handleOwnerPackageNameChange(@NonNull String oldPath,
+ @NonNull String oldOwnerPackage, @NonNull String newOwnerPackage) {
+ if (Objects.equals(oldOwnerPackage, newOwnerPackage)) {
+ return;
+ }
+ // Invalidate saved owned ID's of the previous owner of the renamed path, this prevents old
+ // owner from gaining access to replaced file.
+ invalidateLocalCallingIdentityCache(oldOwnerPackage, "owner_package_changed:" + oldPath);
+ }
+
/**
* Return the {@link MediaColumns#DATA} field for the given {@code Uri}.
*/
@@ -5454,6 +5531,16 @@
}
} else {
Log.i(TAG, "Using lower FS for " + filePath);
+ if (forWrite) {
+ // When opening for write on the lower filesystem, invalidate the VFS dentry
+ // so subsequent open/getattr calls will return correctly.
+ //
+ // A 'dirty' dentry with write back cache enabled can cause the kernel to
+ // ignore file attributes or even see stale page cache data when the lower
+ // filesystem has been modified outside of the FUSE driver
+ invalidateFuseDentry(file);
+ }
+
pfd = lowerFsFd;
}
}
@@ -6813,7 +6900,6 @@
sMutableColumns.add(MediaStore.MediaColumns.IS_PENDING);
sMutableColumns.add(MediaStore.MediaColumns.IS_TRASHED);
sMutableColumns.add(MediaStore.MediaColumns.IS_FAVORITE);
- sMutableColumns.add(MediaStore.MediaColumns.DATE_EXPIRES);
sMutableColumns.add(MediaStore.Audio.AudioColumns.BOOKMARK);
diff --git a/src/com/android/providers/media/PermissionActivity.java b/src/com/android/providers/media/PermissionActivity.java
index 85d679c..0c398dd 100644
--- a/src/com/android/providers/media/PermissionActivity.java
+++ b/src/com/android/providers/media/PermissionActivity.java
@@ -30,6 +30,7 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
@@ -50,7 +51,9 @@
import android.util.Size;
import android.view.KeyEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
import android.widget.TextView;
@@ -64,6 +67,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -88,6 +92,9 @@
private String verb;
private String data;
private String volumeName;
+ private ApplicationInfo appInfo;
+
+ private TextView titleView;
private static final String VERB_WRITE = "write";
private static final String VERB_TRASH = "trash";
@@ -116,7 +123,8 @@
uris = collectUris(getIntent().getExtras().getParcelable(MediaStore.EXTRA_CLIP_DATA));
values = getIntent().getExtras().getParcelable(MediaStore.EXTRA_CONTENT_VALUES);
- label = resolveCallingLabel();
+ appInfo = resolveCallingAppInfo();
+ label = resolveAppLabel(appInfo);
verb = resolveVerb();
data = resolveData();
volumeName = MediaStore.getVolumeName(uris.get(0));
@@ -159,6 +167,12 @@
final WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
params.width = getResources().getDimensionPixelSize(R.dimen.permission_dialog_width);
dialog.getWindow().setAttributes(params);
+
+ // Hunt around to find the title of our newly created dialog so we can
+ // adjust accessibility focus once descriptions have been loaded
+ titleView = (TextView) findViewByPredicate(dialog.getWindow().getDecorView(), (view) -> {
+ return (view instanceof TextView) && view.isImportantForAccessibility();
+ });
}
private void onPositiveAction(DialogInterface dialog, int which) {
@@ -166,8 +180,8 @@
@Override
protected Void doInBackground(Void... params) {
Log.d(TAG, "User allowed grant for " + uris);
- Metrics.logPermissionGranted(volumeName,
- System.currentTimeMillis(), getCallingPackage(), 1);
+ Metrics.logPermissionGranted(volumeName, appInfo.uid,
+ getCallingPackage(), 1);
try {
switch (getIntent().getAction()) {
case MediaStore.CREATE_WRITE_REQUEST_CALL: {
@@ -220,8 +234,8 @@
@Override
protected Void doInBackground(Void... params) {
Log.d(TAG, "User declined request for " + uris);
- Metrics.logPermissionDenied(volumeName,
- System.currentTimeMillis(), getCallingPackage(), 1);
+ Metrics.logPermissionDenied(volumeName, appInfo.uid, getCallingPackage(),
+ 1);
return null;
}
@@ -246,18 +260,12 @@
}
/**
- * Resolve a label that represents the remote calling app, typically the
- * name of that app.
+ * Resolve a label that represents the app denoted by given {@link ApplicationInfo}.
*/
- private @NonNull CharSequence resolveCallingLabel() throws NameNotFoundException {
- final String callingPackage = getCallingPackage();
- if (TextUtils.isEmpty(callingPackage)) {
- throw new NameNotFoundException("Missing calling package");
- }
-
+ private @NonNull CharSequence resolveAppLabel(final ApplicationInfo ai)
+ throws NameNotFoundException {
final PackageManager pm = getPackageManager();
- final CharSequence callingLabel = pm
- .getApplicationLabel(pm.getApplicationInfo(callingPackage, 0));
+ final CharSequence callingLabel = pm.getApplicationLabel(ai);
if (TextUtils.isEmpty(callingLabel)) {
throw new NameNotFoundException("Missing calling package");
}
@@ -265,6 +273,18 @@
return callingLabel;
}
+ /**
+ * Resolve the application info of the calling app.
+ */
+ private @NonNull ApplicationInfo resolveCallingAppInfo() throws NameNotFoundException {
+ final String callingPackage = getCallingPackage();
+ if (TextUtils.isEmpty(callingPackage)) {
+ throw new NameNotFoundException("Missing calling package");
+ }
+
+ return getPackageManager().getApplicationInfo(callingPackage, 0);
+ }
+
private @NonNull String resolveVerb() {
switch (getIntent().getAction()) {
case MediaStore.CREATE_WRITE_REQUEST_CALL:
@@ -361,6 +381,27 @@
}
/**
+ * Recursively walk the given view hierarchy looking for the first
+ * {@link View} which matches the given predicate.
+ */
+ private static @Nullable View findViewByPredicate(@NonNull View root,
+ @NonNull Predicate<View> predicate) {
+ if (predicate.test(root)) {
+ return root;
+ }
+ if (root instanceof ViewGroup) {
+ final ViewGroup group = (ViewGroup) root;
+ for (int i = 0; i < group.getChildCount(); i++) {
+ final View res = findViewByPredicate(group.getChildAt(i), predicate);
+ if (res != null) {
+ return res;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Task that will load a set of {@link Description} to be eventually
* displayed in the body of the dialog.
*/
@@ -423,6 +464,11 @@
} else {
bindAsText(results);
}
+
+ // This is pretty hacky, but somehow our dynamic loading of content
+ // can confuse accessibility focus, so refocus on the actual dialog
+ // title to announce ourselves properly
+ titleView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
}
/**
diff --git a/src/com/android/providers/media/scan/MediaScanner.java b/src/com/android/providers/media/scan/MediaScanner.java
index 6034ca2..b217da1 100644
--- a/src/com/android/providers/media/scan/MediaScanner.java
+++ b/src/com/android/providers/media/scan/MediaScanner.java
@@ -16,10 +16,10 @@
package com.android.providers.media.scan;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_EVENT__REASON__DEMAND;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_EVENT__REASON__IDLE;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_EVENT__REASON__MOUNTED;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_EVENT__REASON__UNKNOWN;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__REASON__DEMAND;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__REASON__IDLE;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__REASON__MOUNTED;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__REASON__UNKNOWN;
import android.content.Context;
import android.net.Uri;
@@ -29,10 +29,10 @@
import java.io.File;
public interface MediaScanner {
- public static final int REASON_UNKNOWN = MEDIA_PROVIDER_SCAN_EVENT__REASON__UNKNOWN;
- public static final int REASON_MOUNTED = MEDIA_PROVIDER_SCAN_EVENT__REASON__MOUNTED;
- public static final int REASON_DEMAND = MEDIA_PROVIDER_SCAN_EVENT__REASON__DEMAND;
- public static final int REASON_IDLE = MEDIA_PROVIDER_SCAN_EVENT__REASON__IDLE;
+ public static final int REASON_UNKNOWN = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__UNKNOWN;
+ public static final int REASON_MOUNTED = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__MOUNTED;
+ public static final int REASON_DEMAND = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__DEMAND;
+ public static final int REASON_IDLE = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__IDLE;
public Context getContext();
public void scanDirectory(File file, int reason);
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index 7f12f9f..6ef88c5 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -758,13 +758,12 @@
*/
private static void withGenericValues(ContentProviderOperation.Builder op,
File file, BasicFileAttributes attrs, String mimeType) {
- op.withValue(FileColumns.MEDIA_TYPE, MimeUtils.resolveMediaType(mimeType));
+ withOptionalMimeType(op, Optional.ofNullable(mimeType));
op.withValue(MediaColumns.DATA, file.getAbsolutePath());
op.withValue(MediaColumns.SIZE, attrs.size());
op.withValue(MediaColumns.DATE_MODIFIED, lastModifiedTime(file, attrs));
op.withValue(MediaColumns.DATE_TAKEN, null);
- op.withValue(MediaColumns.MIME_TYPE, mimeType);
op.withValue(MediaColumns.IS_DRM, 0);
op.withValue(MediaColumns.WIDTH, null);
op.withValue(MediaColumns.HEIGHT, null);
@@ -799,11 +798,11 @@
*/
private static void withRetrieverValues(ContentProviderOperation.Builder op,
MediaMetadataRetriever mmr, String mimeType) {
- withOptionalValue(op, MediaColumns.DATE_TAKEN,
- parseOptionalDate(mmr.extractMetadata(METADATA_KEY_DATE)));
- withOptionalValue(op, MediaColumns.MIME_TYPE,
+ withOptionalMimeType(op,
parseOptionalMimeType(mimeType, mmr.extractMetadata(METADATA_KEY_MIMETYPE)));
+ withOptionalValue(op, MediaColumns.DATE_TAKEN,
+ parseOptionalDate(mmr.extractMetadata(METADATA_KEY_DATE)));
withOptionalValue(op, MediaColumns.CD_TRACK_NUMBER,
parseOptional(mmr.extractMetadata(METADATA_KEY_CD_TRACK_NUMBER)));
withOptionalValue(op, MediaColumns.ALBUM,
@@ -845,8 +844,9 @@
*/
private static void withXmpValues(ContentProviderOperation.Builder op,
XmpInterface xmp, String mimeType) {
- withOptionalValue(op, MediaColumns.MIME_TYPE,
+ withOptionalMimeType(op,
parseOptionalMimeType(mimeType, xmp.getFormat()));
+
op.withValue(MediaColumns.DOCUMENT_ID, xmp.getDocumentId());
op.withValue(MediaColumns.INSTANCE_ID, xmp.getInstanceId());
op.withValue(MediaColumns.ORIGINAL_DOCUMENT_ID, xmp.getOriginalDocumentId());
@@ -857,22 +857,39 @@
* Overwrite a value in the given {@link ContentProviderOperation}, but only
* when the given {@link Optional} value is present.
*/
- private static void withOptionalValue(ContentProviderOperation.Builder op,
- String key, Optional<?> value) {
+ private static void withOptionalValue(@NonNull ContentProviderOperation.Builder op,
+ @NonNull String key, @NonNull Optional<?> value) {
if (value.isPresent()) {
op.withValue(key, value.get());
}
}
+ /**
+ * Overwrite the {@link MediaColumns#MIME_TYPE} and
+ * {@link FileColumns#MEDIA_TYPE} values in the given
+ * {@link ContentProviderOperation}, but only when the given
+ * {@link Optional} value is present.
+ *
+ * @param value An optional MIME type to apply to this operation.
+ */
+ private static void withOptionalMimeType(@NonNull ContentProviderOperation.Builder op,
+ @NonNull Optional<String> value) {
+ if (value.isPresent()) {
+ final String mimeType = value.get();
+ final int mediaType = MimeUtils.resolveMediaType(mimeType);
+
+ op.withValue(MediaColumns.MIME_TYPE, mimeType);
+ op.withValue(FileColumns.MEDIA_TYPE, mediaType);
+ }
+ }
+
private static @NonNull ContentProviderOperation.Builder scanItemDirectory(long existingId,
File file, BasicFileAttributes attrs, String mimeType, String volumeName) {
final ContentProviderOperation.Builder op = newUpsert(volumeName, existingId);
withGenericValues(op, file, attrs, mimeType);
try {
- op.withValue(FileColumns.MEDIA_TYPE, 0);
op.withValue(FileColumns.FORMAT, MtpConstants.FORMAT_ASSOCIATION);
- op.withValue(FileColumns.MIME_TYPE, null);
} catch (Exception e) {
logTroubleScanning(file, e);
}
diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java
index 8d64d74..78f2c6f 100644
--- a/src/com/android/providers/media/util/FileUtils.java
+++ b/src/com/android/providers/media/util/FileUtils.java
@@ -804,13 +804,13 @@
* Default duration that {@link MediaColumns#IS_PENDING} items should be
* preserved for until automatically cleaned by {@link #runIdleMaintenance}.
*/
- public static final long DEFAULT_DURATION_PENDING = DateUtils.WEEK_IN_MILLIS;
+ public static final long DEFAULT_DURATION_PENDING = 7 * DateUtils.DAY_IN_MILLIS;
/**
* Default duration that {@link MediaColumns#IS_TRASHED} items should be
* preserved for until automatically cleaned by {@link #runIdleMaintenance}.
*/
- public static final long DEFAULT_DURATION_TRASHED = DateUtils.WEEK_IN_MILLIS;
+ public static final long DEFAULT_DURATION_TRASHED = 30 * DateUtils.DAY_IN_MILLIS;
public static boolean isDownload(@NonNull String path) {
return PATTERN_DOWNLOADS_FILE.matcher(path).matches();
@@ -834,6 +834,12 @@
"(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|media|obb|sandbox)/([^/]+)(/.*)?");
/**
+ * Regex that matches Android/obb or Android/data path.
+ */
+ public static final Pattern PATTERN_DATA_OR_OBB_PATH = Pattern.compile(
+ "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb)/?$");
+
+ /**
* Regex that matches paths for {@link MediaColumns#RELATIVE_PATH}; it
* captures both top-level paths and sandboxed paths.
*/
@@ -920,6 +926,15 @@
}
/**
+ * Returns true if relative path is Android/data or Android/obb path.
+ */
+ public static boolean isDataOrObbPath(String path) {
+ if (path == null) return false;
+ final Matcher m = PATTERN_DATA_OR_OBB_PATH.matcher(path);
+ return m.matches();
+ }
+
+ /**
* Returns the name of the top level directory, or null if the path doesn't go through the
* external storage directory.
*/
@@ -933,6 +948,36 @@
}
/**
+ * Compute the value of {@link MediaColumns#DATE_EXPIRES} based on other
+ * columns being modified by this operation.
+ */
+ public static void computeDateExpires(@NonNull ContentValues values) {
+ // External apps have no ability to change this field
+ values.remove(MediaColumns.DATE_EXPIRES);
+
+ // Only define the field when this modification is actually adjusting
+ // one of the flags that should influence the expiration
+ final Integer pending = values.getAsInteger(MediaColumns.IS_PENDING);
+ if (pending != null) {
+ if (pending != 0) {
+ values.put(MediaColumns.DATE_EXPIRES,
+ (System.currentTimeMillis() + DEFAULT_DURATION_PENDING) / 1000);
+ } else {
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ }
+ }
+ final Integer trashed = values.getAsInteger(MediaColumns.IS_TRASHED);
+ if (trashed != null) {
+ if (trashed != 0) {
+ values.put(MediaColumns.DATE_EXPIRES,
+ (System.currentTimeMillis() + DEFAULT_DURATION_TRASHED) / 1000);
+ } else {
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ }
+ }
+ }
+
+ /**
* Compute several scattered {@link MediaColumns} values from
* {@link MediaColumns#DATA}. This method performs no enforcement of
* argument validity.
diff --git a/src/com/android/providers/media/util/Metrics.java b/src/com/android/providers/media/util/Metrics.java
index 2b40301..86a0302 100644
--- a/src/com/android/providers/media/util/Metrics.java
+++ b/src/com/android/providers/media/util/Metrics.java
@@ -16,17 +16,17 @@
package com.android.providers.media.util;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_DELETION_EVENT;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_IDLE_MAINTENANCE;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_PERMISSION_EVENT;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_PERMISSION_EVENT__RESULT__USER_DENIED;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_PERMISSION_EVENT__RESULT__USER_GRANTED;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_EVENT;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_EVENT__VOLUME_TYPE__EXTERNAL_OTHER;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_EVENT__VOLUME_TYPE__EXTERNAL_PRIMARY;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_EVENT__VOLUME_TYPE__INTERNAL;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_EVENT__VOLUME_TYPE__UNKNOWN;
-import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCHEMA_CHANGE;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_CONTENT_DELETED;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_IDLE_MAINTENANCE_FINISHED;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_PERMISSION_REQUESTED;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_PERMISSION_REQUESTED__RESULT__USER_DENIED;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_PERMISSION_REQUESTED__RESULT__USER_GRANTED;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__EXTERNAL_OTHER;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__EXTERNAL_PRIMARY;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__INTERNAL;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__UNKNOWN;
+import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCHEMA_CHANGED;
import static com.android.providers.media.scan.MediaScanner.REASON_DEMAND;
import static com.android.providers.media.scan.MediaScanner.REASON_IDLE;
import static com.android.providers.media.scan.MediaScanner.REASON_MOUNTED;
@@ -55,41 +55,41 @@
final float normalizedUpdateCount = ((float) updateCount) / itemCount;
final float normalizedDeleteCount = ((float) deleteCount) / itemCount;
- MediaProviderStatsLog.write(MEDIA_PROVIDER_SCAN_EVENT,
+ MediaProviderStatsLog.write(MEDIA_PROVIDER_SCAN_OCCURRED,
translateVolumeName(volumeName), reason, itemCount, normalizedDurationMillis,
normalizedInsertCount, normalizedUpdateCount, normalizedDeleteCount);
}
- public static void logDeletion(@NonNull String volumeName, long timestampMillis,
- String packageName, int itemCount) {
+ public static void logDeletion(@NonNull String volumeName, int uid, String packageName,
+ int itemCount) {
Logging.logPersistent(String.format(
- "Deleted %4$d items on %1$s due to %3$s",
- volumeName, timestampMillis, packageName, itemCount));
+ "Deleted %3$d items on %1$s due to %2$s",
+ volumeName, packageName, itemCount));
- MediaProviderStatsLog.write(MEDIA_PROVIDER_DELETION_EVENT,
- translateVolumeName(volumeName), timestampMillis, packageName, itemCount);
+ MediaProviderStatsLog.write(MEDIA_CONTENT_DELETED,
+ translateVolumeName(volumeName), uid, itemCount);
}
- public static void logPermissionGranted(@NonNull String volumeName, long timestampMillis,
- String packageName, int itemCount) {
+ public static void logPermissionGranted(@NonNull String volumeName, int uid, String packageName,
+ int itemCount) {
Logging.logPersistent(String.format(
- "Granted permission to %4$d items on %1$s to %3$s",
- volumeName, timestampMillis, packageName, itemCount));
+ "Granted permission to %3$d items on %1$s to %2$s",
+ volumeName, packageName, itemCount));
- MediaProviderStatsLog.write(MEDIA_PROVIDER_PERMISSION_EVENT,
- translateVolumeName(volumeName), timestampMillis, packageName, itemCount,
- MEDIA_PROVIDER_PERMISSION_EVENT__RESULT__USER_GRANTED);
+ MediaProviderStatsLog.write(MEDIA_PROVIDER_PERMISSION_REQUESTED,
+ translateVolumeName(volumeName), uid, itemCount,
+ MEDIA_PROVIDER_PERMISSION_REQUESTED__RESULT__USER_GRANTED);
}
- public static void logPermissionDenied(@NonNull String volumeName, long timestampMillis,
- String packageName, int itemCount) {
+ public static void logPermissionDenied(@NonNull String volumeName, int uid, String packageName,
+ int itemCount) {
Logging.logPersistent(String.format(
- "Denied permission to %4$d items on %1$s to %3$s",
- volumeName, timestampMillis, packageName, itemCount));
+ "Denied permission to %3$d items on %1$s to %2$s",
+ volumeName, packageName, itemCount));
- MediaProviderStatsLog.write(MEDIA_PROVIDER_PERMISSION_EVENT,
- translateVolumeName(volumeName), timestampMillis, packageName, itemCount,
- MEDIA_PROVIDER_PERMISSION_EVENT__RESULT__USER_DENIED);
+ MediaProviderStatsLog.write(MEDIA_PROVIDER_PERMISSION_REQUESTED,
+ translateVolumeName(volumeName), uid, itemCount,
+ MEDIA_PROVIDER_PERMISSION_REQUESTED__RESULT__USER_DENIED);
}
public static void logSchemaChange(@NonNull String volumeName, int versionFrom, int versionTo,
@@ -100,7 +100,7 @@
final float normalizedDurationMillis = ((float) durationMillis) / itemCount;
- MediaProviderStatsLog.write(MEDIA_PROVIDER_SCHEMA_CHANGE,
+ MediaProviderStatsLog.write(MEDIA_PROVIDER_SCHEMA_CHANGED,
translateVolumeName(volumeName), versionFrom, versionTo, itemCount,
normalizedDurationMillis);
}
@@ -115,7 +115,7 @@
final float normalizedStaleThumbnails = ((float) staleThumbnails) / itemCount;
final float normalizedExpiredMedia = ((float) expiredMedia) / itemCount;
- MediaProviderStatsLog.write(MEDIA_PROVIDER_IDLE_MAINTENANCE,
+ MediaProviderStatsLog.write(MEDIA_PROVIDER_IDLE_MAINTENANCE_FINISHED,
translateVolumeName(volumeName), itemCount, normalizedDurationMillis,
normalizedStaleThumbnails, normalizedExpiredMedia);
}
@@ -133,16 +133,16 @@
private static int translateVolumeName(@NonNull String volumeName) {
switch (volumeName) {
case MediaStore.VOLUME_INTERNAL:
- return MEDIA_PROVIDER_SCAN_EVENT__VOLUME_TYPE__INTERNAL;
+ return MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__INTERNAL;
case MediaStore.VOLUME_EXTERNAL:
// Callers using generic "external" volume name end up applying
// to all external volumes, so we can't tell which volumes were
// actually changed
- return MEDIA_PROVIDER_SCAN_EVENT__VOLUME_TYPE__UNKNOWN;
+ return MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__UNKNOWN;
case MediaStore.VOLUME_EXTERNAL_PRIMARY:
- return MEDIA_PROVIDER_SCAN_EVENT__VOLUME_TYPE__EXTERNAL_PRIMARY;
+ return MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__EXTERNAL_PRIMARY;
default:
- return MEDIA_PROVIDER_SCAN_EVENT__VOLUME_TYPE__EXTERNAL_OTHER;
+ return MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__EXTERNAL_OTHER;
}
}
}
diff --git a/src/com/android/providers/media/util/PermissionUtils.java b/src/com/android/providers/media/util/PermissionUtils.java
index c076914..387ced0 100644
--- a/src/com/android/providers/media/util/PermissionUtils.java
+++ b/src/com/android/providers/media/util/PermissionUtils.java
@@ -35,16 +35,22 @@
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
+import android.provider.MediaStore;
public class PermissionUtils {
// Callers must hold both the old and new permissions, so that we can
// handle obscure cases like when an app targets Q but was installed on
// a device that was originally running on P before being upgraded to Q.
+ private static volatile int sLegacyMediaProviderUid = -1;
+
public static boolean checkPermissionSystem(Context context,
int pid, int uid, String packageName) {
+ // Apps sharing legacy MediaProvider's uid like DownloadProvider and MTP are treated as
+ // system.
return uid == android.os.Process.SYSTEM_UID || uid == android.os.Process.myUid()
- || uid == android.os.Process.SHELL_UID || uid == android.os.Process.ROOT_UID;
+ || uid == android.os.Process.SHELL_UID || uid == android.os.Process.ROOT_UID
+ || isLegacyMediaProvider(context, uid);
}
public static boolean checkPermissionBackup(Context context, int pid, int uid) {
@@ -209,4 +215,15 @@
throw new IllegalStateException(op + " has unknown mode " + mode);
}
}
+
+ private static boolean isLegacyMediaProvider(Context context, int uid) {
+ if (sLegacyMediaProviderUid == -1) {
+ // Uid stays constant while legacy Media Provider stays installed. Cache legacy
+ // MediaProvider's uid for the first time.
+ sLegacyMediaProviderUid = context.getPackageManager()
+ .resolveContentProvider(MediaStore.AUTHORITY_LEGACY, 0)
+ .applicationInfo.uid;
+ }
+ return (uid == sLegacyMediaProviderUid);
+ }
}
diff --git a/tests/Android.bp b/tests/Android.bp
index e79f576..ee5143d 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -9,6 +9,7 @@
"device-tests",
"mts",
],
+ compile_multilib: "both",
manifest: "AndroidManifest.xml",
diff --git a/tests/client/Android.bp b/tests/client/Android.bp
index 65b45cd..9d5be41 100644
--- a/tests/client/Android.bp
+++ b/tests/client/Android.bp
@@ -4,6 +4,7 @@
"device-tests",
"mts",
],
+ compile_multilib: "both",
manifest: "AndroidManifest.xml",
diff --git a/tests/jni/FuseDaemonTest/Android.bp b/tests/jni/FuseDaemonTest/Android.bp
index 1208e1d..e3e92b0 100644
--- a/tests/jni/FuseDaemonTest/Android.bp
+++ b/tests/jni/FuseDaemonTest/Android.bp
@@ -47,6 +47,7 @@
manifest: "AndroidManifest.xml",
srcs: ["src/**/*.java"],
static_libs: ["androidx.test.rules", "truth-prebuilt", "tests-fusedaemon-lib"],
+ compile_multilib: "both",
test_suites: ["general-tests", "mts"],
sdk_version: "test_current",
java_resources: [
@@ -61,6 +62,7 @@
manifest: "legacy/AndroidManifest.xml",
srcs: ["legacy/src/**/*.java"],
static_libs: ["androidx.test.rules", "truth-prebuilt", "tests-fusedaemon-lib"],
+ compile_multilib: "both",
test_suites: ["general-tests", "mts"],
sdk_version: "test_current",
target_sdk_version: "29",
diff --git a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
index c8690c5..259266b 100644
--- a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
+++ b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
@@ -159,6 +159,7 @@
runDeviceTest("testOpenContentResolverDup");
runDeviceTest("testContentResolverDelete");
runDeviceTest("testContentResolverUpdate");
+ runDeviceTest("testOpenContentResolverClose");
}
@Test
diff --git a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/ReaddirTestHelper.java b/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/ReaddirTestHelper.java
index 074231b..29986bc 100644
--- a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/ReaddirTestHelper.java
+++ b/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/ReaddirTestHelper.java
@@ -18,6 +18,7 @@
import java.io.File;
import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.Files;
@@ -69,14 +70,15 @@
public static ArrayList<String> readDirectory(String directoryPath, Filter<Path> filter) {
ArrayList<String> directoryEntries = new ArrayList<String>();
File dir = new File(directoryPath);
+
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(dir.toPath(),
filter)) {
for (Path de: directoryStream) {
directoryEntries.add(de.getFileName().toString());
}
- } catch (IOException x) {
+ } catch (IOException | DirectoryIteratorException x) {
Log.e(TAG, "IOException occurred while readding directory entries from " +
- directoryPath);
+ directoryPath, x);
}
return directoryEntries;
}
diff --git a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
index 124aa3b..bcb50a4 100644
--- a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
+++ b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
@@ -777,7 +777,7 @@
ParcelFileDescriptor.MODE_READ_WRITE);
ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
- assertRWR(readPfd.getFileDescriptor(), writePfd.getFileDescriptor());
+ assertRWR(readPfd, writePfd);
assertUpperFsFd(writePfd); // With cache
} finally {
file.delete();
@@ -796,7 +796,7 @@
ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_READ_WRITE);
- assertRWR(readPfd.getFileDescriptor(), writePfd.getFileDescriptor());
+ assertRWR(readPfd, writePfd);
assertLowerFsFd(writePfd);
} finally {
file.delete();
@@ -815,7 +815,7 @@
ParcelFileDescriptor.MODE_READ_WRITE);
ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw");
- assertRWR(readPfd.getFileDescriptor(), writePfd.getFileDescriptor());
+ assertRWR(readPfd, writePfd);
assertUpperFsFd(readPfd); // With cache
} finally {
file.delete();
@@ -834,7 +834,7 @@
ParcelFileDescriptor writePfd = ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_READ_WRITE);
- assertRWR(readPfd.getFileDescriptor(), writePfd.getFileDescriptor());
+ assertRWR(readPfd, writePfd);
assertLowerFsFd(readPfd);
} finally {
file.delete();
@@ -854,7 +854,7 @@
ParcelFileDescriptor writePfd = openWithMediaProvider(file, "w");
ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw");
- assertRWR(readPfd.getFileDescriptor(), writePfd.getFileDescriptor());
+ assertRWR(readPfd, writePfd);
assertLowerFsFd(writePfd);
assertUpperFsFd(readPfd); // Without cache
} finally {
@@ -878,7 +878,7 @@
ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_READ_WRITE);
- assertRWR(readPfd.getFileDescriptor(), writePfdDup.getFileDescriptor());
+ assertRWR(readPfd, writePfdDup);
assertLowerFsFd(writePfdDup);
} finally {
file.delete();
@@ -886,6 +886,38 @@
}
@Test
+ public void testOpenContentResolverClose() throws Exception {
+ String displayName = "open_content_resolver_close.jpg";
+ File file = new File(DCIM_DIR, displayName);
+
+ try {
+ byte[] readBuffer = new byte[10];
+ byte[] writeBuffer = new byte[10];
+ Arrays.fill(writeBuffer, (byte) 1);
+
+ assertThat(file.createNewFile()).isTrue();
+
+ // Lower fs open and write
+ ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
+ Os.pwrite(writePfd.getFileDescriptor(), writeBuffer, 0, 10, 0);
+
+ // Close so upper fs open will not use direct_io
+ writePfd.close();
+
+ // Upper fs open and read without direct_io
+ ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_READ_WRITE);
+ Os.pread(readPfd.getFileDescriptor(), readBuffer, 0, 10, 0);
+
+ // Last write on lower fs is visible via upper fs
+ assertThat(readBuffer).isEqualTo(writeBuffer);
+ assertThat(readPfd.getStatSize()).isEqualTo(writeBuffer.length);
+ } finally {
+ file.delete();
+ }
+ }
+
+ @Test
public void testContentResolverDelete() throws Exception {
String displayName = "content_resolver_delete.jpg";
File file = new File(DCIM_DIR, displayName);
@@ -1819,7 +1851,11 @@
* underlying file on disk but may be derived from different mount points and in that case
* have separate VFS caches.
*/
- private void assertRWR(FileDescriptor readFd, FileDescriptor writeFd) throws Exception {
+ private void assertRWR(ParcelFileDescriptor readPfd, ParcelFileDescriptor writePfd)
+ throws Exception {
+ FileDescriptor readFd = readPfd.getFileDescriptor();
+ FileDescriptor writeFd = writePfd.getFileDescriptor();
+
byte[] readBuffer = new byte[10];
byte[] writeBuffer = new byte[10];
Arrays.fill(writeBuffer, (byte) 1);
@@ -1840,6 +1876,7 @@
// Assert that the last write is indeed visible via readFd
assertThat(readBuffer).isEqualTo(writeBuffer);
+ assertThat(readPfd.getStatSize()).isEqualTo(writePfd.getStatSize());
}
private void assertLowerFsFd(ParcelFileDescriptor pfd) throws Exception {
diff --git a/tests/res/raw/test_m4a.m4a b/tests/res/raw/test_m4a.m4a
new file mode 100644
index 0000000..977e479
--- /dev/null
+++ b/tests/res/raw/test_m4a.m4a
Binary files differ
diff --git a/tests/src/com/android/providers/media/PermissionActivityTest.java b/tests/src/com/android/providers/media/PermissionActivityTest.java
index e751a90..7bbce62 100644
--- a/tests/src/com/android/providers/media/PermissionActivityTest.java
+++ b/tests/src/com/android/providers/media/PermissionActivityTest.java
@@ -19,15 +19,22 @@
import android.app.Instrumentation;
import android.content.ClipData;
import android.content.ContentValues;
+import android.content.Context;
import android.content.Intent;
+import android.net.Uri;
+import android.os.Environment;
import android.provider.MediaStore;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.providers.media.scan.MediaScannerTest;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
+
/**
* We already have solid coverage of this logic in {@code CtsProviderTestCases},
* but the coverage system currently doesn't measure that, so we add the bare
@@ -45,11 +52,18 @@
activity.startActivityForResult(createIntent(), 42);
}
- private static Intent createIntent() {
+ private static Intent createIntent() throws Exception {
+ final Context context = InstrumentationRegistry.getContext();
+
+ final File dir = Environment
+ .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
+ final File file = MediaScannerTest.stage(R.raw.test_image,
+ new File(dir, "test" + System.nanoTime() + ".jpg"));
+ final Uri uri = MediaStore.scanFile(context.getContentResolver(), file);
+
final Intent intent = new Intent(MediaStore.CREATE_WRITE_REQUEST_CALL, null,
- InstrumentationRegistry.getContext(), PermissionActivity.class);
- intent.putExtra(MediaStore.EXTRA_CLIP_DATA, ClipData.newRawUri("",
- MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY, 42)));
+ context, PermissionActivity.class);
+ intent.putExtra(MediaStore.EXTRA_CLIP_DATA, ClipData.newRawUri("", uri));
intent.putExtra(MediaStore.EXTRA_CONTENT_VALUES, new ContentValues());
return intent;
}
diff --git a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
index 336741c..e94d599 100644
--- a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
@@ -719,6 +719,25 @@
}
}
+ /**
+ * Verify a narrow exception where we allow an {@code mp4} video file on
+ * disk to be indexed as an {@code m4a} audio file.
+ */
+ @Test
+ public void testScan_148316354() throws Exception {
+ final File file = new File(mDir, "148316354.mp4");
+ stage(R.raw.test_m4a, file);
+
+ final Uri uri = mModern.scanFile(file, REASON_UNKNOWN);
+ try (Cursor cursor = mIsolatedResolver
+ .query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, null)) {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals("audio/mp4",
+ cursor.getString(cursor.getColumnIndex(MediaColumns.MIME_TYPE)));
+ }
+ }
+
@Test
public void testAlbumArtPattern() throws Exception {
for (String path : new String[] {
diff --git a/tests/src/com/android/providers/media/util/FileUtilsTest.java b/tests/src/com/android/providers/media/util/FileUtilsTest.java
index 35f789e..da8ddce 100644
--- a/tests/src/com/android/providers/media/util/FileUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/FileUtilsTest.java
@@ -51,6 +51,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -61,6 +62,9 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.google.common.collect.Range;
+import com.google.common.truth.Truth;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -548,6 +552,63 @@
assertEquals("/", values.get(MediaColumns.RELATIVE_PATH));
}
+ @Test
+ public void testComputeDateExpires_None() throws Exception {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.DATE_EXPIRES, 1577836800L);
+
+ FileUtils.computeDateExpires(values);
+ assertFalse(values.containsKey(MediaColumns.DATE_EXPIRES));
+ }
+
+ @Test
+ public void testComputeDateExpires_Pending_Set() throws Exception {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.IS_PENDING, 1);
+ values.put(MediaColumns.DATE_EXPIRES, 1577836800L);
+
+ FileUtils.computeDateExpires(values);
+ final long target = (System.currentTimeMillis()
+ + FileUtils.DEFAULT_DURATION_PENDING) / 1_000;
+ Truth.assertThat(values.getAsLong(MediaColumns.DATE_EXPIRES))
+ .isIn(Range.closed(target - 5, target + 5));
+ }
+
+ @Test
+ public void testComputeDateExpires_Pending_Clear() throws Exception {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.IS_PENDING, 0);
+ values.put(MediaColumns.DATE_EXPIRES, 1577836800L);
+
+ FileUtils.computeDateExpires(values);
+ assertTrue(values.containsKey(MediaColumns.DATE_EXPIRES));
+ assertNull(values.get(MediaColumns.DATE_EXPIRES));
+ }
+
+ @Test
+ public void testComputeDateExpires_Trashed_Set() throws Exception {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.IS_TRASHED, 1);
+ values.put(MediaColumns.DATE_EXPIRES, 1577836800L);
+
+ FileUtils.computeDateExpires(values);
+ final long target = (System.currentTimeMillis()
+ + FileUtils.DEFAULT_DURATION_TRASHED) / 1_000;
+ Truth.assertThat(values.getAsLong(MediaColumns.DATE_EXPIRES))
+ .isIn(Range.closed(target - 5, target + 5));
+ }
+
+ @Test
+ public void testComputeDateExpires_Trashed_Clear() throws Exception {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.IS_TRASHED, 0);
+ values.put(MediaColumns.DATE_EXPIRES, 1577836800L);
+
+ FileUtils.computeDateExpires(values);
+ assertTrue(values.containsKey(MediaColumns.DATE_EXPIRES));
+ assertNull(values.get(MediaColumns.DATE_EXPIRES));
+ }
+
private static File touch(File dir, String name) throws IOException {
final File res = new File(dir, name);
res.createNewFile();