Volumes know parent disks; unsupported disks.

This is cleaner and more direct than the reverse of having the disk
publish child volume membership.  Rename state constants to match
public API.  Add state representing bad removal.  Make it clear that
volume flags are related to mounting.

Send new unsupported disk event when we finish scanning an entire
disk and have no meaningful volumes.

Splice disk labels into description when known.  Only adoptable
slots are directly visible to apps.

Bug: 19993667
Change-Id: I12fda95be0d82781f70c3d85c039749052dc936b
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java
index 2c60d41..2f63988 100644
--- a/core/java/android/os/storage/DiskInfo.java
+++ b/core/java/android/os/storage/DiskInfo.java
@@ -20,6 +20,7 @@
 import android.content.res.Resources;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.DebugUtils;
 
 import com.android.internal.util.IndentingPrintWriter;
@@ -46,7 +47,6 @@
     public final int flags;
     public long size;
     public String label;
-    public String[] volumeIds;
 
     public DiskInfo(String id, int flags) {
         this.id = Preconditions.checkNotNull(id);
@@ -58,7 +58,6 @@
         flags = parcel.readInt();
         size = parcel.readLong();
         label = parcel.readString();
-        volumeIds = parcel.readStringArray();
     }
 
     public @NonNull String getId() {
@@ -66,11 +65,19 @@
     }
 
     public String getDescription() {
-        // TODO: splice vendor label into these strings
+        final Resources res = Resources.getSystem();
         if ((flags & FLAG_SD) != 0) {
-            return Resources.getSystem().getString(com.android.internal.R.string.storage_sd_card);
+            if (TextUtils.isEmpty(label)) {
+                return res.getString(com.android.internal.R.string.storage_sd_card);
+            } else {
+                return res.getString(com.android.internal.R.string.storage_sd_card_label, label);
+            }
         } else if ((flags & FLAG_USB) != 0) {
-            return Resources.getSystem().getString(com.android.internal.R.string.storage_usb);
+            if (TextUtils.isEmpty(label)) {
+                return res.getString(com.android.internal.R.string.storage_usb_drive);
+            } else {
+                return res.getString(com.android.internal.R.string.storage_usb_drive_label, label);
+            }
         } else {
             return null;
         }
@@ -96,13 +103,11 @@
     }
 
     public void dump(IndentingPrintWriter pw) {
-        pw.println("DiskInfo:");
+        pw.println("DiskInfo{" + id + "}:");
         pw.increaseIndent();
-        pw.printPair("id", id);
         pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
         pw.printPair("size", size);
         pw.printPair("label", label);
-        pw.printPair("volumeIds", volumeIds);
         pw.decreaseIndent();
         pw.println();
     }
@@ -156,6 +161,5 @@
         parcel.writeInt(this.flags);
         parcel.writeLong(size);
         parcel.writeString(label);
-        parcel.writeStringArray(volumeIds);
     }
 }
diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java
index fd914bc..8e878a4 100644
--- a/core/java/android/os/storage/IMountServiceListener.java
+++ b/core/java/android/os/storage/IMountServiceListener.java
@@ -98,6 +98,13 @@
                     reply.writeNoException();
                     return true;
                 }
+                case TRANSACTION_onDiskUnsupported: {
+                    data.enforceInterface(DESCRIPTOR);
+                    final DiskInfo disk = (DiskInfo) data.readParcelable(null);
+                    onDiskUnsupported(disk);
+                    reply.writeNoException();
+                    return true;
+                }
             }
             return super.onTransact(code, data, reply, flags);
         }
@@ -198,6 +205,22 @@
                     _data.recycle();
                 }
             }
+
+            @Override
+            public void onDiskUnsupported(DiskInfo disk) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeParcelable(disk, 0);
+                    mRemote.transact(Stub.TRANSACTION_onDiskUnsupported, _data, _reply,
+                            android.os.IBinder.FLAG_ONEWAY);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
         }
 
         static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0);
@@ -206,6 +229,7 @@
 
         static final int TRANSACTION_onVolumeStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 2);
         static final int TRANSACTION_onVolumeMetadataChanged = (IBinder.FIRST_CALL_TRANSACTION + 3);
