Merge "Fix misleading transcoding exception" into sc-dev
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 4f29271..a482e01 100755
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -645,6 +645,13 @@
if (pkg == ".nomedia") {
return true;
}
+ if (android::base::StartsWith(path, "/storage/emulated")) {
+ // Emulated storage bind-mounts app-private data directories, and so these
+ // should not be accessible through FUSE anyway.
+ LOG(WARNING) << "Rejected access to app-private dir on FUSE: " << path
+ << " from uid: " << uid;
+ return false;
+ }
if (!mp->isUidAllowedAccessToDataOrObbPath(uid, path)) {
PLOG(WARNING) << "Invalid other package file access from " << uid << "(: " << path;
return false;
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index e93dd86..ea47c97 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -171,8 +171,7 @@
<item quantity="other"><xliff:g id="COUNT">^1</xliff:g> वटा वस्तु मेटाइँदै छन्…</item>
<item quantity="one">वस्तु मेटाइँदै छ…</item>
</plurals>
- <!-- no translation found for transcode_denied (6760546817138288976) -->
- <skip />
+ <string name="transcode_denied" msgid="6760546817138288976">"<xliff:g id="APP_NAME">%s</xliff:g> ले मिडिया फाइलहरू प्रयोग गर्न सक्दैन"</string>
<string name="transcode_processing_cancelled" msgid="5340383917746945590">"मिडिया प्रोसेस गर्ने कार्य रद्द गरियो"</string>
<string name="transcode_processing_error" msgid="8921643164508407874">"मिडिया प्रोसेस गर्ने क्रममा त्रुटि भयो"</string>
<string name="transcode_processing_success" msgid="447288876429730122">"मिडिया प्रोसेस गरियो"</string>
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index 97a33e3..868f34d 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -69,6 +69,7 @@
public class LocalCallingIdentity {
public final int pid;
public final int uid;
+ private final UserHandle user;
private final Context context;
private final String packageNameUnchecked;
// Info used for logging permission checks
@@ -77,9 +78,16 @@
private LocalCallingIdentity(Context context, int pid, int uid, String packageNameUnchecked,
@Nullable String attributionTag) {
+ this(context, pid, uid, UserHandle.getUserHandleForUid(uid), packageNameUnchecked,
+ attributionTag);
+ }
+
+ private LocalCallingIdentity(Context context, int pid, int uid, UserHandle user,
+ String packageNameUnchecked, @Nullable String attributionTag) {
this.context = context;
this.pid = pid;
this.uid = uid;
+ this.user = user;
this.packageNameUnchecked = packageNameUnchecked;
this.attributionTag = attributionTag;
}
@@ -112,8 +120,16 @@
if (callingAttributionTag == null) {
callingAttributionTag = context.getAttributionTag();
}
+ UserHandle user;
+ if (binderUid == Process.SHELL_UID || binderUid == Process.ROOT_UID) {
+ // For requests coming from the shell (eg `content query`), assume they are
+ // for the user we are running as.
+ user = Process.myUserHandle();
+ } else {
+ user = UserHandle.getUserHandleForUid(binderUid);
+ }
return new LocalCallingIdentity(context, Binder.getCallingPid(), binderUid,
- callingPackage, callingAttributionTag);
+ user, callingPackage, callingAttributionTag);
}
public static LocalCallingIdentity fromExternal(Context context, int uid) {
@@ -131,6 +147,7 @@
ident.hasPermissionResolved = PERMISSION_IS_REDACTION_NEEDED;
}
}
+
return ident;
}
@@ -140,10 +157,15 @@
}
public static LocalCallingIdentity fromSelf(Context context) {
+ return fromSelfAsUser(context, Process.myUserHandle());
+ }
+
+ public static LocalCallingIdentity fromSelfAsUser(Context context, UserHandle user) {
final LocalCallingIdentity ident = new LocalCallingIdentity(
context,
android.os.Process.myPid(),
android.os.Process.myUid(),
+ user,
context.getOpPackageName(),
context.getAttributionTag());
@@ -218,6 +240,10 @@
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
+ public UserHandle getUser() {
+ return user;
+ }
+
public static final int PERMISSION_IS_SELF = 1 << 0;
public static final int PERMISSION_IS_SHELL = 1 << 1;
public static final int PERMISSION_IS_MANAGER = 1 << 2;
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 8fa70f3..2a8cb3b 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -376,17 +376,6 @@
sDataColumns.put(MediaStore.Audio.AlbumColumns.ALBUM_ART, null);
}
- private static final Object sCacheLock = new Object();
-
- @GuardedBy("sCacheLock")
- private static final Set<String> sCachedExternalVolumeNames = new ArraySet<>();
- @GuardedBy("sCacheLock")
- private static final Map<String, File> sCachedVolumePaths = new ArrayMap<>();
- @GuardedBy("sCacheLock")
- private static final Map<String, Collection<File>> sCachedVolumeScanPaths = new ArrayMap<>();
- @GuardedBy("sCacheLock")
- private static final ArrayMap<File, String> sCachedVolumePathToId = new ArrayMap<>();
-
private static final int sUserId = UserHandle.myUserId();
/**
@@ -401,42 +390,18 @@
private final LRUCache<String, Integer> mNonHiddenPaths = new LRUCache<>(NON_HIDDEN_CACHE_SIZE);
public void updateVolumes() {
- synchronized (sCacheLock) {
- sCachedExternalVolumeNames.clear();
- sCachedExternalVolumeNames.addAll(MediaStore.getExternalVolumeNames(getContext()));
- Log.v(TAG, "Updated external volumes to: " + sCachedExternalVolumeNames.toString());
-
- sCachedVolumePaths.clear();
- sCachedVolumeScanPaths.clear();
- sCachedVolumePathToId.clear();
- try {
- sCachedVolumeScanPaths.put(MediaStore.VOLUME_INTERNAL,
- FileUtils.getVolumeScanPaths(getContext(), MediaStore.VOLUME_INTERNAL));
- } catch (FileNotFoundException e) {
- Log.wtf(TAG, "Failed to update volume " + MediaStore.VOLUME_INTERNAL, e);
- }
-
- for (String volumeName : sCachedExternalVolumeNames) {
- try {
- final Uri uri = MediaStore.Files.getContentUri(volumeName);
- final StorageVolume volume = mStorageManager.getStorageVolume(uri);
- sCachedVolumePaths.put(volumeName, volume.getDirectory());
- sCachedVolumeScanPaths.put(volumeName,
- FileUtils.getVolumeScanPaths(getContext(), volumeName));
- sCachedVolumePathToId.put(volume.getDirectory(), volume.getId());
- } catch (IllegalStateException | FileNotFoundException e) {
- Log.wtf(TAG, "Failed to update volume " + volumeName, e);
- }
- }
- }
-
+ mVolumeCache.update();
// Update filters to reflect mounted volumes so users don't get
// confused by metadata from ejected volumes
ForegroundThread.getExecutor().execute(() -> {
- mExternalDatabase.setFilterVolumeNames(getExternalVolumeNames());
+ mExternalDatabase.setFilterVolumeNames(mVolumeCache.getExternalVolumeNames());
});
}
+ public @NonNull MediaVolume getVolume(@NonNull String volumeName) throws FileNotFoundException {
+ return mVolumeCache.findVolume(volumeName, mCallingIdentity.get().getUser());
+ }
+
public @NonNull File getVolumePath(@NonNull String volumeName) throws FileNotFoundException {
// Ugly hack to keep unit tests passing, where we don't always have a
// Context to discover volumes with
@@ -444,54 +409,16 @@
return Environment.getExternalStorageDirectory();
}
- synchronized (sCacheLock) {
- if (sCachedVolumePaths.containsKey(volumeName)) {
- return sCachedVolumePaths.get(volumeName);
- }
-
- // Nothing found above; let's ask directly and cache the answer
- final File res = FileUtils.getVolumePath(getContext(), volumeName);
- sCachedVolumePaths.put(volumeName, res);
- return res;
- }
+ return mVolumeCache.getVolumePath(volumeName, mCallingIdentity.get().getUser());
}
public @NonNull String getVolumeId(@NonNull File file) throws FileNotFoundException {
- synchronized (sCacheLock) {
- for (int i = 0; i < sCachedVolumePathToId.size(); i++) {
- if (FileUtils.contains(sCachedVolumePathToId.keyAt(i), file)) {
- return sCachedVolumePathToId.valueAt(i);
- }
- }
-
- // Nothing found above; let's ask directly and cache the answer
- final StorageVolume volume = mStorageManager.getStorageVolume(file);
- if (volume == null) {
- throw new FileNotFoundException("Missing volume for " + file);
- }
- sCachedVolumePathToId.put(volume.getDirectory(), volume.getId());
- return volume.getId();
- }
- }
-
- public @NonNull Set<String> getExternalVolumeNames() {
- synchronized (sCacheLock) {
- return new ArraySet<>(sCachedExternalVolumeNames);
- }
+ return mVolumeCache.getVolumeId(file);
}
public @NonNull Collection<File> getVolumeScanPaths(String volumeName)
throws FileNotFoundException {
- synchronized (sCacheLock) {
- if (sCachedVolumeScanPaths.containsKey(volumeName)) {
- return new ArrayList<>(sCachedVolumeScanPaths.get(volumeName));
- }
-
- // Nothing found above; let's ask directly and cache the answer
- final Collection<File> res = FileUtils.getVolumeScanPaths(getContext(), volumeName);
- sCachedVolumeScanPaths.put(volumeName, res);
- return res;
- }
+ return mVolumeCache.getVolumeScanPaths(volumeName, mCallingIdentity.get().getUser());
}
/**
@@ -515,6 +442,8 @@
private DevicePolicyManager mDevicePolicyManager;
private UserManager mUserManager;
+ private VolumeCache mVolumeCache;
+
private int mExternalStorageAuthorityAppId;
private int mDownloadsAuthorityAppId;
private Size mThumbSize;
@@ -894,39 +823,22 @@
* devices. We only do this once per volume so we don't annoy the user if
* deleted manually.
*/
- private void ensureDefaultFolders(@NonNull String volumeName, @NonNull SQLiteDatabase db) {
- try {
- final File path = getVolumePath(volumeName);
- final StorageVolume vol = mStorageManager.getStorageVolume(path);
- final String key;
- if (vol == null) {
- Log.w(TAG, "Failed to ensure default folders for " + volumeName);
- return;
- }
+ private void ensureDefaultFolders(@NonNull MediaVolume volume, @NonNull SQLiteDatabase db) {
+ final String key = "created_default_folders_" + volume.getId();
- if (vol.isPrimary()) {
- key = "created_default_folders";
- } else {
- key = "created_default_folders_" + vol.getMediaStoreVolumeName();
- }
-
- final SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(getContext());
- if (prefs.getInt(key, 0) == 0) {
- for (String folderName : DEFAULT_FOLDER_NAMES) {
- final File folder = new File(vol.getDirectory(), folderName);
- if (!folder.exists()) {
- folder.mkdirs();
- insertDirectory(db, folder.getAbsolutePath());
- }
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ if (prefs.getInt(key, 0) == 0) {
+ for (String folderName : DEFAULT_FOLDER_NAMES) {
+ final File folder = new File(volume.getPath(), folderName);
+ if (!folder.exists()) {
+ folder.mkdirs();
+ insertDirectory(db, folder.getAbsolutePath());
}
-
- SharedPreferences.Editor editor = prefs.edit();
- editor.putInt(key, 1);
- editor.commit();
}
- } catch (IOException e) {
- Log.w(TAG, "Failed to ensure default folders for " + volumeName, e);
+
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putInt(key, 1);
+ editor.commit();
}
}
@@ -936,10 +848,10 @@
* {@link DatabaseHelper#getOrCreateUuid} doesn't match the UUID found on
* disk, then all thumbnails will be considered stable and will be deleted.
*/
- private void ensureThumbnailsValid(@NonNull String volumeName, @NonNull SQLiteDatabase db) {
+ private void ensureThumbnailsValid(@NonNull MediaVolume volume, @NonNull SQLiteDatabase db) {
final String uuidFromDatabase = DatabaseHelper.getOrCreateUuid(db);
try {
- for (File dir : getThumbnailDirectories(volumeName)) {
+ for (File dir : getThumbnailDirectories(volume)) {
if (!dir.exists()) {
dir.mkdirs();
}
@@ -967,7 +879,7 @@
}
}
} catch (IOException e) {
- Log.w(TAG, "Failed to ensure thumbnails valid for " + volumeName, e);
+ Log.w(TAG, "Failed to ensure thumbnails valid for " + volume.getName(), e);
}
}
@@ -992,6 +904,7 @@
mPackageManager = context.getPackageManager();
mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
mUserManager = context.getSystemService(UserManager.class);
+ mVolumeCache = new VolumeCache(context);
// Reasonable thumbnail size is half of the smallest screen edge width
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
@@ -1029,9 +942,9 @@
});
updateVolumes();
- attachVolume(MediaStore.VOLUME_INTERNAL, /* validate */ false);
- for (String volumeName : getExternalVolumeNames()) {
- attachVolume(volumeName, /* validate */ false);
+ attachVolume(MediaVolume.fromInternal(), /* validate */ false);
+ for (MediaVolume volume : mVolumeCache.getExternalVolumes()) {
+ attachVolume(volume, /* validate */ false);
}
// Watch for performance-sensitive activity
@@ -1092,7 +1005,11 @@
}
public LocalCallingIdentity clearLocalCallingIdentity() {
- return clearLocalCallingIdentity(LocalCallingIdentity.fromSelf(getContext()));
+ // We retain the user part of the calling identity, since we are executing
+ // the call on behalf of that user, and we need to maintain the user context
+ // to correctly resolve things like volumes
+ UserHandle user = mCallingIdentity.get().getUser();
+ return clearLocalCallingIdentity(LocalCallingIdentity.fromSelfAsUser(getContext(), user));
}
public LocalCallingIdentity clearLocalCallingIdentity(LocalCallingIdentity replacement) {
@@ -1133,19 +1050,19 @@
Logging.trimPersistent();
// Scan all volumes to resolve any staleness
- for (String volumeName : getExternalVolumeNames()) {
+ for (MediaVolume volume : mVolumeCache.getExternalVolumes()) {
// Possibly bail before digging into each volume
signal.throwIfCanceled();
try {
- MediaService.onScanVolume(getContext(), volumeName, REASON_IDLE);
+ MediaService.onScanVolume(getContext(), volume, REASON_IDLE);
} catch (IOException e) {
Log.w(TAG, e);
}
// Ensure that our thumbnails are valid
mExternalDatabase.runWithTransaction((db) -> {
- ensureThumbnailsValid(volumeName, db);
+ ensureThumbnailsValid(volume, db);
return null;
});
}
@@ -1341,7 +1258,7 @@
}
public Uri scanFile(File file, int reason) {
- return mMediaScanner.scanFile(file, reason);
+ return scanFile(file, reason, null);
}
public Uri scanFile(File file, int reason, String ownerPackage) {
@@ -4171,13 +4088,20 @@
if (match == VOLUMES) {
String name = initialValues.getAsString("name");
- Uri attachedVolume = attachVolume(name, /* validate */ true);
- if (mMediaScannerVolume != null && mMediaScannerVolume.equals(name)) {
- final DatabaseHelper helper = getDatabaseForUri(
- MediaStore.Files.getContentUri(mMediaScannerVolume));
- helper.mScanStartTime = SystemClock.elapsedRealtime();
+ MediaVolume volume = null;
+ try {
+ volume = getVolume(name);
+ Uri attachedVolume = attachVolume(volume, /* validate */ true);
+ if (mMediaScannerVolume != null && mMediaScannerVolume.equals(name)) {
+ final DatabaseHelper helper = getDatabaseForUri(
+ MediaStore.Files.getContentUri(mMediaScannerVolume));
+ helper.mScanStartTime = SystemClock.elapsedRealtime();
+ }
+ return attachedVolume;
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Couldn't find volume with name " + volume.getName());
+ return null;
}
- return attachedVolume;
}
final DatabaseHelper helper = getDatabaseForUri(uri);
@@ -4625,7 +4549,7 @@
final String volumeName = MediaStore.getVolumeName(uri);
final String includeVolumes;
if (MediaStore.VOLUME_EXTERNAL.equals(volumeName)) {
- includeVolumes = bindList(getExternalVolumeNames().toArray());
+ includeVolumes = bindList(mVolumeCache.getExternalVolumeNames().toArray());
} else {
includeVolumes = bindList(volumeName);
}
@@ -5605,6 +5529,7 @@
}
case MediaStore.SCAN_FILE_CALL:
case MediaStore.SCAN_VOLUME_CALL: {
+ final int userId = Binder.getCallingUid() / PER_USER_RANGE;
final LocalCallingIdentity token = clearLocalCallingIdentity();
final CallingIdentity providerToken = clearCallingIdentity();
try {
@@ -5617,7 +5542,13 @@
}
case MediaStore.SCAN_VOLUME_CALL: {
final String volumeName = arg;
- MediaService.onScanVolume(getContext(), volumeName, REASON_DEMAND);
+ try {
+ MediaVolume volume = mVolumeCache.findVolume(volumeName,
+ UserHandle.of(userId));
+ MediaService.onScanVolume(getContext(), volume, REASON_DEMAND);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Failed to find volume " + volumeName, e);
+ }
break;
}
}
@@ -5994,12 +5925,12 @@
final long[] knownIdsRaw = knownIds.toArray();
Arrays.sort(knownIdsRaw);
- for (String volumeName : getExternalVolumeNames()) {
+ for (MediaVolume volume : mVolumeCache.getExternalVolumes()) {
final List<File> thumbDirs;
try {
- thumbDirs = getThumbnailDirectories(volumeName);
+ thumbDirs = getThumbnailDirectories(volume);
} catch (FileNotFoundException e) {
- Log.w(TAG, "Failed to resolve volume " + volumeName, e);
+ Log.w(TAG, "Failed to resolve volume " + volume.getName(), e);
continue;
}
@@ -6141,8 +6072,8 @@
}
};
- private List<File> getThumbnailDirectories(String volumeName) throws FileNotFoundException {
- final File volumePath = getVolumePath(volumeName);
+ private List<File> getThumbnailDirectories(MediaVolume volume) throws FileNotFoundException {
+ final File volumePath = volume.getPath();
return Arrays.asList(
FileUtils.buildPath(volumePath, Environment.DIRECTORY_MUSIC, DIRECTORY_THUMBNAILS),
FileUtils.buildPath(volumePath, Environment.DIRECTORY_MOVIES, DIRECTORY_THUMBNAILS),
@@ -9280,8 +9211,16 @@
private @NonNull DatabaseHelper getDatabaseForUri(Uri uri) throws VolumeNotFoundException {
final String volumeName = resolveVolumeName(uri);
- synchronized (mAttachedVolumeNames) {
- if (!mAttachedVolumeNames.contains(volumeName)) {
+ synchronized (mAttachedVolumes) {
+ boolean volumeAttached = false;
+ for (MediaVolume vol : mAttachedVolumes) {
+ UserHandle user = mCallingIdentity.get().getUser();
+ if (vol.getName().equals(volumeName) && vol.isVisibleToUser(user)) {
+ volumeAttached = true;
+ break;
+ }
+ }
+ if (!volumeAttached) {
throw new VolumeNotFoundException(volumeName);
}
}
@@ -9316,42 +9255,43 @@
return MediaStore.AUTHORITY_URI.buildUpon().appendPath(volumeName).build();
}
- public Uri attachVolume(String volume, boolean validate) {
+ public Uri attachVolume(MediaVolume volume, boolean validate) {
if (mCallingIdentity.get().pid != android.os.Process.myPid()) {
throw new SecurityException(
"Opening and closing databases not allowed.");
}
+ final String volumeName = volume.getName();
+
// Quick check for shady volume names
- MediaStore.checkArgumentVolumeName(volume);
+ MediaStore.checkArgumentVolumeName(volumeName);
// Quick check that volume actually exists
- if (!MediaStore.VOLUME_INTERNAL.equals(volume) && validate) {
+ if (!MediaStore.VOLUME_INTERNAL.equals(volumeName) && validate) {
try {
- getVolumePath(volume);
+ getVolumePath(volumeName);
} catch (IOException e) {
throw new IllegalArgumentException(
"Volume " + volume + " currently unavailable", e);
}
}
- synchronized (mAttachedVolumeNames) {
- mAttachedVolumeNames.add(volume);
+ synchronized (mAttachedVolumes) {
+ mAttachedVolumes.add(volume);
}
final ContentResolver resolver = getContext().getContentResolver();
- final Uri uri = getBaseContentUri(volume);
- resolver.notifyChange(getBaseContentUri(volume), null);
+ final Uri uri = getBaseContentUri(volumeName);
+ // TODO(b/182396009) we probably also want to notify clone profile (and vice versa)
+ resolver.notifyChange(getBaseContentUri(volumeName), null);
if (LOGV) Log.v(TAG, "Attached volume: " + volume);
- if (!MediaStore.VOLUME_INTERNAL.equals(volume)) {
+ if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
// Also notify on synthetic view of all devices
resolver.notifyChange(getBaseContentUri(MediaStore.VOLUME_EXTERNAL), null);
ForegroundThread.getExecutor().execute(() -> {
- final DatabaseHelper helper = MediaStore.VOLUME_INTERNAL.equals(volume)
- ? mInternalDatabase : mExternalDatabase;
- helper.runWithTransaction((db) -> {
+ mExternalDatabase.runWithTransaction((db) -> {
ensureDefaultFolders(volume, db);
ensureThumbnailsValid(volume, db);
return null;
@@ -9360,26 +9300,33 @@
// We just finished the database operation above, we know that
// it's ready to answer queries, so notify our DocumentProvider
// so it can answer queries without risking ANR
- MediaDocumentsProvider.onMediaStoreReady(getContext(), volume);
+ MediaDocumentsProvider.onMediaStoreReady(getContext(), volumeName);
});
}
return uri;
}
private void detachVolume(Uri uri) {
- detachVolume(MediaStore.getVolumeName(uri));
+ final String volumeName = MediaStore.getVolumeName(uri);
+ try {
+ detachVolume(getVolume(volumeName));
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Couldn't find volume for URI " + uri, e) ;
+ }
}
- public void detachVolume(String volume) {
+ public void detachVolume(MediaVolume volume) {
if (mCallingIdentity.get().pid != android.os.Process.myPid()) {
throw new SecurityException(
"Opening and closing databases not allowed.");
}
- // Quick check for shady volume names
- MediaStore.checkArgumentVolumeName(volume);
+ final String volumeName = volume.getName();
- if (MediaStore.VOLUME_INTERNAL.equals(volume)) {
+ // Quick check for shady volume names
+ MediaStore.checkArgumentVolumeName(volumeName);
+
+ if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
throw new UnsupportedOperationException(
"Deleting the internal volume is not allowed");
}
@@ -9387,24 +9334,24 @@
// Signal any scanning to shut down
mMediaScanner.onDetachVolume(volume);
- synchronized (mAttachedVolumeNames) {
- mAttachedVolumeNames.remove(volume);
+ synchronized (mAttachedVolumes) {
+ mAttachedVolumes.remove(volume);
}
final ContentResolver resolver = getContext().getContentResolver();
- final Uri uri = getBaseContentUri(volume);
- resolver.notifyChange(getBaseContentUri(volume), null);
+ final Uri uri = getBaseContentUri(volumeName);
+ resolver.notifyChange(getBaseContentUri(volumeName), null);
- if (!MediaStore.VOLUME_INTERNAL.equals(volume)) {
+ if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
// Also notify on synthetic view of all devices
resolver.notifyChange(getBaseContentUri(MediaStore.VOLUME_EXTERNAL), null);
}
- if (LOGV) Log.v(TAG, "Detached volume: " + volume);
+ if (LOGV) Log.v(TAG, "Detached volume: " + volumeName);
}
- @GuardedBy("mAttachedVolumeNames")
- private final ArraySet<String> mAttachedVolumeNames = new ArraySet<>();
+ @GuardedBy("mAttachedVolumes")
+ private final ArraySet<MediaVolume> mAttachedVolumes = new ArraySet<>();
@GuardedBy("mCustomCollators")
private final ArraySet<String> mCustomCollators = new ArraySet<>();
@@ -9734,8 +9681,8 @@
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
writer.println("mThumbSize=" + mThumbSize);
- synchronized (mAttachedVolumeNames) {
- writer.println("mAttachedVolumeNames=" + mAttachedVolumeNames);
+ synchronized (mAttachedVolumes) {
+ writer.println("mAttachedVolumes=" + mAttachedVolumes);
}
writer.println();
diff --git a/src/com/android/providers/media/MediaService.java b/src/com/android/providers/media/MediaService.java
index d7c6bab..5e3e10f 100644
--- a/src/com/android/providers/media/MediaService.java
+++ b/src/com/android/providers/media/MediaService.java
@@ -27,7 +27,9 @@
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Trace;
+import android.os.storage.StorageVolume;
import android.provider.MediaStore;
import android.util.Log;
@@ -68,7 +70,7 @@
break;
}
case Intent.ACTION_MEDIA_MOUNTED: {
- onScanVolume(this, intent.getData(), REASON_MOUNTED);
+ onScanVolume(this, intent, REASON_MOUNTED);
break;
}
default: {
@@ -100,21 +102,25 @@
}
}
- private static void onScanVolume(Context context, Uri uri, int reason)
+ private static void onScanVolume(Context context, Intent intent, int reason)
throws IOException {
- final File file = new File(uri.getPath()).getCanonicalFile();
- final String volumeName = FileUtils.getVolumeName(context, file);
- onScanVolume(context, volumeName, reason);
+ final StorageVolume volume = intent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME);
+ if (volume != null) {
+ onScanVolume(context, MediaVolume.fromStorageVolume(volume), reason);
+ } else {
+ Log.e(TAG, "Couldn't retrieve StorageVolume from intent");
+ }
}
- public static void onScanVolume(Context context, String volumeName, int reason)
+ public static void onScanVolume(Context context, MediaVolume volume, int reason)
throws IOException {
+ final String volumeName = volume.getName();
// If we're about to scan any external storage, scan internal first
// to ensure that we have ringtones ready to roll before a possibly very
// long external storage scan
if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
- onScanVolume(context, MediaStore.VOLUME_INTERNAL, reason);
+ onScanVolume(context, MediaVolume.fromInternal(), reason);
RingtoneManager.ensureDefaultRingtones(context);
}
@@ -131,7 +137,7 @@
try (ContentProviderClient cpc = context.getContentResolver()
.acquireContentProviderClient(MediaStore.AUTHORITY)) {
final MediaProvider provider = ((MediaProvider) cpc.getLocalContentProvider());
- provider.attachVolume(volumeName, /* validate */ true);
+ provider.attachVolume(volume, /* validate */ true);
final ContentResolver resolver = ContentResolver.wrap(cpc.getLocalContentProvider());
diff --git a/src/com/android/providers/media/MediaVolume.java b/src/com/android/providers/media/MediaVolume.java
new file mode 100644
index 0000000..577a040
--- /dev/null
+++ b/src/com/android/providers/media/MediaVolume.java
@@ -0,0 +1,118 @@
+/*
+ * 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;
+
+import android.os.UserHandle;
+import android.os.storage.StorageVolume;
+import android.provider.MediaStore;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.util.Objects;
+
+/**
+ * MediaVolume is a MediaProvider-internal representation of a storage volume.
+ *
+ * Before MediaVolume, volumes inside MediaProvider were represented by their name;
+ * but now that MediaProvider handles volumes on behalf on multiple users, the name of a volume
+ * might no longer be unique. So MediaVolume holds both a name and a user. The user may be
+ * null on volumes without an owner (eg public volumes).
+ *
+ * In addition to that, we keep the path and ID of the volume cached in here as well
+ * for easy access.
+ */
+public final class MediaVolume {
+ /**
+ * Name of the volume.
+ */
+ private final @NonNull String mName;
+
+ /**
+ * User to which the volume belongs to; might be null in case of public volumes.
+ */
+ private final @Nullable UserHandle mUser;
+
+ /**
+ * Path on which the volume is mounted.
+ */
+ private final @Nullable File mPath;
+
+ /**
+ * Unique ID of the volume; eg "external;0"
+ */
+ private final @Nullable String mId;
+
+ public @NonNull String getName() {
+ return mName;
+ }
+
+ public @Nullable UserHandle getUser() {
+ return mUser;
+ }
+
+ public @Nullable File getPath() {
+ return mPath;
+ }
+
+ public @Nullable String getId() {
+ return mId;
+ }
+
+ private MediaVolume (@NonNull String name, UserHandle user, File path, String id) {
+ this.mName = name;
+ this.mUser = user;
+ this.mPath = path;
+ this.mId = id;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ MediaVolume that = (MediaVolume) obj;
+ return Objects.equals(mName, that.mName) &&
+ Objects.equals(mUser, that.mUser) &&
+ Objects.equals(mPath, that.mPath) &&
+ Objects.equals(mId, that.mId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mName, mUser, mPath, mId);
+ }
+
+ public boolean isVisibleToUser(UserHandle user) {
+ return mUser == null || user.equals(mUser);
+ }
+
+ @NonNull
+ public static MediaVolume fromStorageVolume(StorageVolume storageVolume) {
+ String name = storageVolume.getMediaStoreVolumeName();
+ UserHandle user = storageVolume.getOwner();
+ File path = storageVolume.getDirectory();
+ String id = storageVolume.getId();
+ return new MediaVolume(name, user, path, id);
+ }
+
+ public static MediaVolume fromInternal() {
+ String name = MediaStore.VOLUME_INTERNAL;
+
+ return new MediaVolume(name, null, null, null);
+ }
+}
diff --git a/src/com/android/providers/media/VolumeCache.java b/src/com/android/providers/media/VolumeCache.java
new file mode 100644
index 0000000..d94ddfa
--- /dev/null
+++ b/src/com/android/providers/media/VolumeCache.java
@@ -0,0 +1,227 @@
+/*
+ * 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;
+
+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;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.provider.MediaStore;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+
+import com.android.providers.media.util.FileUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The VolumeCache class keeps track of all the volumes that are available,
+ * as well as their scan paths.
+ */
+public class VolumeCache {
+ private final Context mContext;
+
+ private final Object mLock = new Object();
+
+ private final UserManager mUserManager;
+
+ @GuardedBy("mLock")
+ private final ArrayList<MediaVolume> mExternalVolumes = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private final Map<MediaVolume, Collection<File>> mCachedVolumeScanPaths = new ArrayMap<>();
+
+ @GuardedBy("mLock")
+ private Collection<File> mCachedInternalScanPaths;
+
+ @GuardedBy("mLock")
+ private final LongSparseArray<Context> mUserContexts = new LongSparseArray<>();
+
+ public VolumeCache(Context context) {
+ mContext = context;
+ mUserManager = context.getSystemService(UserManager.class);
+ }
+
+ public @NonNull List<MediaVolume> getExternalVolumes() {
+ synchronized(mLock) {
+ return new ArrayList<>(mExternalVolumes);
+ }
+ }
+
+ public @NonNull Set<String> getExternalVolumeNames() {
+ synchronized (mLock) {
+ ArraySet<String> volNames = new ArraySet<String>();
+ for (MediaVolume vol : mExternalVolumes) {
+ volNames.add(vol.getName());
+ }
+ return volNames;
+ }
+ }
+
+ public @NonNull MediaVolume findVolume(@NonNull String volumeName, @NonNull UserHandle user)
+ throws FileNotFoundException {
+ synchronized (mLock) {
+ for (MediaVolume vol : mExternalVolumes) {
+ if (vol.getName().equals(volumeName) && vol.isVisibleToUser(user)) {
+ return vol;
+ }
+ }
+ }
+
+ throw new FileNotFoundException("Couldn't find volume with name " + volumeName);
+ }
+
+ public @NonNull File getVolumePath(@NonNull String volumeName, @NonNull UserHandle user)
+ throws FileNotFoundException {
+ synchronized (mLock) {
+ try {
+ MediaVolume volume = findVolume(volumeName, user);
+ return volume.getPath();
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "getVolumePath for unknown volume: " + volumeName);
+ // Try again by using FileUtils below
+ }
+
+ final Context userContext = getContextForUser(user);
+ return FileUtils.getVolumePath(userContext, volumeName);
+ }
+ }
+
+ public @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName,
+ @NonNull UserHandle user) throws FileNotFoundException {
+ synchronized (mLock) {
+ if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
+ // Internal is shared by all users
+ return mCachedInternalScanPaths;
+ }
+ try {
+ MediaVolume volume = findVolume(volumeName, user);
+ if (mCachedVolumeScanPaths.containsKey(volume)) {
+ return mCachedVolumeScanPaths.get(volume);
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Didn't find cached volume scan paths for " + volumeName);
+ }
+
+ // Nothing found above; let's ask directly
+ final Context userContext = getContextForUser(user);
+ final Collection<File> res = FileUtils.getVolumeScanPaths(userContext, volumeName);
+
+ return res;
+ }
+ }
+
+ public @NonNull MediaVolume findVolumeForFile(@NonNull File file) throws FileNotFoundException {
+ synchronized (mLock) {
+ for (MediaVolume volume : mExternalVolumes) {
+ if (FileUtils.contains(volume.getPath(), file)) {
+ return volume;
+ }
+ }
+ }
+
+ Log.w(TAG, "Didn't find any volume for getVolume(" + file.getPath() + ")");
+ // Nothing found above; let's ask directly
+ final StorageManager sm = mContext.getSystemService(StorageManager.class);
+ final StorageVolume volume = sm.getStorageVolume(file);
+ if (volume == null) {
+ throw new FileNotFoundException("Missing volume for " + file);
+ }
+
+ return MediaVolume.fromStorageVolume(volume);
+ }
+
+ public @NonNull String getVolumeId(@NonNull File file) throws FileNotFoundException {
+ MediaVolume volume = findVolumeForFile(file);
+
+ 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);
+ for (String volumeName : MediaStore.getExternalVolumeNames(userContext)) {
+ try {
+ final Uri uri = MediaStore.Files.getContentUri(volumeName);
+ final StorageVolume storageVolume = sm.getStorageVolume(uri);
+ MediaVolume volume = MediaVolume.fromStorageVolume(storageVolume);
+ mExternalVolumes.add(volume);
+ mCachedVolumeScanPaths.put(volume, FileUtils.getVolumeScanPaths(userContext,
+ volume.getName()));
+ } catch (IllegalStateException | FileNotFoundException e) {
+ Log.wtf(TAG, "Failed to update volume " + volumeName, e);
+ }
+ }
+ }
+
+ public void update() {
+ synchronized (mLock) {
+ mCachedVolumeScanPaths.clear();
+ try {
+ mCachedInternalScanPaths = FileUtils.getVolumeScanPaths(mContext,
+ MediaStore.VOLUME_INTERNAL);
+ } catch (FileNotFoundException e) {
+ 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);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java b/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
index 573d3c9..612889d 100644
--- a/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
+++ b/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
@@ -30,6 +30,7 @@
import androidx.annotation.Nullable;
import com.android.providers.media.MediaProvider;
+import com.android.providers.media.MediaVolume;
import java.io.File;
import java.io.IOException;
@@ -79,20 +80,20 @@
Objects.requireNonNull(vol);
MediaProvider mediaProvider = getMediaProvider();
- String volumeName = vol.getMediaStoreVolumeName();
switch(vol.getState()) {
case Environment.MEDIA_MOUNTED:
- mediaProvider.attachVolume(volumeName, /* validate */ false);
+ mediaProvider.attachVolume(MediaVolume.fromStorageVolume(vol),
+ /* validate */ false);
break;
case Environment.MEDIA_UNMOUNTED:
case Environment.MEDIA_EJECTING:
case Environment.MEDIA_REMOVED:
case Environment.MEDIA_BAD_REMOVAL:
- mediaProvider.detachVolume(volumeName);
+ mediaProvider.detachVolume(MediaVolume.fromStorageVolume(vol));
break;
default:
- Log.i(TAG, "Ignoring volume state for vol:" + volumeName
+ Log.i(TAG, "Ignoring volume state for vol:" + vol.getMediaStoreVolumeName()
+ ". State: " + vol.getState());
}
// Check for invalidation of cached volumes
diff --git a/src/com/android/providers/media/scan/LegacyMediaScanner.java b/src/com/android/providers/media/scan/LegacyMediaScanner.java
index 0a53a0d..d8d3bed 100644
--- a/src/com/android/providers/media/scan/LegacyMediaScanner.java
+++ b/src/com/android/providers/media/scan/LegacyMediaScanner.java
@@ -21,6 +21,8 @@
import androidx.annotation.Nullable;
+import com.android.providers.media.MediaVolume;
+
import java.io.File;
@Deprecated
@@ -52,7 +54,7 @@
}
@Override
- public void onDetachVolume(String volumeName) {
+ public void onDetachVolume(MediaVolume volume) {
throw new UnsupportedOperationException();
}
diff --git a/src/com/android/providers/media/scan/MediaScanner.java b/src/com/android/providers/media/scan/MediaScanner.java
index 1306873..45d2a24 100644
--- a/src/com/android/providers/media/scan/MediaScanner.java
+++ b/src/com/android/providers/media/scan/MediaScanner.java
@@ -26,6 +26,8 @@
import androidx.annotation.Nullable;
+import com.android.providers.media.MediaVolume;
+
import java.io.File;
public interface MediaScanner {
@@ -38,7 +40,7 @@
public void scanDirectory(File file, int reason);
public Uri scanFile(File file, int reason);
public Uri scanFile(File file, int reason, @Nullable String ownerPackage);
- public void onDetachVolume(String volumeName);
+ public void onDetachVolume(MediaVolume volume);
public void onIdleScanStopped();
public void onDirectoryDirty(File file);
}
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index e346891..9a38945 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -93,6 +93,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
+import com.android.providers.media.MediaVolume;
import com.android.providers.media.util.DatabaseUtils;
import com.android.providers.media.util.ExifUtils;
import com.android.providers.media.util.FileUtils;
@@ -270,10 +271,10 @@
}
@Override
- public void onDetachVolume(String volumeName) {
+ public void onDetachVolume(MediaVolume volume) {
synchronized (mActiveScans) {
for (Scan scan : mActiveScans) {
- if (volumeName.equals(scan.mVolumeName)) {
+ if (volume.equals(scan.mVolume)) {
scan.mSignal.cancel();
}
}
@@ -322,6 +323,7 @@
private final File mRoot;
private final int mReason;
+ private final MediaVolume mVolume;
private final String mVolumeName;
private final Uri mFilesUri;
private final CancellationSignal mSignal;
@@ -364,7 +366,13 @@
mRoot = root;
mReason = reason;
- mVolumeName = FileUtils.getVolumeName(mContext, root);
+
+ if (FileUtils.contains(Environment.getStorageDirectory(), root)) {
+ mVolume = MediaVolume.fromStorageVolume(FileUtils.getStorageVolume(mContext, root));
+ } else {
+ mVolume = MediaVolume.fromInternal();
+ }
+ mVolumeName = mVolume.getName();
mFilesUri = MediaStore.Files.getContentUri(mVolumeName);
mSignal = new CancellationSignal();
diff --git a/src/com/android/providers/media/scan/NullMediaScanner.java b/src/com/android/providers/media/scan/NullMediaScanner.java
index 3f84109..7a1a396 100644
--- a/src/com/android/providers/media/scan/NullMediaScanner.java
+++ b/src/com/android/providers/media/scan/NullMediaScanner.java
@@ -23,6 +23,8 @@
import androidx.annotation.Nullable;
+import com.android.providers.media.MediaVolume;
+
import java.io.File;
/**
@@ -61,7 +63,7 @@
}
@Override
- public void onDetachVolume(String volumeName) {
+ public void onDetachVolume(MediaVolume volume) {
// Ignored
}
diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java
index d1e946b..cf7c23a 100644
--- a/src/com/android/providers/media/util/FileUtils.java
+++ b/src/com/android/providers/media/util/FileUtils.java
@@ -45,9 +45,11 @@
import android.content.ClipDescription;
import android.content.ContentValues;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.provider.MediaStore;
@@ -859,16 +861,39 @@
}
/**
+ * Return StorageVolume corresponding to the file on Path
+ */
+ public static @NonNull StorageVolume getStorageVolume(@NonNull Context context,
+ @NonNull File path) throws FileNotFoundException {
+ int userId = extractUserId(path.getPath());
+ Context userContext = context;
+ if (userId >= 0 && (context.getUser().getIdentifier() != userId)) {
+ // This volume is for a different user than our context, create a context
+ // for that user to retrieve the correct volume.
+ try {
+ userContext = context.createPackageContextAsUser("system", 0,
+ UserHandle.of(userId));
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new FileNotFoundException("Can't get package context for user " + userId);
+ }
+ }
+
+ StorageVolume volume = userContext.getSystemService(StorageManager.class)
+ .getStorageVolume(path);
+ if (volume == null) {
+ throw new FileNotFoundException("Can't find volume for " + path.getPath());
+ }
+
+ return volume;
+ }
+
+ /**
* Return volume name which hosts the given path.
*/
public static @NonNull String getVolumeName(@NonNull Context context, @NonNull File path)
throws FileNotFoundException {
if (contains(Environment.getStorageDirectory(), path)) {
- StorageVolume volume = context.getSystemService(StorageManager.class)
- .getStorageVolume(path);
- if (volume == null) {
- throw new FileNotFoundException("Can't find volume for " + path.getPath());
- }
+ StorageVolume volume = getStorageVolume(context, path);
return volume.getMediaStoreVolumeName();
} else {
return MediaStore.VOLUME_INTERNAL;
diff --git a/tests/src/com/android/providers/media/DatabaseHelperTest.java b/tests/src/com/android/providers/media/DatabaseHelperTest.java
index 1757ad3..a159fd0 100644
--- a/tests/src/com/android/providers/media/DatabaseHelperTest.java
+++ b/tests/src/com/android/providers/media/DatabaseHelperTest.java
@@ -27,6 +27,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -69,6 +70,8 @@
@Before
public void setUp() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS);
final Context context = InstrumentationRegistry.getTargetContext();
sIsolatedContext = new IsolatedContext(context, TAG, /*asFuseThread*/ false);
sIsolatedResolver = sIsolatedContext.getContentResolver();
diff --git a/tests/src/com/android/providers/media/IdleServiceTest.java b/tests/src/com/android/providers/media/IdleServiceTest.java
index 9b0b169..064a4f8 100644
--- a/tests/src/com/android/providers/media/IdleServiceTest.java
+++ b/tests/src/com/android/providers/media/IdleServiceTest.java
@@ -90,6 +90,11 @@
final Context context = InstrumentationRegistry.getTargetContext();
final ContentResolver resolver = context.getContentResolver();
+ // Previous tests (like DatabaseHelperTest) may have left stale
+ // .database_uuid files, do an idle run first to clean them up.
+ runIdleMaintenance(resolver);
+ MediaStore.waitForIdle(resolver);
+
final File dir = Environment.getExternalStorageDirectory();
final File mediaDir = context.getExternalMediaDirs()[0];
diff --git a/tests/src/com/android/providers/media/MediaDocumentsProviderTest.java b/tests/src/com/android/providers/media/MediaDocumentsProviderTest.java
index 2db98b9..f9754e8 100644
--- a/tests/src/com/android/providers/media/MediaDocumentsProviderTest.java
+++ b/tests/src/com/android/providers/media/MediaDocumentsProviderTest.java
@@ -63,7 +63,8 @@
public void setUp() {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
- Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+ Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ Manifest.permission.INTERACT_ACROSS_USERS);
}
@After
diff --git a/tests/src/com/android/providers/media/MediaProviderForFuseTest.java b/tests/src/com/android/providers/media/MediaProviderForFuseTest.java
index afc57be..7de9834 100644
--- a/tests/src/com/android/providers/media/MediaProviderForFuseTest.java
+++ b/tests/src/com/android/providers/media/MediaProviderForFuseTest.java
@@ -58,7 +58,8 @@
InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
Manifest.permission.LOG_COMPAT_CHANGE,
Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
- Manifest.permission.UPDATE_APP_OPS_STATS);
+ Manifest.permission.UPDATE_APP_OPS_STATS,
+ Manifest.permission.INTERACT_ACROSS_USERS);
final Context context = InstrumentationRegistry.getTargetContext();
sIsolatedContext = new IsolatedContext(context, "modern", /*asFuseThread*/ true);
diff --git a/tests/src/com/android/providers/media/MediaProviderTest.java b/tests/src/com/android/providers/media/MediaProviderTest.java
index b7e2eb2..ef2c44b 100644
--- a/tests/src/com/android/providers/media/MediaProviderTest.java
+++ b/tests/src/com/android/providers/media/MediaProviderTest.java
@@ -44,6 +44,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
@@ -107,6 +108,7 @@
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ Manifest.permission.READ_DEVICE_CONFIG,
Manifest.permission.INTERACT_ACROSS_USERS);
final Context context = InstrumentationRegistry.getTargetContext();
@@ -625,6 +627,10 @@
}
};
+ final ProviderInfo info = sIsolatedContext.getPackageManager()
+ .resolveContentProvider(MediaStore.AUTHORITY, PackageManager.GET_META_DATA);
+ // Attach providerInfo, to make sure mCallingIdentity can be populated
+ provider.attachInfo(sIsolatedContext, info);
final Uri uri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
final ContentValues values = new ContentValues();
@@ -1047,6 +1053,10 @@
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
};
+ final ProviderInfo info = sIsolatedContext.getPackageManager()
+ .resolveContentProvider(MediaStore.AUTHORITY, PackageManager.GET_META_DATA);
+ // Attach providerInfo, to make sure mCallingIdentity can be populated
+ provider.attachInfo(sIsolatedContext, info);
provider.ensureFileColumns(uri, values);
assertMimetype(values, "image/png");
diff --git a/tests/src/com/android/providers/media/ResolvePlaylistTest.java b/tests/src/com/android/providers/media/ResolvePlaylistTest.java
index 0bd2850..0f6c50a 100644
--- a/tests/src/com/android/providers/media/ResolvePlaylistTest.java
+++ b/tests/src/com/android/providers/media/ResolvePlaylistTest.java
@@ -58,7 +58,8 @@
final Context context = InstrumentationRegistry.getTargetContext();
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
- Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+ Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ Manifest.permission.INTERACT_ACROSS_USERS);
mDir = new File(context.getExternalMediaDirs()[0], "test_" + System.nanoTime());
mDir.mkdirs();
diff --git a/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java b/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java
index af13fb8..cf9cb39 100644
--- a/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java
@@ -54,7 +54,7 @@
} catch (UnsupportedOperationException expected) {
}
try {
- scanner.onDetachVolume(MediaStore.VOLUME_EXTERNAL_PRIMARY);
+ scanner.onDetachVolume(null);
fail();
} catch (UnsupportedOperationException expected) {
}
diff --git a/tests/src/com/android/providers/media/scan/MediaScannerTest.java b/tests/src/com/android/providers/media/scan/MediaScannerTest.java
index 67d118d..98cbdcc 100644
--- a/tests/src/com/android/providers/media/scan/MediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/MediaScannerTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertEquals;
+import android.Manifest;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
@@ -143,6 +144,8 @@
@Before
public void setUp() {
final Context context = InstrumentationRegistry.getTargetContext();
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.INTERACT_ACROSS_USERS);
mLegacy = new LegacyMediaScanner(
new IsolatedContext(context, "legacy", /*asFuseThread*/ false));
diff --git a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
index 1834ac8..3d636cc 100644
--- a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
@@ -114,7 +114,8 @@
final Context context = InstrumentationRegistry.getTargetContext();
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
- Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+ Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ Manifest.permission.INTERACT_ACROSS_USERS);
mDir = new File(context.getExternalMediaDirs()[0], "test_" + System.nanoTime());
mDir.mkdirs();
diff --git a/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java b/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java
index 44528ba..063f1b7 100644
--- a/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java
@@ -41,6 +41,6 @@
scanner.scanFile(new File("/dev/null"), MediaScanner.REASON_UNKNOWN,
InstrumentationRegistry.getContext().getPackageName());
- scanner.onDetachVolume(MediaStore.VOLUME_EXTERNAL_PRIMARY);
+ scanner.onDetachVolume(null);
}
}