* commit '1a5804d7aa253432f37fbd5d8eb89ac363501fe9': Include external storage devices in DocumentsUI.
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user