+        static final int TRANSACTION_onDiskUnsupported = (IBinder.FIRST_CALL_TRANSACTION + 4);
     }
 
     /**
@@ -230,4 +254,6 @@
             throws RemoteException;
 
     public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException;
+
+    public void onDiskUnsupported(DiskInfo disk) throws RemoteException;
 }
diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java
index 28a187d..ad2fae0 100644
--- a/core/java/android/os/storage/StorageEventListener.java
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -43,4 +43,7 @@
 
     public void onVolumeMetadataChanged(VolumeInfo vol) {
     }
+
+    public void onDiskUnsupported(DiskInfo disk) {
+    }
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 0e977ff..b49c14e 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -87,6 +87,7 @@
         private static final int MSG_STORAGE_STATE_CHANGED = 1;
         private static final int MSG_VOLUME_STATE_CHANGED = 2;
         private static final int MSG_VOLUME_METADATA_CHANGED = 3;
+        private static final int MSG_DISK_UNSUPPORTED = 4;
 
         final StorageEventListener mCallback;
         final Handler mHandler;
@@ -113,6 +114,10 @@
                     mCallback.onVolumeMetadataChanged((VolumeInfo) args.arg1);
                     args.recycle();
                     return true;
+                case MSG_DISK_UNSUPPORTED:
+                    mCallback.onDiskUnsupported((DiskInfo) args.arg1);
+                    args.recycle();
+                    return true;
             }
             args.recycle();
             return false;
@@ -147,6 +152,13 @@
             args.arg1 = vol;
             mHandler.obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget();
         }
+
+        @Override
+        public void onDiskUnsupported(DiskInfo disk) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = disk;
+            mHandler.obtainMessage(MSG_DISK_UNSUPPORTED, args).sendToTarget();
+        }
     }
 
     /**
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index a241728..4177380 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -63,15 +63,17 @@
     public static final int TYPE_OBB = 4;
 
     public static final int STATE_UNMOUNTED = 0;
-    public static final int STATE_MOUNTING = 1;
+    public static final int STATE_CHECKING = 1;
     public static final int STATE_MOUNTED = 2;
-    public static final int STATE_FORMATTING = 3;
-    public static final int STATE_UNMOUNTING = 4;
-    public static final int STATE_UNMOUNTABLE = 5;
-    public static final int STATE_REMOVED = 6;
+    public static final int STATE_MOUNTED_READ_ONLY = 3;
+    public static final int STATE_FORMATTING = 4;
+    public static final int STATE_EJECTING = 5;
+    public static final int STATE_UNMOUNTABLE = 6;
+    public static final int STATE_REMOVED = 7;
+    public static final int STATE_BAD_REMOVAL = 8;
 
-    public static final int FLAG_PRIMARY = 1 << 0;
-    public static final int FLAG_VISIBLE = 1 << 1;
+    public static final int MOUNT_FLAG_PRIMARY = 1 << 0;
+    public static final int MOUNT_FLAG_VISIBLE = 1 << 1;
 
     public static final int USER_FLAG_INITED = 1 << 0;
     public static final int USER_FLAG_SNOOZED = 1 << 1;
@@ -97,10 +99,10 @@
 
     static {
         sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
-        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTING, Environment.MEDIA_CHECKING);
+        sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING);
         sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
         sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
-        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTING, Environment.MEDIA_EJECTING);
+        sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING);
         sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
         sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED);
 
@@ -115,8 +117,9 @@
     /** vold state */
     public final String id;
     public final int type;
-    public int flags = 0;
-    public int userId = -1;
+    public final String diskId;
+    public int mountFlags = 0;
+    public int mountUserId = -1;
     public int state = STATE_UNMOUNTED;
     public String fsType;
     public String fsUuid;
@@ -125,28 +128,28 @@
 
     /** Framework state */
     public final int mtpIndex;
-    public String diskId;
     public String nickname;
     public int userFlags = 0;
 
