am 703840da: am 1a5804d7: am 0c0f1e2e: Merge "Include external storage devices in DocumentsUI." into klp-dev
* commit '703840da8a33c703f2d787526438c650b0318845':
Include external storage devices in DocumentsUI.
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 1668f59..0285cb9 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -51,6 +51,7 @@
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 @@
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 @@
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 @@
pw.printPair("mOwner", mOwner);
pw.printPair("mUuid", mUuid);
pw.printPair("mUserLabel", mUserLabel);
+ pw.printPair("mState", mState);
pw.decreaseIndent();
}
@@ -298,5 +309,6 @@
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 99a4260..5169fef 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 11ff2d8..d42354f 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 @@
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 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 @@
// 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 @@
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 @@
@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);
+ 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());
+ 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 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 0000000..8a6c7d6
--- /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 7308b7d..e60231a 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.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.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 @@
final String oldState;
synchronized (mVolumesLock) {
oldState = mVolumeStates.put(path, state);
+ volume.setState(state);
}
if (state.equals(oldState)) {
@@ -1255,6 +1254,7 @@
// Until we hear otherwise, treat as unmounted
mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED);
+ volume.setState(Environment.MEDIA_UNMOUNTED);
}
}
@@ -1298,6 +1298,7 @@
} else {
// Place stub status for early callers to find
mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED);
+ volume.setState(Environment.MEDIA_MOUNTED);
}
}