diff --git a/core/api/current.txt b/core/api/current.txt index c175d9750fe8a..6394bea920432 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -33167,6 +33167,7 @@ package android.os.storage { method @Nullable public android.os.storage.StorageVolume getStorageVolume(@NonNull java.io.File); method @NonNull public android.os.storage.StorageVolume getStorageVolume(@NonNull android.net.Uri); method @NonNull public java.util.List getStorageVolumes(); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public java.util.List getStorageVolumesIncludingSharedProfiles(); method @NonNull public java.util.UUID getUuidForPath(@NonNull java.io.File) throws java.io.IOException; method public boolean isAllocationSupported(@NonNull java.io.FileDescriptor); method public boolean isCacheBehaviorGroup(java.io.File) throws java.io.IOException; diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 29accb9b31876..2fdeb50b4dbdd 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -290,6 +290,8 @@ public class StorageManager { public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10; /** {@hide} */ public static final int FLAG_INCLUDE_RECENT = 1 << 11; + /** {@hide} */ + public static final int FLAG_INCLUDE_SHARED_PROFILE = 1 << 12; /** {@hide} */ public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM; @@ -1326,6 +1328,23 @@ public class StorageManager { return res; } + /** + * Return the list of shared/external storage volumes currently available to + * the calling user and the user it shares media with + * CDD link : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support + *

+ * This is similar to {@link StorageManager#getStorageVolumes()} except that the result also + * includes the volumes belonging to any user it shares media with + */ + @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) + public @NonNull List getStorageVolumesIncludingSharedProfiles() { + final ArrayList res = new ArrayList<>(); + Collections.addAll(res, + getVolumeList(mContext.getUserId(), + FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_SHARED_PROFILE)); + return res; + } + /** * Return the list of shared/external storage volumes both currently and * recently available to the calling user. diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 98764e02d803e..b74a788d2c181 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -123,6 +123,7 @@ import android.provider.DocumentsContract; import android.provider.Downloads; import android.provider.MediaStore; import android.provider.Settings; +import android.service.storage.ExternalStorageService; import android.sysprop.VoldProperties; import android.text.TextUtils; import android.text.format.DateUtils; @@ -160,8 +161,6 @@ import com.android.server.storage.StorageSessionController.ExternalStorageServic import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import libcore.io.IoUtils; import libcore.util.EmptyArray; @@ -173,6 +172,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.math.BigInteger; import java.security.GeneralSecurityException; @@ -491,6 +492,8 @@ class StorageManagerService extends IStorageManager.Stub @GuardedBy("mAppFuseLock") private AppFuseBridge mAppFuseBridge = null; + private HashMap mUserSharesMediaWith = new HashMap<>(); + /** Matches known application dir paths. The first group contains the generic part of the path, * the second group contains the user id (or null if it's a public volume without users), the * third group contains the package name, and the fourth group the remainder of the path. @@ -1235,6 +1238,21 @@ class StorageManagerService extends IStorageManager.Stub private void onUnlockUser(int userId) { Slog.d(TAG, "onUnlockUser " + userId); + if (userId != UserHandle.USER_SYSTEM) { + // Check if this user shares media with another user + try { + Context userContext = mContext.createPackageContextAsUser("system", 0, + UserHandle.of(userId)); + UserManager um = userContext.getSystemService(UserManager.class); + if (um != null && um.isMediaSharedWithParent()) { + int parentUserId = um.getProfileParent(userId).id; + mUserSharesMediaWith.put(userId, parentUserId); + mUserSharesMediaWith.put(parentUserId, userId); + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to create user context for user " + userId); + } + } // We purposefully block here to make sure that user-specific // staging area is ready so it's ready for zygote-forked apps to // bind mount against. @@ -3953,6 +3971,29 @@ class StorageManagerService extends IStorageManager.Stub final boolean realState = (flags & StorageManager.FLAG_REAL_STATE) != 0; final boolean includeInvisible = (flags & StorageManager.FLAG_INCLUDE_INVISIBLE) != 0; final boolean includeRecent = (flags & StorageManager.FLAG_INCLUDE_RECENT) != 0; + final boolean includeSharedProfile = + (flags & StorageManager.FLAG_INCLUDE_SHARED_PROFILE) != 0; + + // Only Apps with MANAGE_EXTERNAL_STORAGE should call the API with includeSharedProfile + if (includeSharedProfile) { + try { + // Get package name for calling app and + // verify it has MANAGE_EXTERNAL_STORAGE permission + final String[] packagesFromUid = mIPackageManager.getPackagesForUid(callingUid); + if (packagesFromUid == null) { + throw new SecurityException("Unknown uid " + callingUid); + } + // Checking first entry in packagesFromUid is enough as using "sharedUserId" + // mechanism is rare and discouraged. Also, Apps that share same UID share the same + // permissions. + if (!mStorageManagerInternal.hasExternalStorageAccess(callingUid, + packagesFromUid[0])) { + throw new SecurityException("Only File Manager Apps permitted"); + } + } catch (RemoteException re) { + throw new SecurityException("Unknown uid " + callingUid, re); + } + } // Report all volumes as unmounted until we've recorded that user 0 has unlocked. There // are no guarantees that callers will see a consistent view of the volume before that @@ -3984,6 +4025,7 @@ class StorageManagerService extends IStorageManager.Stub final ArrayList res = new ArrayList<>(); final ArraySet resUuids = new ArraySet<>(); + final int userIdSharingMedia = mUserSharesMediaWith.getOrDefault(userId, -1); synchronized (mLock) { for (int i = 0; i < mVolumes.size(); i++) { final String volId = mVolumes.keyAt(i); @@ -3996,6 +4038,11 @@ class StorageManagerService extends IStorageManager.Stub if (vol.getMountUserId() == userId) { break; } + if (includeSharedProfile && vol.getMountUserId() == userIdSharingMedia) { + // If the volume belongs to a user we share media with, + // return it too. + break; + } // Skip if emulated volume not for userId default: continue; @@ -4003,10 +4050,12 @@ class StorageManagerService extends IStorageManager.Stub boolean match = false; if (forWrite) { - match = vol.isVisibleForWrite(userId); + match = vol.isVisibleForWrite(userId) + || (includeSharedProfile && vol.isVisibleForWrite(userIdSharingMedia)); } else { match = vol.isVisibleForUser(userId) - || (includeInvisible && vol.getPath() != null); + || (includeInvisible && vol.getPath() != null) + || (includeSharedProfile && vol.isVisibleForRead(userIdSharingMedia)); } if (!match) continue; @@ -4027,9 +4076,13 @@ class StorageManagerService extends IStorageManager.Stub reportUnmounted = true; } - final StorageVolume userVol = vol.buildStorageVolume(mContext, userId, + int volUserId = userId; + if (volUserId != vol.getMountUserId() && vol.getMountUserId() >= 0) { + volUserId = vol.getMountUserId(); + } + final StorageVolume userVol = vol.buildStorageVolume(mContext, volUserId, reportUnmounted); - if (vol.isPrimary()) { + if (vol.isPrimary() && vol.getMountUserId() == userId) { res.add(0, userVol); foundPrimary = true; } else {