-    public VolumeInfo(String id, int type, int mtpIndex) {
+    public VolumeInfo(String id, int type, String diskId, int mtpIndex) {
         this.id = Preconditions.checkNotNull(id);
         this.type = type;
+        this.diskId = diskId;
         this.mtpIndex = mtpIndex;
     }
 
     public VolumeInfo(Parcel parcel) {
         id = parcel.readString();
         type = parcel.readInt();
-        flags = parcel.readInt();
-        userId = parcel.readInt();
+        diskId = parcel.readString();
+        mountFlags = parcel.readInt();
+        mountUserId = parcel.readInt();
         state = parcel.readInt();
         fsType = parcel.readString();
         fsUuid = parcel.readString();
         fsLabel = parcel.readString();
         path = parcel.readString();
         mtpIndex = parcel.readInt();
-        diskId = parcel.readString();
         nickname = parcel.readString();
         userFlags = parcel.readInt();
     }
@@ -209,11 +212,11 @@
     }
 
     public boolean isPrimary() {
-        return (flags & FLAG_PRIMARY) != 0;
+        return (mountFlags & MOUNT_FLAG_PRIMARY) != 0;
     }
 
     public boolean isVisible() {
-        return (flags & FLAG_VISIBLE) != 0;
+        return (mountFlags & MOUNT_FLAG_VISIBLE) != 0;
     }
 
     public boolean isInited() {
@@ -225,7 +228,7 @@
     }
 
     public boolean isVisibleToUser(int userId) {
-        if (type == TYPE_PUBLIC && userId == this.userId) {
+        if (type == TYPE_PUBLIC && userId == this.mountUserId) {
             return isVisible();
         } else if (type == TYPE_EMULATED) {
             return isVisible();
@@ -241,7 +244,7 @@
     public File getPathForUser(int userId) {
         if (path == null) {
             return null;
-        } else if (type == TYPE_PUBLIC && userId == this.userId) {
+        } else if (type == TYPE_PUBLIC && userId == this.mountUserId) {
             return new File(path);
         } else if (type == TYPE_EMULATED) {
             return new File(path, Integer.toString(userId));
@@ -333,12 +336,12 @@
     }
 
     public void dump(IndentingPrintWriter pw) {
-        pw.println("VolumeInfo:");
+        pw.println("VolumeInfo{" + id + "}:");
         pw.increaseIndent();
-        pw.printPair("id", id);
         pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
-        pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
-        pw.printPair("userId", userId);
+        pw.printPair("diskId", diskId);
+        pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags));
+        pw.printPair("mountUserId", mountUserId);
         pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
         pw.println();
         pw.printPair("fsType", fsType);
@@ -347,7 +350,6 @@
         pw.println();
         pw.printPair("path", path);
         pw.printPair("mtpIndex", mtpIndex);
-        pw.printPair("diskId", diskId);
         pw.printPair("nickname", nickname);
         pw.printPair("userFlags", DebugUtils.flagsToString(getClass(), "USER_FLAG_", userFlags));
         pw.decreaseIndent();
@@ -401,15 +403,15 @@
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeString(id);
         parcel.writeInt(type);
-        parcel.writeInt(this.flags);
-        parcel.writeInt(userId);
+        parcel.writeString(diskId);
+        parcel.writeInt(mountFlags);
+        parcel.writeInt(mountUserId);
         parcel.writeInt(state);
         parcel.writeString(fsType);
         parcel.writeString(fsUuid);
         parcel.writeString(fsLabel);
         parcel.writeString(path);
         parcel.writeInt(mtpIndex);
-        parcel.writeString(diskId);
         parcel.writeString(nickname);
         parcel.writeInt(userFlags);
     }
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 59366cc..345289f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3356,8 +3356,15 @@
     <!-- Storage description for internal storage. [CHAR LIMIT=NONE] -->
     <string name="storage_internal">Internal storage</string>
 
-    <!-- Storage description for the SD card. [CHAR LIMIT=NONE] -->
+    <!-- Storage description for a generic SD card. [CHAR LIMIT=NONE] -->
     <string name="storage_sd_card">SD card</string>
+    <!-- Storage description for a SD card from a specific manufacturer. [CHAR LIMIT=NONE] -->
+    <string name="storage_sd_card_label"><xliff:g id="manufacturer" example="SanDisk">%s</xliff:g> SD card</string>
+
+    <!-- Storage description for a generic USB drive. [CHAR LIMIT=NONE] -->
+    <string name="storage_usb_drive">USB drive</string>
+    <!-- Storage description for a USB drive from a specific manufacturer. [CHAR LIMIT=NONE] -->
+    <string name="storage_usb_drive_label"><xliff:g id="manufacturer" example="Seagate">%s</xliff:g> USB drive</string>
 
     <!-- Storage description for USB storage. [CHAR LIMIT=NONE] -->
     <string name="storage_usb">USB storage</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 180b415..18f11d7 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2214,6 +2214,9 @@
 
   <java-symbol type="string" name="storage_internal" />
   <java-symbol type="string" name="storage_sd_card" />
+  <java-symbol type="string" name="storage_sd_card_label" />
+  <java-symbol type="string" name="storage_usb_drive" />
+  <java-symbol type="string" name="storage_usb_drive_label" />
   <java-symbol type="string" name="storage_usb" />
 
   <!-- Floating toolbar -->
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 818f5ee..4a441c7 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -107,8 +107,8 @@
             case VolumeInfo.STATE_UNMOUNTED:
                 onVolumeUnmounted(vol);
                 break;
-            case VolumeInfo.STATE_MOUNTING:
-                onVolumeMounting(vol);
+            case VolumeInfo.STATE_CHECKING:
+                onVolumeChecking(vol);
                 break;
             case VolumeInfo.STATE_MOUNTED:
                 onVolumeMounted(vol);
@@ -116,8 +116,8 @@
             case VolumeInfo.STATE_FORMATTING:
                 onVolumeFormatting(vol);
                 break;
-            case VolumeInfo.STATE_UNMOUNTING:
-                onVolumeUnmounting(vol);
+            case VolumeInfo.STATE_EJECTING:
+                onVolumeEjecting(vol);
                 break;
             case VolumeInfo.STATE_UNMOUNTABLE:
                 onVolumeUnmountable(vol);
@@ -125,6 +125,9 @@
             case VolumeInfo.STATE_REMOVED:
                 onVolumeRemoved(vol);
                 break;
+            case VolumeInfo.STATE_BAD_REMOVAL:
+                onVolumeBadRemoval(vol);
+                break;
         }
     }
 
