Adjust policy for "stale" volumes outside module.

We're not ready to commit to VolumeRecord in an API surface, so the
next best choice we have is to place the logic in MediaStore, which
is outside the Mainline module boundary.

This also has the benefit of giving partners control over exactly
what policy they want to use for expiring stale volumes.

Bug: 137890034
Test: atest --test-mapping packages/providers/MediaProvider
Change-Id: I677e4bfa6d08e32775e02c323debbaa90acf632b
diff --git a/api/system-current.txt b/api/system-current.txt
index c7bde93..e3ed2e8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6980,6 +6980,11 @@
     field public static final int FLAG_REMOVABLE_USB = 524288; // 0x80000
   }
 
+  public final class MediaStore {
+    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
+    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
+  }
+
   public abstract class SearchIndexableData {
     ctor public SearchIndexableData();
     ctor public SearchIndexableData(android.content.Context);
diff --git a/api/test-current.txt b/api/test-current.txt
index 3ce8c5e..3e14469 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2455,7 +2455,7 @@
     method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
     method @NonNull public static java.io.File getVolumePath(@NonNull String) throws java.io.FileNotFoundException;
-    method @NonNull public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
+    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
     method public static android.net.Uri scanFile(android.content.Context, java.io.File);
     method public static android.net.Uri scanFileFromShell(android.content.Context, java.io.File);
     method public static void scanVolume(android.content.Context, java.io.File);
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 2c53025..1e11f4a 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -27,6 +27,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
@@ -62,6 +63,7 @@
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
+import android.os.storage.VolumeRecord;
 import android.service.media.CameraPrewarmService;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -3603,6 +3605,43 @@
     }
 
     /**
+     * Return list of all specific volume names that have recently been part of
+     * {@link #VOLUME_EXTERNAL}.
+     * <p>
+     * This includes both currently mounted volumes <em>and</em> recently
+     * mounted (but currently unmounted) volumes. Any indexed metadata for these
+     * volumes is preserved to optimize the speed of remounting at a later time.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
+    public static @NonNull Set<String> getRecentExternalVolumeNames(@NonNull Context context) {
+        final StorageManager sm = context.getSystemService(StorageManager.class);
+
+        // We always have primary storage
+        final Set<String> volumeNames = new ArraySet<>();
+        volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
+
+        final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
+        for (VolumeRecord rec : sm.getVolumeRecords()) {
+            // Skip volumes without valid UUIDs
+            if (TextUtils.isEmpty(rec.fsUuid)) continue;
+
+            final VolumeInfo vi = sm.findVolumeByUuid(rec.fsUuid);
+            if (vi != null && vi.isVisibleForUser(UserHandle.myUserId())
+                    && vi.isMountedReadable()) {
+                // We're mounted right now
+                volumeNames.add(rec.getNormalizedFsUuid());
+            } else if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
+                // We're not mounted right now, but we've been seen recently
+                volumeNames.add(rec.getNormalizedFsUuid());
+            }
+        }
+        return volumeNames;
+    }
+
+    /**
      * Return the volume name that the given {@link Uri} references.
      */
     public static @NonNull String getVolumeName(@NonNull Uri uri) {
@@ -3701,6 +3740,8 @@
      * @hide
      */
     @TestApi
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
     public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
             throws FileNotFoundException {
         if (TextUtils.isEmpty(volumeName)) {