Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 355df8f1 authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "Wire up non-visible volumes, more states."

parents 774afb9a 27de30d3
Loading
Loading
Loading
Loading
+13 −4
Original line number Diff line number Diff line
@@ -505,6 +505,16 @@ public class StorageManager {
        return null;
    }

    /** {@hide} */
    public @Nullable VolumeInfo findPrivateForEmulated(VolumeInfo emulatedVol) {
        return findVolumeById(emulatedVol.getId().replace("emulated", "private"));
    }

    /** {@hide} */
    public @Nullable VolumeInfo findEmulatedForPrivate(VolumeInfo privateVol) {
        return findVolumeById(privateVol.getId().replace("private", "emulated"));
    }

    /** {@hide} */
    public @NonNull List<VolumeInfo> getVolumes() {
        return getVolumes(0);
@@ -523,10 +533,9 @@ public class StorageManager {
    public @Nullable String getBestVolumeDescription(VolumeInfo vol) {
        String descrip = vol.getDescription();

        if (vol.diskId != null) {
            final DiskInfo disk = findDiskById(vol.diskId);
            if (disk != null && TextUtils.isEmpty(descrip)) {
                descrip = disk.getDescription();
        if (vol.disk != null) {
            if (TextUtils.isEmpty(descrip)) {
                descrip = vol.disk.getDescription();
            }
        }

+49 −7
Original line number Diff line number Diff line
@@ -101,23 +101,27 @@ public class VolumeInfo implements Parcelable {
        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
        sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING);
        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, Environment.MEDIA_MOUNTED_READ_ONLY);
        sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
        sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING);
        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
        sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED);
        sStateToEnvironment.put(VolumeInfo.STATE_BAD_REMOVAL, Environment.MEDIA_BAD_REMOVAL);

        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
        sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
        sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
        sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED_READ_ONLY, Intent.ACTION_MEDIA_MOUNTED);
        sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE);
        sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED);
        sEnvironmentToBroadcast.put(Environment.MEDIA_BAD_REMOVAL, Intent.ACTION_MEDIA_BAD_REMOVAL);
    }

    /** vold state */
    public final String id;
    public final int type;
    public final String diskId;
    public final DiskInfo disk;
    public int mountFlags = 0;
    public int mountUserId = -1;
    public int state = STATE_UNMOUNTED;