@@ -132,7 +135,7 @@
         // Ignored
     }
 
-    private void onVolumeMounting(VolumeInfo vol) {
+    private void onVolumeChecking(VolumeInfo vol) {
         final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
         final CharSequence title = mContext.getString(
                 R.string.ext_media_checking_notification_title, disk.getDescription());
@@ -194,7 +197,7 @@
         // Ignored
     }
 
-    private void onVolumeUnmounting(VolumeInfo vol) {
+    private void onVolumeEjecting(VolumeInfo vol) {
         final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
         final CharSequence title = mContext.getString(
                 R.string.ext_media_unmounting_notification_title, disk.getDescription());
@@ -247,6 +250,26 @@
         mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL);
     }
 
+    private void onVolumeBadRemoval(VolumeInfo vol) {
+        if (!vol.isPrimary()) {
+            // Ignore non-primary media
+            return;
+        }
+
+        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
+        final CharSequence title = mContext.getString(
+                R.string.ext_media_badremoval_notification_title, disk.getDescription());
+        final CharSequence text = mContext.getString(
+                R.string.ext_media_badremoval_notification_message, disk.getDescription());
+
+        final Notification notif = buildNotificationBuilder(title, text)
+                .setSmallIcon(R.drawable.stat_notify_sdcard)
+                .setCategory(Notification.CATEGORY_ERROR)
+                .build();
+
+        mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL);
+    }
+
     private Notification.Builder buildNotificationBuilder(CharSequence title, CharSequence text) {
         return new Notification.Builder(mContext)
                 .setColor(mContext.getColor(R.color.system_notification_accent_color))
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 7e4df58..34a9559 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -103,6 +103,7 @@
 import java.security.spec.KeySpec;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -215,8 +216,7 @@
         public static final int DISK_CREATED = 640;
         public static final int DISK_SIZE_CHANGED = 641;
         public static final int DISK_LABEL_CHANGED = 642;
-        public static final int DISK_VOLUME_CREATED = 643;
-        public static final int DISK_VOLUME_DESTROYED = 644;
+        public static final int DISK_UNSUPPORTED = 643;
         public static final int DISK_DESTROYED = 649;
 
         public static final int VOLUME_CREATED = 650;
@@ -583,7 +583,8 @@
                 case H_VOLUME_MOUNT: {
                     final VolumeInfo vol = (VolumeInfo) msg.obj;
                     try {
-                        mConnector.execute("volume", "mount", vol.id, vol.flags, vol.userId);
+                        mConnector.execute("volume", "mount", vol.id, vol.mountFlags,
+                                vol.mountUserId);
                     } catch (NativeDaemonConnectorException ignored) {
                     }
                     break;
@@ -658,7 +659,7 @@
 
             // Create a stub volume that represents internal storage
             final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL,
-                    VolumeInfo.TYPE_PRIVATE, 0);
+                    VolumeInfo.TYPE_PRIVATE, null, 0);
             internal.state = VolumeInfo.STATE_MOUNTED;
             internal.path = Environment.getDataDirectory().getAbsolutePath();
             mVolumes.put(internal.id, internal);
