am 1a5804d7: am 0c0f1e2e: Merge "Include external storage devices in DocumentsUI." into klp-dev

* commit '1a5804d7aa253432f37fbd5d8eb89ac363501fe9':
  Include external storage devices in DocumentsUI.
This commit is contained in:
Jeff Sharkey
2013-10-17 18:53:26 -07:00
committed by Android Git Automerger
5 changed files with 164 additions and 42 deletions

View File

@ -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);
}
}

View File

@ -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"

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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);
}
}