@@ -131,17 +135,21 @@ public class VolumeInfo implements Parcelable {
    public String nickname;
    public int userFlags = 0;

    public VolumeInfo(String id, int type, String diskId, int mtpIndex) {
    public VolumeInfo(String id, int type, DiskInfo disk, int mtpIndex) {
        this.id = Preconditions.checkNotNull(id);
        this.type = type;
        this.diskId = diskId;
        this.disk = disk;
        this.mtpIndex = mtpIndex;
    }

    public VolumeInfo(Parcel parcel) {
        id = parcel.readString();
        type = parcel.readInt();
        diskId = parcel.readString();
        if (parcel.readInt() != 0) {
            disk = DiskInfo.CREATOR.createFromParcel(parcel);
        } else {
            disk = null;
        }
        mountFlags = parcel.readInt();
        mountUserId = parcel.readInt();
        state = parcel.readInt();
@@ -179,8 +187,12 @@ public class VolumeInfo implements Parcelable {
        return id;
    }

    public @Nullable DiskInfo getDisk() {
        return disk;
    }

    public @Nullable String getDiskId() {
        return diskId;
        return (disk != null) ? disk.id : null;
    }

    public int getType() {
@@ -199,6 +211,10 @@ public class VolumeInfo implements Parcelable {
        return nickname;
    }

    public int getMountUserId() {
        return mountUserId;
    }

    public @Nullable String getDescription() {
        if (ID_PRIVATE_INTERNAL.equals(id)) {
            return Resources.getSystem().getString(com.android.internal.R.string.storage_internal);
@@ -211,6 +227,14 @@ public class VolumeInfo implements Parcelable {
        }
    }

    public boolean isMountedReadable() {
        return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY;
    }

    public boolean isMountedWritable() {
        return state == STATE_MOUNTED;
    }

    public boolean isPrimary() {
        return (mountFlags & MOUNT_FLAG_PRIMARY) != 0;
    }
@@ -253,6 +277,19 @@ public class VolumeInfo implements Parcelable {
        }
    }

    /**
     * Path which is accessible to apps holding
     * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
     */
    public File getInternalPathForUser(int userId) {
        if (type == TYPE_PUBLIC) {
            // TODO: plumb through cleaner path from vold
            return new File(path.replace("/storage/", "/mnt/media_rw/"));
        } else {
            return getPathForUser(userId);
        }
    }

    public StorageVolume buildStorageVolume(Context context, int userId) {
        final boolean removable;
        final boolean emulated;
@@ -339,7 +376,7 @@ public class VolumeInfo implements Parcelable {
        pw.println("VolumeInfo{" + id + "}:");
        pw.increaseIndent();
        pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
        pw.printPair("diskId", diskId);
        pw.printPair("diskId", getDiskId());
        pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags));
        pw.printPair("mountUserId", mountUserId);
        pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
@@ -403,7 +440,12 @@ public class VolumeInfo implements Parcelable {
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeString(id);
        parcel.writeInt(type);
        parcel.writeString(diskId);
        if (disk != null) {
            parcel.writeInt(1);
            disk.writeToParcel(parcel, flags);
        } else {
            parcel.writeInt(0);
        }
        parcel.writeInt(mountFlags);
        parcel.writeInt(mountUserId);
        parcel.writeInt(state);
+1 −1
Original line number Diff line number Diff line
@@ -361,7 +361,7 @@ public class PackageHelper {
        VolumeInfo bestCandidate = null;
        long bestCandidateAvailBytes = Long.MIN_VALUE;
        for (VolumeInfo vol : storageManager.getVolumes()) {
            if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.state == VolumeInfo.STATE_MOUNTED) {
            if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
                final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path));
                if (availBytes >= sizeBytes) {
                    allCandidates.add(vol.fsUuid);
+90 −63
Original line number Diff line number Diff line
@@ -26,34 +26,35 @@ import android.database.MatrixCursor.RowBuilder;
import android.graphics.Point;
import android.net.Uri;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.FileObserver;
import android.os.FileUtils;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DebugUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.android.internal.util.IndentingPrintWriter;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.Map;
import java.util.List;
import java.util.Objects;

public class ExternalStorageProvider extends DocumentsProvider {
@@ -80,6 +81,8 @@ public class ExternalStorageProvider extends DocumentsProvider {
        public int flags;
        public String title;
        public String docId;
        public File visiblePath;
        public File path;
    }

    private static final String ROOT_ID_PRIMARY_EMULATED = "primary";
@@ -90,26 +93,17 @@ public class ExternalStorageProvider extends DocumentsProvider {
    private final Object mRootsLock = new Object();

    @GuardedBy("mRootsLock")
    private ArrayList<RootInfo> mRoots;
    @GuardedBy("mRootsLock")
    private HashMap<String, RootInfo> mIdToRoot;
    @GuardedBy("mRootsLock")
    private HashMap<String, File> mIdToPath;
    private ArrayMap<String, RootInfo> mRoots = new ArrayMap<>();

    @GuardedBy("mObservers")
    private Map<File, DirectoryObserver> mObservers = Maps.newHashMap();
    private ArrayMap<File, DirectoryObserver> mObservers = new ArrayMap<>();

    @Override
    public boolean onCreate() {
        mStorageManager = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
        mHandler = new Handler();

        mRoots = Lists.newArrayList();
        mIdToRoot = Maps.newHashMap();
        mIdToPath = Maps.newHashMap();

        updateVolumes();

        return true;
    }

@@ -121,52 +115,53 @@ public class ExternalStorageProvider extends DocumentsProvider {

    private void updateVolumesLocked() {
        mRoots.clear();
        mIdToPath.clear();
        mIdToRoot.clear();

        final StorageVolume[] volumes = mStorageManager.getVolumeList();
        for (StorageVolume volume : volumes) {
            final boolean mounted = Environment.MEDIA_MOUNTED.equals(volume.getState())
                    || Environment.MEDIA_MOUNTED_READ_ONLY.equals(volume.getState());
            if (!mounted) continue;
        final int userId = UserHandle.myUserId();
        final List<VolumeInfo> volumes = mStorageManager.getVolumes();
        for (VolumeInfo volume : volumes) {
            if (!volume.isMountedReadable()) continue;

            final String rootId;
            if (volume.isPrimary() && volume.isEmulated()) {
            if (VolumeInfo.ID_EMULATED_INTERNAL.equals(volume.getId())) {
                rootId = ROOT_ID_PRIMARY_EMULATED;
            } else if (volume.getUuid() != null) {
                rootId = volume.getUuid();
            } else if (volume.getType() == VolumeInfo.TYPE_EMULATED) {
                final VolumeInfo privateVol = mStorageManager.findPrivateForEmulated(volume);
                rootId = privateVol.getFsUuid();
            } else if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
                rootId = volume.getFsUuid();
            } else {
                Log.d(TAG, "Missing UUID for " + volume.getPath() + "; skipping");
                // Unsupported volume; ignore
                continue;
            }

            if (mIdToPath.containsKey(rootId)) {
                Log.w(TAG, "Duplicate UUID " + rootId + "; skipping");
            if (TextUtils.isEmpty(rootId)) {
                Log.d(TAG, "Missing UUID for " + volume.getId() + "; skipping");
                continue;
            }
            if (mRoots.containsKey(rootId)) {
                Log.w(TAG, "Duplicate UUID " + rootId + " for " + volume.getId() + "; skipping");
                continue;
            }

            try {
                final File path = volume.getPathFile();
                mIdToPath.put(rootId, path);

                final RootInfo root = new RootInfo();
                mRoots.put(rootId, root);

                root.rootId = rootId;
                root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
                        | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
                if (ROOT_ID_PRIMARY_EMULATED.equals(rootId)) {
                    root.title = getContext().getString(R.string.root_internal_storage);
                } else {
                    final String userLabel = volume.getUserLabel();
                    if (!TextUtils.isEmpty(userLabel)) {
                        root.title = userLabel;
                    } else {
                        root.title = volume.getDescription(getContext());
                    root.title = mStorageManager.getBestVolumeDescription(volume);
                }
                if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
                    root.flags |= Root.FLAG_HAS_SETTINGS;
                }
                root.docId = getDocIdForFile(path);
                mRoots.add(root);
                mIdToRoot.put(rootId, root);
                root.visiblePath = volume.getPathForUser(userId);
                root.path = volume.getInternalPathForUser(userId);
                root.docId = getDocIdForFile(root.path);

            } catch (FileNotFoundException e) {
                throw new IllegalStateException(e);
            }
@@ -190,23 +185,26 @@ public class ExternalStorageProvider extends DocumentsProvider {
        String path = file.getAbsolutePath();

        // Find the most-specific root path
        Map.Entry<String, File> mostSpecific = null;
        String mostSpecificId = null;
        String mostSpecificPath = null;
        synchronized (mRootsLock) {
            for (Map.Entry<String, File> root : mIdToPath.entrySet()) {
                final String rootPath = root.getValue().getPath();
                if (path.startsWith(rootPath) && (mostSpecific == null
                        || rootPath.length() > mostSpecific.getValue().getPath().length())) {
                    mostSpecific = root;
            for (int i = 0; i < mRoots.size(); i++) {
                final String rootId = mRoots.keyAt(i);
                final String rootPath = mRoots.valueAt(i).path.getAbsolutePath();
                if (path.startsWith(rootPath) && (mostSpecificPath == null
                        || rootPath.length() > mostSpecificPath.length())) {
                    mostSpecificId = rootId;
                    mostSpecificPath = rootPath;
                }
            }
        }

        if (mostSpecific == null) {
        if (mostSpecificPath == null) {
            throw new FileNotFoundException("Failed to find root that contains " + path);
        }

        // Start at first char of path under root
        final String rootPath = mostSpecific.getValue().getPath();
        final String rootPath = mostSpecificPath;
        if (rootPath.equals(path)) {
            path = "";
        } else if (rootPath.endsWith("/")) {
@@ -215,21 +213,30 @@ public class ExternalStorageProvider extends DocumentsProvider {
            path = path.substring(rootPath.length() + 1);
        }

        return mostSpecific.getKey() + ':' + path;
        return mostSpecificId + ':' + path;
    }

    private File getFileForDocId(String docId) throws FileNotFoundException {
        return getFileForDocId(docId, false);
    }

    private File getFileForDocId(String docId, boolean visible) throws FileNotFoundException {
        final int splitIndex = docId.indexOf(':', 1);
        final String tag = docId.substring(0, splitIndex);
        final String path = docId.substring(splitIndex + 1);

        File target;
        RootInfo root;
        synchronized (mRootsLock) {
            target = mIdToPath.get(tag);
            root = mRoots.get(tag);
        }
        if (target == null) {
        if (root == null) {
            throw new FileNotFoundException("No root for " + tag);
        }

        File target = visible ? root.visiblePath : root.path;
        if (target == null) {
            return null;
        }
        if (!target.exists()) {
            target.mkdirs();
        }
@@ -286,16 +293,13 @@ public class ExternalStorageProvider extends DocumentsProvider {
    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
        final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
        synchronized (mRootsLock) {
            for (String rootId : mIdToPath.keySet()) {
                final RootInfo root = mIdToRoot.get(rootId);
                final File path = mIdToPath.get(rootId);

            for (RootInfo root : mRoots.values()) {
                final RowBuilder row = result.newRow();
                row.add(Root.COLUMN_ROOT_ID, root.rootId);
                row.add(Root.COLUMN_FLAGS, root.flags);
                row.add(Root.COLUMN_TITLE, root.title);
                row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
                row.add(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace());
                row.add(Root.COLUMN_AVAILABLE_BYTES, root.path.getFreeSpace());
            }
        }
        return result;
@@ -464,7 +468,7 @@ public class ExternalStorageProvider extends DocumentsProvider {

        final File parent;
        synchronized (mRootsLock) {
            parent = mIdToPath.get(rootId);
            parent = mRoots.get(rootId).path;
        }

        final LinkedList<File> pending = new LinkedList<File>();
@@ -494,8 +498,10 @@ public class ExternalStorageProvider extends DocumentsProvider {
            String documentId, String mode, CancellationSignal signal)
            throws FileNotFoundException {
        final File file = getFileForDocId(documentId);
        final File visibleFile = getFileForDocId(documentId, true);

        final int pfdMode = ParcelFileDescriptor.parseMode(mode);
        if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY) {
        if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY || visibleFile == null) {
            return ParcelFileDescriptor.open(file, pfdMode);
        } else {
            try {
@@ -505,7 +511,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
                    public void onClose(IOException e) {
                        final Intent intent = new Intent(
                                Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                        intent.setData(Uri.fromFile(file));
                        intent.setData(Uri.fromFile(visibleFile));
                        getContext().sendBroadcast(intent);
                    }
                });
@@ -523,6 +529,27 @@ public class ExternalStorageProvider extends DocumentsProvider {
        return DocumentsContract.openImageThumbnail(file);
    }

    @Override
    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
        synchronized (mRootsLock) {
            for (int i = 0; i < mRoots.size(); i++) {
                final RootInfo root = mRoots.valueAt(i);
                pw.println("Root{" + root.rootId + "}:");
                pw.increaseIndent();
                pw.printPair("flags", DebugUtils.flagsToString(Root.class, "FLAG_", root.flags));
                pw.println();
                pw.printPair("title", root.title);
                pw.printPair("docId", root.docId);
                pw.println();
                pw.printPair("path", root.path);
                pw.printPair("visiblePath", root.visiblePath);
                pw.decreaseIndent();
                pw.println();
            }
        }
    }

    private static String getTypeForFile(File file) {
        if (file.isDirectory()) {
            return Document.MIME_TYPE_DIR;
+8 −7
Original line number Diff line number Diff line
@@ -60,7 +60,7 @@ public class StorageNotification extends SystemUI {
            // Avoid kicking notifications when getting early metadata before
            // mounted. If already mounted, we're being kicked because of a
            // nickname or init'ed change.
            if (vol.getState() == VolumeInfo.STATE_MOUNTED) {
            if (vol.isMountedReadable()) {
                onVolumeStateChangedInternal(vol, vol.getState(), vol.getState());
            }
        }
@@ -111,6 +111,7 @@ public class StorageNotification extends SystemUI {
                onVolumeChecking(vol);
                break;
            case VolumeInfo.STATE_MOUNTED:
            case VolumeInfo.STATE_MOUNTED_READ_ONLY:
                onVolumeMounted(vol);
                break;
            case VolumeInfo.STATE_FORMATTING:
@@ -136,7 +137,7 @@ public class StorageNotification extends SystemUI {
    }

    private void onVolumeChecking(VolumeInfo vol) {
        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
        final DiskInfo disk = vol.getDisk();
        final CharSequence title = mContext.getString(
                R.string.ext_media_checking_notification_title, disk.getDescription());
        final CharSequence text = mContext.getString(
@@ -156,7 +157,7 @@ public class StorageNotification extends SystemUI {
        // Don't annoy when user dismissed in past
        if (vol.isSnoozed()) return;

        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
        final DiskInfo disk = vol.getDisk();
        final Notification notif;
        if (disk.isAdoptable() && !vol.isInited()) {
            final CharSequence title = disk.getDescription();
@@ -198,7 +199,7 @@ public class StorageNotification extends SystemUI {
    }

    private void onVolumeEjecting(VolumeInfo vol) {
        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
        final DiskInfo disk = vol.getDisk();
        final CharSequence title = mContext.getString(
                R.string.ext_media_unmounting_notification_title, disk.getDescription());
        final CharSequence text = mContext.getString(
@@ -215,7 +216,7 @@ public class StorageNotification extends SystemUI {
    }

    private void onVolumeUnmountable(VolumeInfo vol) {
        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
        final DiskInfo disk = vol.getDisk();
        final CharSequence title = mContext.getString(
                R.string.ext_media_unmountable_notification_title, disk.getDescription());
        final CharSequence text = mContext.getString(
@@ -236,7 +237,7 @@ public class StorageNotification extends SystemUI {
            return;
        }

        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
        final DiskInfo disk = vol.getDisk();
        final CharSequence title = mContext.getString(
                R.string.ext_media_nomedia_notification_title, disk.getDescription());
        final CharSequence text = mContext.getString(
@@ -256,7 +257,7 @@ public class StorageNotification extends SystemUI {
            return;
        }

        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
        final DiskInfo disk = vol.getDisk();
        final CharSequence title = mContext.getString(
                R.string.ext_media_badremoval_notification_title, disk.getDescription());
        final CharSequence text = mContext.getString(
Loading