diff options
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); } } |