Merge "Use new API for shouldBypass" into sc-dev
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index 103e09e..2a17e34 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -928,13 +928,19 @@
             @NonNull ParcelFileDescriptor fileDescriptor) throws IOException {
         Bundle input = new Bundle();
         input.putParcelable(EXTRA_FILE_DESCRIPTOR, fileDescriptor);
+        ParcelFileDescriptor pfd;
         try {
             Bundle output = context.getContentResolver().call(AUTHORITY,
                     GET_ORIGINAL_MEDIA_FORMAT_FILE_DESCRIPTOR_CALL, null, input);
-            return output.getParcelable(EXTRA_FILE_DESCRIPTOR);
+            pfd = output.getParcelable(EXTRA_FILE_DESCRIPTOR);
         } catch (Exception e) {
             throw new IOException(e);
         }
+
+        if (pfd == null) {
+            throw new IOException("Input file descriptor already original");
+        }
+        return pfd;
     }
 
     /**
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-de/strings.xml b/res/values-de/strings.xml
index 175ab92..6a0001a 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -172,18 +172,11 @@
       <item quantity="one">Element wird gelöscht…</item>
     </plurals>
     <string name="transcode_denied" msgid="6760546817138288976">"Die App „<xliff:g id="APP_NAME">%s</xliff:g>“ kann Mediendateien nicht verarbeiten"</string>
-    <!-- no translation found for transcode_processing_cancelled (5340383917746945590) -->
-    <skip />
-    <!-- no translation found for transcode_processing_error (8921643164508407874) -->
-    <skip />
-    <!-- no translation found for transcode_processing_success (447288876429730122) -->
-    <skip />
-    <!-- no translation found for transcode_processing_started (7789086308155361523) -->
-    <skip />
-    <!-- no translation found for transcode_processing (6753136468864077258) -->
-    <skip />
-    <!-- no translation found for transcode_cancel (8555752601907598192) -->
-    <skip />
-    <!-- no translation found for transcode_wait (8909773149560697501) -->
-    <skip />
+    <string name="transcode_processing_cancelled" msgid="5340383917746945590">"Medienverarbeitung abgebrochen"</string>
+    <string name="transcode_processing_error" msgid="8921643164508407874">"Fehler bei Medienverarbeitung"</string>
+    <string name="transcode_processing_success" msgid="447288876429730122">"Medienverarbeitung erfolgreich"</string>
+    <string name="transcode_processing_started" msgid="7789086308155361523">"Medienverarbeitung gestartet"</string>
+    <string name="transcode_processing" msgid="6753136468864077258">"Medien werden verarbeitet…"</string>
+    <string name="transcode_cancel" msgid="8555752601907598192">"Abbrechen"</string>
+    <string name="transcode_wait" msgid="8909773149560697501">"Warten"</string>
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index b0ea764..3cffaf6 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -172,18 +172,11 @@
       <item quantity="other"><xliff:g id="COUNT">^1</xliff:g> આઇટમ ડિલીટ કરી રહ્યાં છીએ…</item>
     </plurals>
     <string name="transcode_denied" msgid="6760546817138288976">"<xliff:g id="APP_NAME">%s</xliff:g> મીડિયા ફાઇલો પર પ્રક્રિયા કરી શકતું નથી"</string>
-    <!-- no translation found for transcode_processing_cancelled (5340383917746945590) -->
-    <skip />
-    <!-- no translation found for transcode_processing_error (8921643164508407874) -->
-    <skip />
-    <!-- no translation found for transcode_processing_success (447288876429730122) -->
-    <skip />
-    <!-- no translation found for transcode_processing_started (7789086308155361523) -->
-    <skip />
-    <!-- no translation found for transcode_processing (6753136468864077258) -->
-    <skip />
-    <!-- no translation found for transcode_cancel (8555752601907598192) -->
-    <skip />
-    <!-- no translation found for transcode_wait (8909773149560697501) -->
-    <skip />
+    <string name="transcode_processing_cancelled" msgid="5340383917746945590">"મીડિયા પર થતી પ્રક્રિયા રદ કરવામાં આવી"</string>
+    <string name="transcode_processing_error" msgid="8921643164508407874">"મીડિયા પર થતી પ્રક્રિયામાં ભૂલ આવી"</string>
+    <string name="transcode_processing_success" msgid="447288876429730122">"મીડિયા પર પ્રક્રિયા કરવાનું સફળ થયું"</string>
+    <string name="transcode_processing_started" msgid="7789086308155361523">"મીડિયા પર પ્રક્રિયા શરૂ કરવામાં આવી"</string>
+    <string name="transcode_processing" msgid="6753136468864077258">"મીડિયા પર પ્રક્રિયા થઈ રહી છે…"</string>
+    <string name="transcode_cancel" msgid="8555752601907598192">"રદ કરો"</string>
+    <string name="transcode_wait" msgid="8909773149560697501">"રાહ જુઓ"</string>
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index cd9fdfb..ca1da24 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -172,18 +172,11 @@
       <item quantity="other"><xliff:g id="COUNT">^1</xliff:g> ಐಟಂಗಳನ್ನು ಅಳಿಸಲಾಗುತ್ತಿದೆ…</item>
     </plurals>
     <string name="transcode_denied" msgid="6760546817138288976">"<xliff:g id="APP_NAME">%s</xliff:g> ಮಾಧ್ಯಮ ಫೈಲ್‌ಗಳನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
-    <!-- no translation found for transcode_processing_cancelled (5340383917746945590) -->
-    <skip />
-    <!-- no translation found for transcode_processing_error (8921643164508407874) -->
-    <skip />
-    <!-- no translation found for transcode_processing_success (447288876429730122) -->
-    <skip />
-    <!-- no translation found for transcode_processing_started (7789086308155361523) -->
-    <skip />
-    <!-- no translation found for transcode_processing (6753136468864077258) -->
-    <skip />
-    <!-- no translation found for transcode_cancel (8555752601907598192) -->
-    <skip />
-    <!-- no translation found for transcode_wait (8909773149560697501) -->
-    <skip />
+    <string name="transcode_processing_cancelled" msgid="5340383917746945590">"ಮಾಧ್ಯಮ ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುವಿಕೆ ರದ್ದುಗೊಂಡಿದೆ"</string>
+    <string name="transcode_processing_error" msgid="8921643164508407874">"ಮಾಧ್ಯಮ ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುವಿಕೆ ದೋಷ"</string>
+    <string name="transcode_processing_success" msgid="447288876429730122">"ಮಾಧ್ಯಮ ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುವಿಕೆ ಯಶಸ್ವಿಯಾಗಿದೆ"</string>
+    <string name="transcode_processing_started" msgid="7789086308155361523">"ಮಾಧ್ಯಮ ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುವಿಕೆ ಪ್ರಾರಂಭವಾಗಿದೆ"</string>
+    <string name="transcode_processing" msgid="6753136468864077258">"ಪ್ರಕ್ರಿಯೆಯಲ್ಲಿರುವ ಮಾಧ್ಯಮ…"</string>
+    <string name="transcode_cancel" msgid="8555752601907598192">"ರದ್ದುಮಾಡಿ"</string>
+    <string name="transcode_wait" msgid="8909773149560697501">"ನಿರೀಕ್ಷಿಸಿ"</string>
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 1f2f068..ea47c97 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -171,20 +171,12 @@
       <item quantity="other"><xliff:g id="COUNT">^1</xliff:g> वटा वस्तु मेटाइँदै छन्…</item>
       <item quantity="one">वस्तु मेटाइँदै छ…</item>
     </plurals>
-    <!-- no translation found for transcode_denied (6760546817138288976) -->
-    <skip />
-    <!-- no translation found for transcode_processing_cancelled (5340383917746945590) -->
-    <skip />
-    <!-- no translation found for transcode_processing_error (8921643164508407874) -->
-    <skip />
-    <!-- no translation found for transcode_processing_success (447288876429730122) -->
-    <skip />
-    <!-- no translation found for transcode_processing_started (7789086308155361523) -->
-    <skip />
-    <!-- no translation found for transcode_processing (6753136468864077258) -->
-    <skip />
-    <!-- no translation found for transcode_cancel (8555752601907598192) -->
-    <skip />
-    <!-- no translation found for transcode_wait (8909773149560697501) -->
-    <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>
+    <string name="transcode_processing_started" msgid="7789086308155361523">"मिडिया प्रोसेस गर्न थालियो"</string>
+    <string name="transcode_processing" msgid="6753136468864077258">"मिडिया प्रोसेस गरिँदै छ…"</string>
+    <string name="transcode_cancel" msgid="8555752601907598192">"रद्द गर्नुहोस्"</string>
+    <string name="transcode_wait" msgid="8909773149560697501">"पर्खनुहोस्"</string>
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 7b1448e..d69a10c 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -172,18 +172,11 @@
       <item quantity="one">Një artikull po fshihet…</item>
     </plurals>
     <string name="transcode_denied" msgid="6760546817138288976">"<xliff:g id="APP_NAME">%s</xliff:g> nuk mund t\'i përpunojë skedarët e medias"</string>
-    <!-- no translation found for transcode_processing_cancelled (5340383917746945590) -->
-    <skip />
-    <!-- no translation found for transcode_processing_error (8921643164508407874) -->
-    <skip />
-    <!-- no translation found for transcode_processing_success (447288876429730122) -->
-    <skip />
-    <!-- no translation found for transcode_processing_started (7789086308155361523) -->
-    <skip />
-    <!-- no translation found for transcode_processing (6753136468864077258) -->
-    <skip />
-    <!-- no translation found for transcode_cancel (8555752601907598192) -->
-    <skip />
-    <!-- no translation found for transcode_wait (8909773149560697501) -->
-    <skip />
+    <string name="transcode_processing_cancelled" msgid="5340383917746945590">"Përpunimi i medias u anulua"</string>
+    <string name="transcode_processing_error" msgid="8921643164508407874">"Gabim i përpunimit të medias"</string>
+    <string name="transcode_processing_success" msgid="447288876429730122">"Përpunimi i medias u krye me sukses"</string>
+    <string name="transcode_processing_started" msgid="7789086308155361523">"Përpunimi i medias ka filluar"</string>
+    <string name="transcode_processing" msgid="6753136468864077258">"Media po përpunohet…"</string>
+    <string name="transcode_cancel" msgid="8555752601907598192">"Anulo"</string>
+    <string name="transcode_wait" msgid="8909773149560697501">"Prit"</string>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 7f2e66f..2b14c3e 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -172,18 +172,11 @@
       <item quantity="one">ఐటెమ్‌ను తొలగిస్తోంది…</item>
     </plurals>
     <string name="transcode_denied" msgid="6760546817138288976">"<xliff:g id="APP_NAME">%s</xliff:g> మీడియా ఫైల్‌లను ప్రాసెస్ చేయదు"</string>
-    <!-- no translation found for transcode_processing_cancelled (5340383917746945590) -->
-    <skip />
-    <!-- no translation found for transcode_processing_error (8921643164508407874) -->
-    <skip />
-    <!-- no translation found for transcode_processing_success (447288876429730122) -->
-    <skip />
-    <!-- no translation found for transcode_processing_started (7789086308155361523) -->
-    <skip />
-    <!-- no translation found for transcode_processing (6753136468864077258) -->
-    <skip />
-    <!-- no translation found for transcode_cancel (8555752601907598192) -->
-    <skip />
-    <!-- no translation found for transcode_wait (8909773149560697501) -->
-    <skip />
+    <string name="transcode_processing_cancelled" msgid="5340383917746945590">"మీడియా ప్రాసెసింగ్ రద్దు చేయబడింది"</string>
+    <string name="transcode_processing_error" msgid="8921643164508407874">"మీడియా ప్రాసెసింగ్‌లో ఎర్రర్ ఏర్పడింది"</string>
+    <string name="transcode_processing_success" msgid="447288876429730122">"మీడియా ప్రాసెసింగ్ విజయవంతమైంది"</string>
+    <string name="transcode_processing_started" msgid="7789086308155361523">"మీడియా ప్రాసెసింగ్ ప్రారంభమైంది"</string>
+    <string name="transcode_processing" msgid="6753136468864077258">"మీడియాను ప్రాసెస్ చేస్తోంది…"</string>
+    <string name="transcode_cancel" msgid="8555752601907598192">"రద్దు చేయి"</string>
+    <string name="transcode_wait" msgid="8909773149560697501">"వేచి ఉండు"</string>
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index b8ce3b6..63488cf 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -172,18 +172,11 @@
       <item quantity="one">آئٹم حذف کیا جا رہا ہے…</item>
     </plurals>
     <string name="transcode_denied" msgid="6760546817138288976">"<xliff:g id="APP_NAME">%s</xliff:g> میڈیا کی فائلز پر کارروائی نہیں کر سکتی"</string>
-    <!-- no translation found for transcode_processing_cancelled (5340383917746945590) -->
-    <skip />
-    <!-- no translation found for transcode_processing_error (8921643164508407874) -->
-    <skip />
-    <!-- no translation found for transcode_processing_success (447288876429730122) -->
-    <skip />
-    <!-- no translation found for transcode_processing_started (7789086308155361523) -->
-    <skip />
-    <!-- no translation found for transcode_processing (6753136468864077258) -->
-    <skip />
-    <!-- no translation found for transcode_cancel (8555752601907598192) -->
-    <skip />
-    <!-- no translation found for transcode_wait (8909773149560697501) -->
-    <skip />
+    <string name="transcode_processing_cancelled" msgid="5340383917746945590">"میڈیا پر کارروائی منسوخ ہو گئی"</string>
+    <string name="transcode_processing_error" msgid="8921643164508407874">"میڈیا پر کارروائی کرنے میں خرابی"</string>
+    <string name="transcode_processing_success" msgid="447288876429730122">"میڈیا پر کارروائی کامیاب ہو گئی"</string>
+    <string name="transcode_processing_started" msgid="7789086308155361523">"میڈیا پر کارروائی شروع ہو گئی"</string>
+    <string name="transcode_processing" msgid="6753136468864077258">"میڈیا پر کارروائی ہو رہی ہے…"</string>
+    <string name="transcode_cancel" msgid="8555752601907598192">"منسوخ کریں"</string>
+    <string name="transcode_wait" msgid="8909773149560697501">"انتظار کریں"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index dd13c82..b1f7f7f 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -172,18 +172,11 @@
       <item quantity="one">正在删除内容…</item>
     </plurals>
     <string name="transcode_denied" msgid="6760546817138288976">"<xliff:g id="APP_NAME">%s</xliff:g>无法处理媒体文件"</string>
-    <!-- no translation found for transcode_processing_cancelled (5340383917746945590) -->
-    <skip />
-    <!-- no translation found for transcode_processing_error (8921643164508407874) -->
-    <skip />
-    <!-- no translation found for transcode_processing_success (447288876429730122) -->
-    <skip />
-    <!-- no translation found for transcode_processing_started (7789086308155361523) -->
-    <skip />
-    <!-- no translation found for transcode_processing (6753136468864077258) -->
-    <skip />
-    <!-- no translation found for transcode_cancel (8555752601907598192) -->
-    <skip />
-    <!-- no translation found for transcode_wait (8909773149560697501) -->
-    <skip />
+    <string name="transcode_processing_cancelled" msgid="5340383917746945590">"已取消处理媒体内容"</string>
+    <string name="transcode_processing_error" msgid="8921643164508407874">"处理媒体内容时出错"</string>
+    <string name="transcode_processing_success" msgid="447288876429730122">"已成功处理媒体内容"</string>
+    <string name="transcode_processing_started" msgid="7789086308155361523">"已开始处理媒体内容"</string>
+    <string name="transcode_processing" msgid="6753136468864077258">"正在处理媒体内容…"</string>
+    <string name="transcode_cancel" msgid="8555752601907598192">"取消"</string>
+    <string name="transcode_wait" msgid="8909773149560697501">"等待"</string>
 </resources>
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index b684091..d19627c 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 5bb4070..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;
                         }
                     }
@@ -5766,10 +5697,12 @@
                         extras.getParcelable(MediaStore.EXTRA_FILE_DESCRIPTOR);
                 try {
                     File file = getFileFromFileDescriptor(inputPfd);
-                    boolean supportsTranscode = mTranscodeHelper.supportsTranscode(file.getPath());
-                    if (!supportsTranscode) {
-                        throw new IllegalArgumentException(
-                                "Input file descriptor is already original");
+                    boolean isModernFormat = mTranscodeHelper.isModernFormat(file.getPath());
+                    if (!isModernFormat) {
+                        // Return an empty bundle instead of throwing an exception in the special
+                        // case where the file is not a modern format. This avoids a misleading
+                        // warning in android.database.DatabaseUtils#writeExceptionToParcel
+                        return new Bundle();
                     }
 
                     FuseDaemon fuseDaemon = getFuseDaemonForFile(file);
@@ -5992,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;
             }
 
@@ -6139,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),
@@ -9278,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);
             }
         }
@@ -9314,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;
@@ -9358,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");
         }
@@ -9385,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<>();
 
@@ -9732,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/TranscodeHelper.java b/src/com/android/providers/media/TranscodeHelper.java
index 596db57..e1ffbca 100644
--- a/src/com/android/providers/media/TranscodeHelper.java
+++ b/src/com/android/providers/media/TranscodeHelper.java
@@ -792,7 +792,19 @@
         return isHevc(mimeType) || isHdr10Plus(colorStandard, colorTransfer);
     }
 
-    public boolean supportsTranscode(String path) {
+    public boolean isModernFormat(String filePath) {
+        if (supportsTranscode(filePath)) {
+            Pair<Integer, Long> result = getFileFlagsAndDurationMs(filePath);
+            int fileFlags = result.first;
+            if (fileFlags != 0) {
+                // File is HEVC or HDR
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean supportsTranscode(String path) {
         File file = new File(path);
         String name = file.getName();
         final String cameraRelativePath =
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);
     }
 }