Avoid creating default folders inside StubVolumes

StubVolumes are managed from outside Android (e.g. from Chrome OS), and
thus default folders like Music, Movies, Pictures, etc. should not be
automatically created inside them.
This CL achieves it by skipping default folders creation in StubVolumes
at ensureDefaultFolders() and ensureThumbnailsValid(). Whether a given
volume is a StubVolume is decided by looking up the return value of
StorageVolume.isStub(), which is available in the SDK level T and above.

Bug: 206019156
Test: m

Change-Id: I7406f1b3eef545407f1c017f6e9cca0f8c280dc8
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index acd25b9..09aa1b4 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -861,11 +861,16 @@
     }
 
     /**
-     * Ensure that default folders are created on mounted primary storage
-     * devices. We only do this once per volume so we don't annoy the user if
-     * deleted manually.
+     * Ensure that default folders are created on mounted storage devices.
+     * We only do this once per volume so we don't annoy the user if deleted
+     * manually.
      */
     private void ensureDefaultFolders(@NonNull MediaVolume volume, @NonNull SQLiteDatabase db) {
+        if (volume.isStub()) {
+            // StubVolumes are managed from outside Android (e.g. from Chrome OS). So default
+            // folders should not be automatically created inside them.
+            return;
+        }
         final String key = "created_default_folders_" + volume.getId();
 
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
@@ -891,6 +896,12 @@
      * disk, then all thumbnails will be considered stable and will be deleted.
      */
     private void ensureThumbnailsValid(@NonNull MediaVolume volume, @NonNull SQLiteDatabase db) {
+        if (volume.isStub()) {
+            // StubVolumes are managed from outside Android (e.g. from Chrome OS). So default
+            // folders and thumbnail directories should not be automatically created inside them,
+            // and there is no need to eusure the validity of thumbnails here.
+            return;
+        }
         final String uuidFromDatabase = DatabaseHelper.getOrCreateUuid(db);
         try {
             for (File dir : getThumbnailDirectories(volume)) {
diff --git a/src/com/android/providers/media/MediaVolume.java b/src/com/android/providers/media/MediaVolume.java
index bceeaeb..c955d0a 100644
--- a/src/com/android/providers/media/MediaVolume.java
+++ b/src/com/android/providers/media/MediaVolume.java
@@ -25,6 +25,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import java.io.File;
 import java.util.Objects;
 
@@ -60,6 +62,11 @@
      */
     private final @Nullable String mId;
 
+    /**
+     * Whether the volume is a stub volume (a volume managed from outside Android).
+     */
+    private final boolean mStub;
+
     public @NonNull String getName() {
         return mName;
     }
@@ -76,11 +83,17 @@
         return mId;
     }
 
-    private MediaVolume (@NonNull String name, UserHandle user, File path, String id) {
+    public boolean isStub() {
+        return mStub;
+    }
+
+    private MediaVolume (@NonNull String name, UserHandle user, File path, String id,
+                         boolean stub) {
         this.mName = name;
         this.mUser = user;
         this.mPath = path;
         this.mId = id;
+        this.mStub = stub;
     }
 
     private MediaVolume (Parcel in) {
@@ -88,6 +101,7 @@
         this.mUser = in.readParcelable(null);
         this.mPath  = new File(in.readString());
         this.mId = in.readString();
+        this.mStub = in.readInt() != 0;
     }
 
     @Override
@@ -101,12 +115,13 @@
         // 2. A volume with a certain ID should never be mounted in two different paths, anyway
         return Objects.equals(mName, that.mName) &&
                 Objects.equals(mUser, that.mUser) &&
-                Objects.equals(mId, that.mId);
+                Objects.equals(mId, that.mId) &&
+                (mStub == that.mStub);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mName, mUser, mId);
+        return Objects.hash(mName, mUser, mId, mStub);
     }
 
     public boolean isVisibleToUser(UserHandle user) {
@@ -119,13 +134,14 @@
         UserHandle user = storageVolume.getOwner();
         File path = storageVolume.getDirectory();
         String id = storageVolume.getId();
-        return new MediaVolume(name, user, path, id);
+        boolean stub = SdkLevel.isAtLeastT() ? storageVolume.isStub() : false;
+        return new MediaVolume(name, user, path, id, stub);
     }
 
     public static MediaVolume fromInternal() {
         String name = MediaStore.VOLUME_INTERNAL;
 
-        return new MediaVolume(name, null, null, null);
+        return new MediaVolume(name, null, null, null, false);
     }
 
     @Override
@@ -139,12 +155,13 @@
         dest.writeParcelable(mUser, flags);
         dest.writeString(mPath.toString());
         dest.writeString(mId);
+        dest.writeInt(mStub ? 1 : 0);
     }
 
     @Override
     public String toString() {
         return "MediaVolume name: [" + mName + "] id: [" + mId + "] user: [" + mUser + "] path: ["
-                + mPath + "]";
+                + mPath + "] stub: [" + mStub + "]";
     }
 
     public static final @android.annotation.NonNull Creator<MediaVolume> CREATOR