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

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

Merge "Include external storage devices in DocumentsUI." into klp-dev

parents 8f4c15d8 1f706c6c
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ public class StorageVolume implements Parcelable {

    private String mUuid;
    private String mUserLabel;
    private String mState;

    // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
    // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
@@ -84,6 +85,7 @@ public class StorageVolume implements Parcelable {
        mOwner = in.readParcelable(null);
        mUuid = in.readString();
        mUserLabel = in.readString();
        mState = in.readString();
    }

    public static StorageVolume fromTemplate(StorageVolume template, File path, UserHandle owner) {
@@ -228,6 +230,14 @@ public class StorageVolume implements Parcelable {
        return mUserLabel;
    }

    public void setState(String state) {
        mState = state;
    }

    public String getState() {
        return mState;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof StorageVolume && mPath != null) {
@@ -264,6 +274,7 @@ public class StorageVolume implements Parcelable {
        pw.printPair("mOwner", mOwner);
        pw.printPair("mUuid", mUuid);
        pw.printPair("mUserLabel", mUserLabel);
        pw.printPair("mState", mState);
        pw.decreaseIndent();
    }

@@ -298,5 +309,6 @@ public class StorageVolume implements Parcelable {
        parcel.writeParcelable(mOwner, flags);
        parcel.writeString(mUuid);
        parcel.writeString(mUserLabel);
        parcel.writeString(mState);
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -16,6 +16,14 @@
            </intent-filter>
        </provider>

        <receiver android:name=".MountReceiver">
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_MOUNTED" />
                <action android:name="android.intent.action.MEDIA_UNMOUNTED" />
                <data android:scheme="file" />
            </intent-filter>
        </receiver>

        <!-- TODO: find a better place for tests to live -->
        <provider
            android:name=".TestDocumentsProvider"
+107 −41
Original line number Diff line number Diff line
@@ -16,21 +16,25 @@

package com.android.externalstorage;

import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.graphics.Point;
import android.media.ExifInterface;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsContract;
import android.provider.DocumentsProvider;
import android.util.Log;
import android.webkit.MimeTypeMap;

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

@@ -45,6 +49,8 @@ import java.util.Map;
public class ExternalStorageProvider extends DocumentsProvider {
    private static final String TAG = "ExternalStorage";

    public static final String AUTHORITY = "com.android.externalstorage.documents";

    // docId format: root:path/to/file

    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
@@ -64,40 +70,89 @@ public class ExternalStorageProvider extends DocumentsProvider {
        public String docId;
    }

    private static final String ROOT_ID_PRIMARY_EMULATED = "primary";

    private StorageManager mStorageManager;

    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;

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

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

        // TODO: support multiple storage devices, requiring that volume serial
        // number be burned into rootId so we can identify files from different
        // volumes. currently we only use a static rootId for emulated storage,
        // since that storage never changes.
        if (!Environment.isExternalStorageEmulated()) return true;
        updateVolumes();

        return true;
    }

    public void updateVolumes() {
        synchronized (mRootsLock) {
            updateVolumesLocked();
        }
    }

    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 String rootId;
            if (volume.isPrimary() && volume.isEmulated()) {
                rootId = ROOT_ID_PRIMARY_EMULATED;
            } else if (volume.getUuid() != null) {
                rootId = volume.getUuid();
            } else {
                Log.d(TAG, "Missing UUID for " + volume.getPath() + "; skipping");
                continue;
            }

            if (mIdToPath.containsKey(rootId)) {
                Log.w(TAG, "Duplicate UUID " + rootId + "; skipping");
                continue;
            }

            try {
            final String rootId = "primary";
            final File path = Environment.getExternalStorageDirectory();
                final File path = volume.getPathFile();
                mIdToPath.put(rootId, path);

                final RootInfo root = new RootInfo();
                root.rootId = rootId;
                root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
                        | Root.FLAG_SUPPORTS_SEARCH;
                if (ROOT_ID_PRIMARY_EMULATED.equals(rootId)) {
                    root.title = getContext().getString(R.string.root_internal_storage);
                } else {
                    root.title = volume.getUserLabel();
                }
                root.docId = getDocIdForFile(path);
                mRoots.add(root);
                mIdToRoot.put(rootId, root);
            } catch (FileNotFoundException e) {
                throw new IllegalStateException(e);
            }
        }

        return true;
        Log.d(TAG, "After updating volumes, found " + mRoots.size() + " active roots");

        getContext().getContentResolver()
                .notifyChange(DocumentsContract.buildRootsUri(AUTHORITY), null, false);
    }

    private static String[] resolveRootProjection(String[] projection) {
@@ -113,6 +168,7 @@ public class ExternalStorageProvider extends DocumentsProvider {

        // Find the most-specific root path
        Map.Entry<String, File> mostSpecific = null;
        synchronized (mRootsLock) {
            for (Map.Entry<String, File> root : mIdToPath.entrySet()) {
                final String rootPath = root.getValue().getPath();
                if (path.startsWith(rootPath) && (mostSpecific == null
@@ -120,6 +176,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
                    mostSpecific = root;
                }
            }
        }

        if (mostSpecific == null) {
            throw new FileNotFoundException("Failed to find root that contains " + path);
@@ -143,7 +200,10 @@ public class ExternalStorageProvider extends DocumentsProvider {
        final String tag = docId.substring(0, splitIndex);
        final String path = docId.substring(splitIndex + 1);

        File target = mIdToPath.get(tag);
        File target;
        synchronized (mRootsLock) {
            target = mIdToPath.get(tag);
        }
        if (target == null) {
            throw new FileNotFoundException("No root for " + tag);
        }
@@ -199,6 +259,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
    @Override
    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);
@@ -210,6 +271,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
                row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
                row.add(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace());
            }
        }
        return result;
    }

@@ -277,7 +339,11 @@ public class ExternalStorageProvider extends DocumentsProvider {
    public Cursor querySearchDocuments(String rootId, String query, String[] projection)
            throws FileNotFoundException {
        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
        final File parent = mIdToPath.get(rootId);

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

        final LinkedList<File> pending = new LinkedList<File>();
        pending.add(parent);
+35 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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.externalstorage;

import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;

public class MountReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final ContentProviderClient client = context.getContentResolver()
                .acquireContentProviderClient(ExternalStorageProvider.AUTHORITY);
        try {
            ((ExternalStorageProvider) client.getLocalContentProvider()).updateVolumes();
        } finally {
            ContentProviderClient.releaseQuietly(client);
        }
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -56,7 +56,6 @@ import android.os.storage.StorageResultCode;
import android.os.storage.StorageVolume;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
import android.util.Xml;

@@ -85,7 +84,6 @@ import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -666,6 +664,7 @@ class MountService extends IMountService.Stub
        final String oldState;
        synchronized (mVolumesLock) {
            oldState = mVolumeStates.put(path, state);
            volume.setState(state);
        }

        if (state.equals(oldState)) {
@@ -1255,6 +1254,7 @@ class MountService extends IMountService.Stub

                            // Until we hear otherwise, treat as unmounted
                            mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED);
                            volume.setState(Environment.MEDIA_UNMOUNTED);
                        }
                    }

@@ -1298,6 +1298,7 @@ class MountService extends IMountService.Stub
        } else {
            // Place stub status for early callers to find
            mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED);
            volume.setState(Environment.MEDIA_MOUNTED);
        }
    }