Merge "Introduce user cache and resolve cross-profile access correctly." into sc-dev
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index d19627c..60b4e0e 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -63,6 +63,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.providers.media.util.LongArray;
+import com.android.providers.media.util.UserCache;
import java.util.Locale;
@@ -104,7 +105,8 @@
private static final long UNKNOWN_ROW_ID = -1;
- public static LocalCallingIdentity fromBinder(Context context, ContentProvider provider) {
+ public static LocalCallingIdentity fromBinder(Context context, ContentProvider provider,
+ UserCache userCache) {
String callingPackage = provider.getCallingPackageUnchecked();
int binderUid = Binder.getCallingUid();
if (callingPackage == null) {
@@ -128,6 +130,12 @@
} else {
user = UserHandle.getUserHandleForUid(binderUid);
}
+ if (!userCache.userSharesMediaWithParent(user)) {
+ // It's possible that we got a cross-profile intent from a regular work profile; in
+ // that case, the request was explicitly targeted at the media database of the owner
+ // user; reflect that here.
+ user = Process.myUserHandle();
+ }
return new LocalCallingIdentity(context, Binder.getCallingPid(), binderUid,
user, callingPackage, callingAttributionTag);
}
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 6b410c4..e13d874 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -210,6 +210,7 @@
import com.android.providers.media.util.MimeUtils;
import com.android.providers.media.util.PermissionUtils;
import com.android.providers.media.util.SQLiteQueryBuilder;
+import com.android.providers.media.util.UserCache;
import com.android.providers.media.util.XmpInterface;
import com.google.common.hash.Hashing;
@@ -442,6 +443,7 @@
private DevicePolicyManager mDevicePolicyManager;
private UserManager mUserManager;
+ private UserCache mUserCache;
private VolumeCache mVolumeCache;
private int mExternalStorageAuthorityAppId;
@@ -519,7 +521,7 @@
final LocalCallingIdentity cached = mCachedCallingIdentity
.get(Binder.getCallingUid());
return (cached != null) ? cached
- : LocalCallingIdentity.fromBinder(getContext(), this);
+ : LocalCallingIdentity.fromBinder(getContext(), this, mUserCache);
}
});
@@ -896,6 +898,8 @@
public boolean onCreate() {
final Context context = getContext();
+ mUserCache = new UserCache(context);
+
// Shift call statistics back to the original caller
Binder.setProxyTransactListener(mTransactListener);
@@ -904,7 +908,7 @@
mPackageManager = context.getPackageManager();
mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
mUserManager = context.getSystemService(UserManager.class);
- mVolumeCache = new VolumeCache(context);
+ mVolumeCache = new VolumeCache(context, mUserCache);
// Reasonable thumbnail size is half of the smallest screen edge width
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
@@ -9214,8 +9218,8 @@
final String volumeName = resolveVolumeName(uri);
synchronized (mAttachedVolumes) {
boolean volumeAttached = false;
+ UserHandle user = mCallingIdentity.get().getUser();
for (MediaVolume vol : mAttachedVolumes) {
- UserHandle user = mCallingIdentity.get().getUser();
if (vol.getName().equals(volumeName) && vol.isVisibleToUser(user)) {
volumeAttached = true;
break;
diff --git a/src/com/android/providers/media/VolumeCache.java b/src/com/android/providers/media/VolumeCache.java
index d94ddfa..cbcd230 100644
--- a/src/com/android/providers/media/VolumeCache.java
+++ b/src/com/android/providers/media/VolumeCache.java
@@ -19,7 +19,6 @@
import static com.android.providers.media.util.Logging.TAG;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
@@ -35,6 +34,7 @@
import androidx.annotation.NonNull;
import com.android.providers.media.util.FileUtils;
+import com.android.providers.media.util.UserCache;
import java.io.File;
import java.io.FileNotFoundException;
@@ -54,6 +54,7 @@
private final Object mLock = new Object();
private final UserManager mUserManager;
+ private final UserCache mUserCache;
@GuardedBy("mLock")
private final ArrayList<MediaVolume> mExternalVolumes = new ArrayList<>();
@@ -64,12 +65,10 @@
@GuardedBy("mLock")
private Collection<File> mCachedInternalScanPaths;
- @GuardedBy("mLock")
- private final LongSparseArray<Context> mUserContexts = new LongSparseArray<>();
-
- public VolumeCache(Context context) {
+ public VolumeCache(Context context, UserCache userCache) {
mContext = context;
mUserManager = context.getSystemService(UserManager.class);
+ mUserCache = userCache;
}
public @NonNull List<MediaVolume> getExternalVolumes() {
@@ -112,7 +111,7 @@
// Try again by using FileUtils below
}
- final Context userContext = getContextForUser(user);
+ final Context userContext = mUserCache.getContextForUser(user);
return FileUtils.getVolumePath(userContext, volumeName);
}
}
@@ -134,7 +133,7 @@
}
// Nothing found above; let's ask directly
- final Context userContext = getContextForUser(user);
+ final Context userContext = mUserCache.getContextForUser(user);
final Collection<File> res = FileUtils.getVolumeScanPaths(userContext, volumeName);
return res;
@@ -167,22 +166,6 @@
return volume.getId();
}
- private @NonNull Context getContextForUser(UserHandle user) {
- synchronized (mLock) {
- Context userContext = mUserContexts.get(user.getIdentifier());
- if (userContext != null) {
- return userContext;
- }
- try {
- userContext = mContext.createPackageContextAsUser("system", 0, user);
- mUserContexts.put(user.getIdentifier(), userContext);
- return userContext;
- } catch (PackageManager.NameNotFoundException e) {
- throw new RuntimeException("Failed to create context for user " + user, e);
- }
- }
- }
-
@GuardedBy("mLock")
private void updateExternalVolumesForUserLocked(Context userContext) {
final StorageManager sm = userContext.getSystemService(StorageManager.class);
@@ -210,17 +193,10 @@
Log.wtf(TAG, "Failed to update volume " + MediaStore.VOLUME_INTERNAL,e );
}
mExternalVolumes.clear();
- for (UserHandle profile : mUserManager.getEnabledProfiles()) {
- if (profile.equals(mContext.getUser())) {
- // Volumes of the user id that MediaProvider runs as
- updateExternalVolumesForUserLocked(mContext);
- } else {
- Context userContext = getContextForUser(profile);
- if (userContext.getSystemService(UserManager.class).isMediaSharedWithParent()) {
- // This profile shares media with its parent - add its volumes, too
- updateExternalVolumesForUserLocked(userContext);
- }
- }
+ List<UserHandle> users = mUserCache.updateAndGetUsers();
+ for (UserHandle user : users) {
+ Context userContext = mUserCache.getContextForUser(user);
+ updateExternalVolumesForUserLocked(userContext);
}
}
}
diff --git a/src/com/android/providers/media/util/UserCache.java b/src/com/android/providers/media/util/UserCache.java
new file mode 100644
index 0000000..885e07e
--- /dev/null
+++ b/src/com/android/providers/media/util/UserCache.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.util;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.LongSparseArray;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * UserCache is a class that keeps track of all users that the current MediaProvider
+ * instance is responsible for. By default, it handles storage for the user it is running as,
+ * but as of Android API 31, it will also handle storage for profiles that share media
+ * with their parent - profiles for which @link{UserManager#isMediaSharedWithParent} is set.
+ *
+ * It also keeps a cache of user contexts, for improving these lookups.
+ *
+ * Note that we don't use the USER_ broadcasts for keeping this state up to date, because they
+ * aren't guaranteed to be received before the volume events for a user.
+ */
+public class UserCache {
+ final Object mLock = new Object();
+ final Context mContext;
+ final UserManager mUserManager;
+
+ @GuardedBy("mLock")
+ final LongSparseArray<Context> mUserContexts = new LongSparseArray<>();
+
+ @GuardedBy("mLock")
+ final ArrayList<UserHandle> mUsers = new ArrayList<>();
+
+ public UserCache(Context context) {
+ mContext = context;
+ mUserManager = context.getSystemService(UserManager.class);
+
+ update();
+ }
+
+ private void update() {
+ List<UserHandle> profiles = mUserManager.getEnabledProfiles();
+ synchronized (mLock) {
+ mUsers.clear();
+ // Add the user we're running as by default
+ mUsers.add(Process.myUserHandle());
+ // And find all profiles that share media with us
+ for (UserHandle profile : profiles) {
+ if (!profile.equals(mContext.getUser())) {
+ // Check if it's a profile that shares media with us
+ Context userContext = getContextForUser(profile);
+ if (userContext.getSystemService(UserManager.class).isMediaSharedWithParent()) {
+ mUsers.add(profile);
+ }
+ }
+ }
+ }
+ }
+
+ public @NonNull List<UserHandle> updateAndGetUsers() {
+ update();
+ synchronized (mLock) {
+ return (List<UserHandle>) mUsers.clone();
+ }
+ }
+
+ public @NonNull Context getContextForUser(@NonNull UserHandle user) {
+ Context userContext;
+ synchronized (mLock) {
+ userContext = mUserContexts.get(user.getIdentifier());
+ if (userContext != null) {
+ return userContext;
+ }
+ }
+ try {
+ userContext = mContext.createPackageContextAsUser("system", 0, user);
+ synchronized (mLock) {
+ mUserContexts.put(user.getIdentifier(), userContext);
+ }
+ return userContext;
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException("Failed to create context for user " + user, e);
+ }
+ }
+
+ /**
+ * Returns whether the passed in user shares media with its parent (or peer).
+ * Note that the value returned here is based on cached data; it relies on
+ * other callers to keep the user cache up-to-date.
+ *
+ * @param user user to check
+ * @return whether the user shares media with its parent
+ */
+ public boolean userSharesMediaWithParent(@NonNull UserHandle user) {
+ synchronized (mLock) {
+ return mUsers.contains(user);
+ }
+ }
+}