summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeff Sharkey <jsharkey@android.com> 2013-10-17 10:52:17 -0700
committer Jeff Sharkey <jsharkey@android.com> 2013-10-17 10:55:32 -0700
commit1f706c6cd1cb841adadc2babc57a34e5728983ec (patch)
treef0b9956276e741a7e7416a272ff47d8ba667fcfe
parent5aca2b8dc4f4ff2d466a64587d06666c7bbd9749 (diff)
Include external storage devices in DocumentsUI.
Include volume UUID in generated document IDs to uniquely identify volumes over time. Show volume label to users. Watch for mount changes to update available roots. Bug: 11175082 Change-Id: Ia151bde768587468efde0c1d97a740b5353d1582
-rw-r--r--core/java/android/os/storage/StorageVolume.java12
-rw-r--r--packages/ExternalStorageProvider/AndroidManifest.xml8
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java148
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java35
-rw-r--r--services/java/com/android/server/MountService.java5
5 files changed, 165 insertions, 43 deletions
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 1668f59f549d..0285cb932114 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -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);
}
}
diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml
index 99a42600cc5a..5169fef5f4a2 100644
--- a/packages/ExternalStorageProvider/AndroidManifest.xml
+++ b/packages/ExternalStorageProvider/AndroidManifest.xml
@@ -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"
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 11ff2d86446d..d42354fa5c47 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -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,42 +70,91 @@ 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;
-
- try {
- final String rootId = "primary";
- final File path = Environment.getExternalStorageDirectory();
- 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;
- root.title = getContext().getString(R.string.root_internal_storage);
- root.docId = getDocIdForFile(path);
- mRoots.add(root);
- mIdToRoot.put(rootId, root);
- } catch (FileNotFoundException e) {
- throw new IllegalStateException(e);
- }
+ 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 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);
+ }
+ }
+
+ 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) {
return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
}
@@ -113,11 +168,13 @@ public class ExternalStorageProvider extends DocumentsProvider {
// Find the most-specific root path
Map.Entry<String, File> mostSpecific = null;
- 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;
+ 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;
+ }
}
}
@@ -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,16 +259,18 @@ public class ExternalStorageProvider extends DocumentsProvider {
@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
- for (String rootId : mIdToPath.keySet()) {
- final RootInfo root = mIdToRoot.get(rootId);
- final File path = mIdToPath.get(rootId);
-
- 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());
+ synchronized (mRootsLock) {
+ for (String rootId : mIdToPath.keySet()) {
+ final RootInfo root = mIdToRoot.get(rootId);
+ final File path = mIdToPath.get(rootId);
+
+ 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());
+ }
}
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);
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java
new file mode 100644
index 000000000000..8a6c7d68525e
--- /dev/null
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java
@@ -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);
+ }
+ }
+}
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 7308b7d7bb3e..e60231a4fd8a 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -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);
}
}