Merge "release unique_fd when passing to FUSE." into rvc-dev
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 1d4aeae..4caad75 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -378,6 +378,25 @@
return std::regex_match(path, PATTERN_OWNED_PATH);
}
+static void invalidate_case_insensitive_dentry_matches(struct fuse* fuse, node* parent,
+ const string& name) {
+ vector<string> children = parent->MatchChildrenCaseInsensitive(name);
+ if (children.empty() || (children.size() == 1 && children[0] == name)) {
+ return;
+ }
+
+ fuse_ino_t parent_ino = fuse->ToInode(parent);
+ std::thread t([=]() {
+ for (const string& child_name : children) {
+ if (fuse_lowlevel_notify_inval_entry(fuse->se, parent_ino, child_name.c_str(),
+ child_name.size())) {
+ LOG(ERROR) << "Failed to invalidate dentry " << child_name;
+ }
+ }
+ });
+ t.detach();
+}
+
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);
@@ -396,6 +415,9 @@
}
TRACE_NODE(node);
+
+ invalidate_case_insensitive_dentry_matches(fuse, parent, name);
+
// 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
diff --git a/jni/node-inl.h b/jni/node-inl.h
index 4c523e6..e9b63bb 100644
--- a/jni/node-inl.h
+++ b/jni/node-inl.h
@@ -221,6 +221,22 @@
return parent_;
}
+ std::vector<std::string> MatchChildrenCaseInsensitive(const std::string& name) const {
+ std::lock_guard<std::recursive_mutex> guard(*lock_);
+
+ const char* name_char = name.c_str();
+ std::vector<std::string> matches;
+
+ for (node* child : children_) {
+ std::string child_name = child->GetName();
+ if (!strcasecmp(name_char, child_name.c_str())) {
+ matches.push_back(child_name);
+ }
+ }
+
+ return matches;
+ }
+
inline void AddHandle(handle* h) {
std::lock_guard<std::recursive_mutex> guard(*lock_);
handles_.emplace_back(std::unique_ptr<handle>(h));
diff --git a/jni/node_test.cpp b/jni/node_test.cpp
index 027181d..901704d 100644
--- a/jni/node_test.cpp
+++ b/jni/node_test.cpp
@@ -228,3 +228,17 @@
new handle(-1, new mediaprovider::fuse::RedactionInfo, true /* cached */));
EXPECT_DEATH(node->DestroyHandle(h2.get()), "");
}
+
+TEST_F(NodeTest, CaseInsensitive) {
+ unique_node_ptr parent = CreateNode(nullptr, "/path");
+ unique_node_ptr lower_child = CreateNode(parent.get(), "child");
+ unique_node_ptr upper_child = CreateNode(parent.get(), "CHILD");
+ unique_node_ptr mixed_child = CreateNode(parent.get(), "cHiLd");
+
+ std::vector<std::string> children = parent->MatchChildrenCaseInsensitive("ChIld");
+
+ ASSERT_EQ(3, children.size());
+ ASSERT_EQ("child", children[0]);
+ ASSERT_EQ("CHILD", children[1]);
+ ASSERT_EQ("cHiLd", children[2]);
+}
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 74d70be..5c4d315 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -23,7 +23,7 @@
<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>
+ <string name="root_audio" msgid="3505830755201326018">"صوتيات"</string>
<string name="root_documents" msgid="3829103301363849237">"المستندات"</string>
<string name="permission_required" msgid="1460820436132943754">"مطلوب الحصول على إذن لتعديل هذا العنصر أو حذفه."</string>
<string name="permission_required_action" msgid="706370952366113539">"متابعة"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 4279f89..12ec655 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -21,7 +21,7 @@
<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_images" msgid="5861633549189045666">"इमेज"</string>
<string name="root_videos" msgid="8792703517064649453">"वीडियो"</string>
<string name="root_audio" msgid="3505830755201326018">"ऑडियो"</string>
<string name="root_documents" msgid="3829103301363849237">"दस्तावेज़"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 1efefcd..2eed59d 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -21,7 +21,7 @@
<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_images" msgid="5861633549189045666">"ਚਿੱਤਰ"</string>
<string name="root_videos" msgid="8792703517064649453">"ਵੀਡੀਓ"</string>
<string name="root_audio" msgid="3505830755201326018">" ਆਡੀਓ"</string>
<string name="root_documents" msgid="3829103301363849237">"ਦਸਤਾਵੇਜ਼"</string>
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index 90a9ed1..5bd8c80 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -33,6 +33,7 @@
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteImages;
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteStorage;
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteVideo;
+import static com.android.providers.media.util.PermissionUtils.generateAppOpMessage;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -58,7 +59,9 @@
public final int pid;
public final int uid;
public final String packageNameUnchecked;
+ // Info used for logging permission checks
public @Nullable String attributionTag;
+ private @Nullable String opDescription;
private LocalCallingIdentity(Context context, int pid, int uid, String packageNameUnchecked,
@Nullable String attributionTag) {
@@ -67,6 +70,7 @@
this.uid = uid;
this.packageNameUnchecked = packageNameUnchecked;
this.attributionTag = attributionTag;
+ this.opDescription = null;
}
/**
@@ -212,7 +216,7 @@
public boolean hasPermission(int permission) {
if ((hasPermissionResolved & permission) == 0) {
- if (hasPermissionInternal(permission)) {
+ if (hasPermissionInternal(permission, opDescription)) {
hasPermission |= permission;
}
hasPermissionResolved |= permission;
@@ -220,7 +224,7 @@
return (hasPermission & permission) != 0;
}
- private boolean hasPermissionInternal(int permission) {
+ private boolean hasPermissionInternal(int permission, @Nullable String description) {
// While we're here, enforce any broad user-level restrictions
if ((uid == Process.SHELL_UID) && context.getSystemService(UserManager.class)
.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
@@ -242,19 +246,26 @@
case PERMISSION_IS_REDACTION_NEEDED:
return isRedactionNeededInternal();
case PERMISSION_READ_AUDIO:
- return checkPermissionReadAudio(context, pid, uid, getPackageName());
+ return checkPermissionReadAudio(context, pid, uid, getPackageName(), attributionTag,
+ generateAppOpMessage(packageName, description));
case PERMISSION_READ_VIDEO:
- return checkPermissionReadVideo(context, pid, uid, getPackageName());
+ return checkPermissionReadVideo(context, pid, uid, getPackageName(), attributionTag,
+ generateAppOpMessage(packageName, description));
case PERMISSION_READ_IMAGES:
- return checkPermissionReadImages(context, pid, uid, getPackageName());
+ return checkPermissionReadImages(context, pid, uid, getPackageName(),
+ attributionTag, generateAppOpMessage(packageName, description));
case PERMISSION_WRITE_AUDIO:
- return checkPermissionWriteAudio(context, pid, uid, getPackageName());
+ return checkPermissionWriteAudio(context, pid, uid, getPackageName(),
+ attributionTag, generateAppOpMessage(packageName, description));
case PERMISSION_WRITE_VIDEO:
- return checkPermissionWriteVideo(context, pid, uid, getPackageName());
+ return checkPermissionWriteVideo(context, pid, uid, getPackageName(),
+ attributionTag, generateAppOpMessage(packageName, description));
case PERMISSION_WRITE_IMAGES:
- return checkPermissionWriteImages(context, pid, uid, getPackageName());
+ return checkPermissionWriteImages(context, pid, uid, getPackageName(),
+ attributionTag, generateAppOpMessage(packageName, description));
case PERMISSION_MANAGE_EXTERNAL_STORAGE:
- return checkPermissionManageExternalStorage(context, pid, uid, getPackageName());
+ return checkPermissionManageExternalStorage(context, pid, uid, getPackageName(),
+ attributionTag, generateAppOpMessage(packageName, description));
default:
return false;
}
@@ -297,13 +308,15 @@
}
private boolean isLegacyWriteInternal() {
- return hasPermission(PERMISSION_IS_LEGACY_GRANTED) &&
- checkPermissionWriteStorage(context, pid, uid, getPackageName());
+ return hasPermission(PERMISSION_IS_LEGACY_GRANTED)
+ && checkPermissionWriteStorage(context, pid, uid, getPackageName(), attributionTag,
+ /*opMessage*/ null);
}
private boolean isLegacyReadInternal() {
- return hasPermission(PERMISSION_IS_LEGACY_GRANTED) &&
- checkPermissionReadStorage(context, pid, uid, getPackageName());
+ return hasPermission(PERMISSION_IS_LEGACY_GRANTED)
+ && checkPermissionReadStorage(context, pid, uid, getPackageName(), attributionTag,
+ /*opMessage*/ null);
}
/** System internals or callers holding permission have no redaction */
@@ -358,4 +371,8 @@
public long getDeletedRowId(@NonNull String path) {
return rowIdOfDeletedPaths.getOrDefault(path, UNKNOWN_ROW_ID);
}
+
+ public void setOpDescription(@Nullable String opDescription) {
+ this.opDescription = opDescription;
+ }
}
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 952f895..762dbf4 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -66,6 +66,7 @@
import static com.android.providers.media.util.Logging.LOGV;
import static com.android.providers.media.util.Logging.TAG;
import static com.android.providers.media.util.PermissionUtils.checkPermissionManageExternalStorage;
+import static com.android.providers.media.util.PermissionUtils.generateAppOpMessage;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpActiveChangedListener;
@@ -120,7 +121,6 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
-import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -407,13 +407,19 @@
private OnOpChangedListener mModeListener =
(op, packageName) -> invalidateLocalCallingIdentityCache(packageName, "op " + op);
- private LocalCallingIdentity getCachedCallingIdentityForFuse(int uid) {
+ /**
+ * Retrieves a cached calling identity or creates a new one. Also, always sets the app-op
+ * description for the calling identity.
+ */
+ private LocalCallingIdentity getCachedCallingIdentityForFuse(
+ int uid, @Nullable String opDescription) {
synchronized (mCachedCallingIdentityForFuse) {
LocalCallingIdentity ident = mCachedCallingIdentityForFuse.get(uid);
if (ident == null) {
ident = LocalCallingIdentity.fromExternal(getContext(), uid);
mCachedCallingIdentityForFuse.put(uid, ident);
}
+ ident.setOpDescription(opDescription);
return ident;
}
}
@@ -1054,7 +1060,9 @@
* to clear other apps' cache directories.
*/
static boolean hasPermissionToClearCaches(Context context, ApplicationInfo ai) {
- return checkPermissionManageExternalStorage(context, /*pid*/-1, ai.uid, ai.packageName);
+ final String opMessage = generateAppOpMessage(ai.packageName, "clear app cache");
+ return checkPermissionManageExternalStorage(context, /*pid*/ -1, ai.uid, ai.packageName,
+ /*attributionTag*/ null, opMessage);
}
/**
@@ -1272,7 +1280,7 @@
@Keep
public String[] getFilesInDirectoryForFuse(String path, int uid) {
final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid, "readdir " + path));
try {
if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -1759,8 +1767,8 @@
@Keep
public int renameForFuse(String oldPath, String newPath, int uid) {
final String errorMessage = "Rename " + oldPath + " to " + newPath + " failed. ";
- final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ final LocalCallingIdentity token = clearLocalCallingIdentity(
+ getCachedCallingIdentityForFuse(uid, "rename " + oldPath + " to " + newPath));
try {
if (isPrivatePackagePathNotOwnedByCaller(oldPath)
@@ -1843,7 +1851,8 @@
public int checkUriPermission(@NonNull Uri uri, int uid,
/* @Intent.AccessUriMode */ int modeFlags) {
final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid,
+ /*opDescription*/ null));
try {
final boolean allowHidden = isCallingPackageAllowedHidden();
@@ -2694,7 +2703,12 @@
if (mimeType != null) {
values.put(FileColumns.MIME_TYPE, mimeType);
- values.put(FileColumns.MEDIA_TYPE, MimeUtils.resolveMediaType(mimeType));
+ if (isCallingPackageSystem() && values.containsKey(FileColumns.MEDIA_TYPE)) {
+ // Leave FileColumns.MEDIA_TYPE untouched if the caller is ModernMediaScanner and
+ // FileColumns.MEDIA_TYPE is already populated.
+ } else{
+ values.put(FileColumns.MEDIA_TYPE, MimeUtils.resolveMediaType(mimeType));
+ }
} else {
values.put(FileColumns.MEDIA_TYPE, mediaType);
}
@@ -5797,8 +5811,8 @@
return getRedactionRanges(file).redactionRanges;
}
- final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ final LocalCallingIdentity token = clearLocalCallingIdentity(
+ getCachedCallingIdentityForFuse(uid, "read metadata from " + path));
long[] res = new long[0];
try {
@@ -5918,9 +5932,8 @@
*/
@Keep
public int isOpenAllowedForFuse(String path, int uid, boolean forWrite) {
- final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
-
+ final LocalCallingIdentity token = clearLocalCallingIdentity(
+ getCachedCallingIdentityForFuse(uid, (forWrite ? "write " : "read ") + path));
try {
if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -6111,7 +6124,7 @@
@Keep
public int insertFileIfNecessaryForFuse(@NonNull String path, int uid) {
final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid, "create " + path));
try {
if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -6181,7 +6194,7 @@
@Keep
public int deleteFileForFuse(@NonNull String path, int uid) throws IOException {
final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid, "delete " + path));
try {
if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -6248,8 +6261,8 @@
@Keep
public int isDirectoryCreationOrDeletionAllowedForFuse(
@NonNull String path, int uid, boolean forCreate) {
- final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ final LocalCallingIdentity token = clearLocalCallingIdentity(
+ getCachedCallingIdentityForFuse(uid, (forCreate ? "mkdir " : "rmdir ") + path));
try {
// App dirs are not indexed, so we don't create an entry for the file.
@@ -6301,8 +6314,8 @@
*/
@Keep
public int isOpendirAllowedForFuse(@NonNull String path, int uid) {
- final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ final LocalCallingIdentity token = clearLocalCallingIdentity(
+ getCachedCallingIdentityForFuse(uid, "directory access " + path));
try {
if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -6328,7 +6341,8 @@
@Keep
public boolean isUidForPackageForFuse(@NonNull String packageName, int uid) {
final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid,
+ /*opDescription*/ null));
try {
return isCallingIdentitySharedPackageName(packageName);
} finally {
diff --git a/src/com/android/providers/media/util/PermissionUtils.java b/src/com/android/providers/media/util/PermissionUtils.java
index 387ced0..e09feca 100644
--- a/src/com/android/providers/media/util/PermissionUtils.java
+++ b/src/com/android/providers/media/util/PermissionUtils.java
@@ -17,8 +17,6 @@
package com.android.providers.media.util;
import static android.Manifest.permission.BACKUP;
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
import static android.app.AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE;
@@ -33,6 +31,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.provider.MediaStore;
@@ -44,8 +43,8 @@
private static volatile int sLegacyMediaProviderUid = -1;
- public static boolean checkPermissionSystem(Context context,
- int pid, int uid, String packageName) {
+ public static boolean checkPermissionSystem(
+ @NonNull 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()
@@ -53,87 +52,124 @@
|| isLegacyMediaProvider(context, uid);
}
- public static boolean checkPermissionBackup(Context context, int pid, int uid) {
+ public static boolean checkPermissionBackup(@NonNull Context context, int pid, int uid) {
return context.checkPermission(BACKUP, pid, uid) == PERMISSION_GRANTED;
}
- public static boolean checkPermissionManageExternalStorage(Context context, int pid, int uid,
- String packageName) {
- return hasAppOpPermission(context, pid, uid, packageName, OPSTR_MANAGE_EXTERNAL_STORAGE);
+ public static boolean checkPermissionManageExternalStorage(@NonNull Context context, int pid,
+ int uid, @NonNull String packageName, @Nullable String attributionTag,
+ @Nullable String opMessage) {
+ return noteAppOpPermission(context, pid, uid, packageName, OPSTR_MANAGE_EXTERNAL_STORAGE,
+ attributionTag, opMessage);
}
- public static boolean checkPermissionWriteStorage(Context context,
- int pid, int uid, String packageName) {
- return checkPermissionAndAppOp(context, pid,
- uid, packageName, WRITE_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE);
+ public static boolean checkPermissionWriteStorage(@NonNull Context context, int pid, int uid,
+ @NonNull String packageName, @Nullable String attributionTag,
+ @Nullable String opMessage) {
+ return noteAppOpPermission(context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE,
+ attributionTag, opMessage);
}
- public static boolean checkPermissionReadStorage(Context context,
- int pid, int uid, String packageName) {
- return checkPermissionAndAppOp(context, pid,
- uid, packageName, READ_EXTERNAL_STORAGE, OPSTR_READ_EXTERNAL_STORAGE);
+ public static boolean checkPermissionReadStorage(@NonNull Context context, int pid, int uid,
+ @NonNull String packageName, @Nullable String attributionTag,
+ @Nullable String opMessage) {
+ return noteAppOpPermission(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE,
+ attributionTag, opMessage);
}
- public static boolean checkIsLegacyStorageGranted(Context context, int uid,
- String packageName) {
+ public static boolean checkIsLegacyStorageGranted(
+ @NonNull Context context, int uid, String packageName) {
return context.getSystemService(AppOpsManager.class)
.unsafeCheckOp(OPSTR_LEGACY_STORAGE, uid, packageName) == MODE_ALLOWED;
}
- public static boolean checkPermissionReadAudio(Context context,
- int pid, int uid, String packageName) {
- if (!checkPermissionAndAppOp(context, pid, uid, packageName,
- READ_EXTERNAL_STORAGE, OPSTR_READ_EXTERNAL_STORAGE)) return false;
- return noteAppOpAllowingLegacy(context, pid, uid, packageName,
- OPSTR_READ_MEDIA_AUDIO);
- }
-
- public static boolean checkPermissionWriteAudio(Context context,
- int pid, int uid, String packageName) {
- if (!checkPermissionAndAppOpAllowingNonLegacy(context, pid, uid, packageName,
- WRITE_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE)) return false;
- return noteAppOpAllowingLegacy(context, pid, uid, packageName,
- OPSTR_WRITE_MEDIA_AUDIO);
- }
-
- public static boolean checkPermissionReadVideo(Context context,
- int pid, int uid, String packageName) {
- if (!checkPermissionAndAppOp(context, pid, uid, packageName,
- READ_EXTERNAL_STORAGE, OPSTR_READ_EXTERNAL_STORAGE)) return false;
- return noteAppOpAllowingLegacy(context, pid, uid, packageName,
- OPSTR_READ_MEDIA_VIDEO);
- }
-
- public static boolean checkPermissionWriteVideo(Context context,
- int pid, int uid, String packageName) {
- if (!checkPermissionAndAppOpAllowingNonLegacy(context, pid, uid, packageName,
- WRITE_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE)) return false;
- return noteAppOpAllowingLegacy(context, pid, uid, packageName,
- OPSTR_WRITE_MEDIA_VIDEO);
- }
-
- public static boolean checkPermissionReadImages(Context context,
- int pid, int uid, String packageName) {
- if (!checkPermissionAndAppOp(context, pid, uid, packageName,
- READ_EXTERNAL_STORAGE, OPSTR_READ_EXTERNAL_STORAGE)) return false;
- return noteAppOpAllowingLegacy(context, pid, uid, packageName,
- OPSTR_READ_MEDIA_IMAGES);
- }
-
- public static boolean checkPermissionWriteImages(Context context,
- int pid, int uid, String packageName) {
- if (!checkPermissionAndAppOpAllowingNonLegacy(context, pid, uid, packageName,
- WRITE_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE)) return false;
- return noteAppOpAllowingLegacy(context, pid, uid, packageName,
- OPSTR_WRITE_MEDIA_IMAGES);
- }
-
- private static boolean checkPermissionAndAppOp(Context context,
- int pid, int uid, String packageName, String permission, String op) {
- if (context.checkPermission(permission, pid, uid) != PERMISSION_GRANTED) {
+ public static boolean checkPermissionReadAudio(@NonNull Context context, int pid, int uid,
+ @NonNull String packageName, @Nullable String attributionTag,
+ @Nullable String opMessage) {
+ if (!checkPermissionAppOp(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE)) {
return false;
}
+ return noteAppOpAllowingLegacy(
+ context, pid, uid, packageName, OPSTR_READ_MEDIA_AUDIO, attributionTag, opMessage);
+ }
+ public static boolean checkPermissionWriteAudio(@NonNull Context context, int pid, int uid,
+ @NonNull String packageName, @Nullable String attributionTag,
+ @Nullable String opMessage) {
+ if (!checkPermissionAppOpAllowingNonLegacy(
+ context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE)) {
+ return false;
+ }
+ return noteAppOpAllowingLegacy(
+ context, pid, uid, packageName, OPSTR_WRITE_MEDIA_AUDIO, attributionTag, opMessage);
+ }
+
+ public static boolean checkPermissionReadVideo(@NonNull Context context, int pid, int uid,
+ @NonNull String packageName, @Nullable String attributionTag,
+ @Nullable String opMessage) {
+ if (!checkPermissionAppOp(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE)) {
+ return false;
+ }
+ return noteAppOpAllowingLegacy(
+ context, pid, uid, packageName, OPSTR_READ_MEDIA_VIDEO, attributionTag, opMessage);
+ }
+
+ public static boolean checkPermissionWriteVideo(@NonNull Context context, int pid, int uid,
+ @NonNull String packageName, @Nullable String attributionTag,
+ @Nullable String opMessage) {
+ if (!checkPermissionAppOpAllowingNonLegacy(
+ context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE)) {
+ return false;
+ }
+ return noteAppOpAllowingLegacy(
+ context, pid, uid, packageName, OPSTR_WRITE_MEDIA_VIDEO, attributionTag, opMessage);
+ }
+
+ public static boolean checkPermissionReadImages(@NonNull Context context, int pid, int uid,
+ @NonNull String packageName, @Nullable String attributionTag,
+ @Nullable String opMessage) {
+ if (!checkPermissionAppOp(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE)) {
+ return false;
+ }
+ return noteAppOpAllowingLegacy(
+ context, pid, uid, packageName, OPSTR_READ_MEDIA_IMAGES, attributionTag, opMessage);
+ }
+
+ public static boolean checkPermissionWriteImages(@NonNull Context context, int pid, int uid,
+ @NonNull String packageName, @Nullable String attributionTag,
+ @Nullable String opMessage) {
+ if (!checkPermissionAppOpAllowingNonLegacy(
+ context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE)) {
+ return false;
+ }
+ return noteAppOpAllowingLegacy(context, pid, uid, packageName, OPSTR_WRITE_MEDIA_IMAGES,
+ attributionTag, opMessage);
+ }
+
+ /**
+ * Generates a message to be used with the different {@link AppOpsManager#noteOp} variations.
+ * If the supplied description is {@code null}, the returned message will be {@code null}.
+ */
+ public static String generateAppOpMessage(
+ @NonNull String packageName, @Nullable String description) {
+ if (description == null) {
+ return null;
+ }
+ return "Package: " + packageName + ". Description: " + description + ".";
+ }
+
+ /**
+ * Checks the permission associated with the given app-op, if it's not granted, returns false.
+ * Else, checks the app-op and returns true iff it's {@link AppOpsManager#MODE_ALLOWED}.
+ * The permission is retrieved from {@link AppOpsManager#opToPermission(String)}.
+ */
+ private static boolean checkPermissionAppOp(@NonNull Context context, int pid, int uid,
+ @NonNull String packageName, @NonNull String op) {
+ final String permission = AppOpsManager.opToPermission(op);
+ if (permission != null
+ && context.checkPermission(permission, pid, uid) != PERMISSION_GRANTED) {
+ return false;
+ }
final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
try {
appOps.checkPackage(uid, packageName);
@@ -155,50 +191,57 @@
}
/**
- * Checks if the given package has the given {@code permission} and {@code op}, but allows it
- * to bypass the permission and app-op check if it's NOT a legacy app, i.e. doesn't hold
- * {@link AppOpsManager#OPSTR_LEGACY_STORAGE}. This is useful for deprecated permissions and/or
- * app-ops, like {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
- * @see #checkPermissionAndAppOp
+ * Similar to {@link #checkPermissionAppOp(Context, int, int, String, String)}, but also returns
+ * true for non-legacy apps.
+ * @see #checkPermissionAppOp
*/
- private static boolean checkPermissionAndAppOpAllowingNonLegacy(Context context,
- int pid, int uid, String packageName, String permission, String op) {
+ private static boolean checkPermissionAppOpAllowingNonLegacy(@NonNull Context context, int pid,
+ int uid, @NonNull String packageName, @NonNull String op) {
final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
- try {
- appOps.checkPackage(uid, packageName);
- } catch (SecurityException e) {
- return false;
- }
+
// Allowing non legacy apps to bypass this check
if (appOps.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, uid,
packageName) != AppOpsManager.MODE_ALLOWED) return true;
// Seems like it's a legacy app, so it has to pass the permission and app-op check
- return checkPermissionAndAppOp(context, pid, uid, packageName, permission, op);
+ return checkPermissionAppOp(context, pid, uid, packageName, op);
}
/**
- * Checks if calling app is allowed the app-op. If its app-op mode is
+ * Notes app-op for the callings package. If its app-op mode is
* {@link AppOpsManager#MODE_DEFAULT} then it falls back to checking the appropriate permission
* for the app-op. The permission is retrieved from
* {@link AppOpsManager#opToPermission(String)}.
*/
- private static boolean hasAppOpPermission(@NonNull Context context, int pid, int uid,
- @NonNull String packageName, @NonNull String op) {
+ private static boolean noteAppOpPermission(@NonNull Context context, int pid, int uid,
+ @NonNull String packageName, @NonNull String op, @Nullable String attributionTag,
+ @Nullable String opMessage) {
final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
- final int mode = appOps.noteOpNoThrow(op, uid, packageName, null, null);
- if (mode == AppOpsManager.MODE_DEFAULT) {
- final String permission = AppOpsManager.opToPermission(op);
- return permission != null
- && context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED;
+ final int mode = appOps.noteOpNoThrow(op, uid, packageName, attributionTag, opMessage);
+ switch (mode) {
+ case AppOpsManager.MODE_ALLOWED:
+ return true;
+ case AppOpsManager.MODE_DEFAULT:
+ final String permission = AppOpsManager.opToPermission(op);
+ return permission != null
+ && context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED;
+ case AppOpsManager.MODE_IGNORED:
+ case AppOpsManager.MODE_ERRORED:
+ return false;
+ default:
+ throw new IllegalStateException(op + " has unknown mode " + mode);
}
- return mode == AppOpsManager.MODE_ALLOWED;
}
- private static boolean noteAppOpAllowingLegacy(Context context,
- int pid, int uid, String packageName, String op) {
+ /**
+ * Similar to {@link #noteAppOpPermission(Context, int, int, String, String, String, String)},
+ * but also returns true for legacy apps.
+ */
+ private static boolean noteAppOpAllowingLegacy(@NonNull Context context, int pid, int uid,
+ @NonNull String packageName, @NonNull String op, @Nullable String attributionTag,
+ @Nullable String opMessage) {
final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
- final int mode = appOps.noteOpNoThrow(op, uid, packageName);
+ final int mode = appOps.noteOpNoThrow(op, uid, packageName, attributionTag, opMessage);
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
return true;
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 7309cf3..77da892 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
@@ -162,6 +162,13 @@
}
@Test
+ public void testCaseInsensitivity() throws Exception {
+ runDeviceTest("testCreateLowerCaseDeleteUpperCase");
+ runDeviceTest("testCreateUpperCaseDeleteLowerCase");
+ runDeviceTest("testCreateMixedCaseDeleteDifferentMixedCase");
+ }
+
+ @Test
public void testCallingIdentityCacheInvalidation() throws Exception {
// General IO access
runDeviceTest("testReadStorageInvalidation");
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 87f7641..c879a6c 100644
--- a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
+++ b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
@@ -955,6 +955,46 @@
}
@Test
+ public void testCreateLowerCaseDeleteUpperCase() throws Exception {
+ File upperCase = new File(DOWNLOAD_DIR, "CREATE_LOWER_DELETE_UPPER");
+ File lowerCase = new File(DOWNLOAD_DIR, "create_lower_delete_upper");
+
+ createDeleteCreate(lowerCase, upperCase);
+ }
+
+ @Test
+ public void testCreateUpperCaseDeleteLowerCase() throws Exception {
+ File upperCase = new File(DOWNLOAD_DIR, "CREATE_UPPER_DELETE_LOWER");
+ File lowerCase = new File(DOWNLOAD_DIR, "create_upper_delete_lower");
+
+ createDeleteCreate(upperCase, lowerCase);
+ }
+
+ @Test
+ public void testCreateMixedCaseDeleteDifferentMixedCase() throws Exception {
+ File mixedCase1 = new File(DOWNLOAD_DIR, "CrEaTe_MiXeD_dElEtE_mIxEd");
+ File mixedCase2 = new File(DOWNLOAD_DIR, "cReAtE_mIxEd_DeLeTe_MiXeD");
+
+ createDeleteCreate(mixedCase1, mixedCase2);
+ }
+
+ private void createDeleteCreate(File create, File delete) throws Exception {
+ try {
+ assertThat(create.createNewFile()).isTrue();
+ Thread.sleep(5);
+
+ assertThat(delete.delete()).isTrue();
+ Thread.sleep(5);
+
+ assertThat(create.createNewFile()).isTrue();
+ Thread.sleep(5);
+ } finally {
+ create.delete();
+ create.delete();
+ }
+ }
+
+ @Test
public void testReadStorageInvalidation() throws Exception {
testAppOpInvalidation(TEST_APP_C, new File(DCIM_DIR, "read_storage.jpg"),
Manifest.permission.READ_EXTERNAL_STORAGE,
diff --git a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
index 425bb67..d581921 100644
--- a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
@@ -59,16 +59,17 @@
assertTrue(checkPermissionSystem(context, pid, uid, packageName));
assertFalse(checkPermissionBackup(context, pid, uid));
- assertFalse(checkPermissionManageExternalStorage(context, pid, uid, packageName));
+ assertFalse(
+ checkPermissionManageExternalStorage(context, pid, uid, packageName, null, null));
- assertTrue(checkPermissionReadStorage(context, pid, uid, packageName));
- assertTrue(checkPermissionWriteStorage(context, pid, uid, packageName));
+ assertTrue(checkPermissionReadStorage(context, pid, uid, packageName, null, null));
+ assertTrue(checkPermissionWriteStorage(context, pid, uid, packageName, null, null));
- assertTrue(checkPermissionReadAudio(context, pid, uid, packageName));
- assertFalse(checkPermissionWriteAudio(context, pid, uid, packageName));
- assertTrue(checkPermissionReadVideo(context, pid, uid, packageName));
- assertFalse(checkPermissionWriteVideo(context, pid, uid, packageName));
- assertTrue(checkPermissionReadImages(context, pid, uid, packageName));
- assertFalse(checkPermissionWriteImages(context, pid, uid, packageName));
+ assertTrue(checkPermissionReadAudio(context, pid, uid, packageName, null, null));
+ assertFalse(checkPermissionWriteAudio(context, pid, uid, packageName, null, null));
+ assertTrue(checkPermissionReadVideo(context, pid, uid, packageName, null, null));
+ assertFalse(checkPermissionWriteVideo(context, pid, uid, packageName, null, null));
+ assertTrue(checkPermissionReadImages(context, pid, uid, packageName, null, null));
+ assertFalse(checkPermissionWriteImages(context, pid, uid, packageName, null, null));
}
}