@@ -826,34 +827,15 @@
                 if (disk != null) {
                     disk.label = cooked[2];
                 }
-                break;
-            }
-            case VoldResponseCode.DISK_VOLUME_CREATED: {
-                if (cooked.length != 3) break;
-                final String diskId = cooked[1];
-                final String volId = cooked[2];
-                final DiskInfo disk = mDisks.get(diskId);
-                if (disk != null) {
-                    disk.volumeIds = ArrayUtils.appendElement(String.class, disk.volumeIds, volId);
-                }
-                final VolumeInfo vol = mVolumes.get(volId);
-                if (vol != null) {
-                    vol.diskId = diskId;
+                if (disk.label != null) {
+                    disk.label = disk.label.trim();
                 }
                 break;
             }
-            case VoldResponseCode.DISK_VOLUME_DESTROYED: {
-                if (cooked.length != 3) break;
-                final String diskId = cooked[1];
-                final String volId = cooked[2];
-                final DiskInfo disk = mDisks.get(diskId);
-                if (disk != null) {
-                    disk.volumeIds = ArrayUtils.removeElement(String.class, disk.volumeIds, volId);
-                }
-                final VolumeInfo vol = mVolumes.get(volId);
-                if (vol != null) {
-                    vol.diskId = null;
-                }
+            case VoldResponseCode.DISK_UNSUPPORTED: {
+                if (cooked.length != 2) break;
+                final DiskInfo disk = mDisks.get(cooked[1]);
+                mCallbacks.notifyDiskUnsupported(disk);
                 break;
             }
             case VoldResponseCode.DISK_DESTROYED: {
@@ -863,11 +845,11 @@
             }
 
             case VoldResponseCode.VOLUME_CREATED: {
-                if (cooked.length != 3) break;
                 final String id = cooked[1];
                 final int type = Integer.parseInt(cooked[2]);
+                final String diskId = (cooked.length == 4) ? cooked[3] : null;
                 final int mtpIndex = allocateMtpIndex(id);
-                final VolumeInfo vol = new VolumeInfo(id, type, mtpIndex);
+                final VolumeInfo vol = new VolumeInfo(id, type, diskId, mtpIndex);
                 mVolumes.put(id, vol);
                 onVolumeCreatedLocked(vol);
                 break;
@@ -942,16 +924,24 @@
                 StorageManager.PROP_PRIMARY_PHYSICAL, false);
         // TODO: enable switching to another emulated primary
         if (VolumeInfo.ID_EMULATED_INTERNAL.equals(vol.id) && !primaryPhysical) {
-            vol.flags |= VolumeInfo.FLAG_PRIMARY;
-            vol.flags |= VolumeInfo.FLAG_VISIBLE;
+            vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
+            vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
             mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
 
         } else if (vol.type == VolumeInfo.TYPE_PUBLIC) {
             if (primaryPhysical) {
-                vol.flags |= VolumeInfo.FLAG_PRIMARY;
+                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
+                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
             }
-            vol.flags |= VolumeInfo.FLAG_VISIBLE;
-            vol.userId = UserHandle.USER_OWNER;
+
+            // Adoptable public disks are visible to apps, since they meet
+            // public API requirement of being in a stable location.
+            final DiskInfo disk = mDisks.get(vol.getDiskId());
+            if (disk != null && disk.isAdoptable()) {
+                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
+            }
+
+            vol.mountUserId = UserHandle.USER_OWNER;
             mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
 
         } else if (vol.type == VolumeInfo.TYPE_PRIVATE) {
@@ -983,7 +973,7 @@
             }
         }
 
-        if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.state == VolumeInfo.STATE_UNMOUNTING) {
+        if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.state == VolumeInfo.STATE_EJECTING) {
             // TODO: this should eventually be handled by new ObbVolume state changes
             /*
              * Some OBBs might have been unmounted when this volume was
@@ -1221,7 +1211,7 @@
             enforceUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
         }
         try {
-            mConnector.execute("volume", "mount", vol.id, vol.flags, vol.userId);
+            mConnector.execute("volume", "mount", vol.id, vol.mountFlags, vol.mountUserId);
         } catch (NativeDaemonConnectorException e) {
             throw e.rethrowAsParcelableException();
         }
@@ -2633,6 +2623,7 @@
         private static final int MSG_STORAGE_STATE_CHANGED = 1;
         private static final int MSG_VOLUME_STATE_CHANGED = 2;
         private static final int MSG_VOLUME_METADATA_CHANGED = 3;
+        private static final int MSG_DISK_UNSUPPORTED = 4;
 
         private final RemoteCallbackList<IMountServiceListener>
                 mCallbacks = new RemoteCallbackList<>();
@@ -2680,6 +2671,10 @@
                     callback.onVolumeMetadataChanged((VolumeInfo) args.arg1);
                     break;
                 }
+                case MSG_DISK_UNSUPPORTED: {
+                    callback.onDiskUnsupported((DiskInfo) args.arg1);
+                    break;
+                }
             }
         }
 
@@ -2704,6 +2699,12 @@
             args.arg1 = vol;
             obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget();
         }
+
+        private void notifyDiskUnsupported(DiskInfo disk) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = disk;
+            obtainMessage(MSG_DISK_UNSUPPORTED, args).sendToTarget();
+        }
     }
 
     @Override
@@ -2757,6 +2758,7 @@
             pw.increaseIndent();
             for (int i = 0; i < mVolumes.size(); i++) {
                 final VolumeInfo vol = mVolumes.valueAt(i);
+                if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) continue;
                 vol.dump(pw);
             }
             pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c12545b..331683b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1534,15 +1534,15 @@
             if (vol.type == VolumeInfo.TYPE_PRIVATE) {
                 if (vol.state == VolumeInfo.STATE_MOUNTED) {
                     loadPrivatePackages(vol);
-                } else if (vol.state == VolumeInfo.STATE_UNMOUNTING) {
+                } else if (vol.state == VolumeInfo.STATE_EJECTING) {
                     unloadPrivatePackages(vol);
                 }
             }
 
-            if (vol.isPrimary() && vol.type == VolumeInfo.TYPE_PUBLIC) {
+            if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.isPrimary()) {
                 if (vol.state == VolumeInfo.STATE_MOUNTED) {
                     updateExternalMediaStatus(true, false);
-                } else if (vol.state == VolumeInfo.STATE_UNMOUNTING) {
+                } else if (vol.state == VolumeInfo.STATE_EJECTING) {
                     updateExternalMediaStatus(false, false);
                 }
             }