diff options
| author | Jeff Sharkey <jsharkey@android.com> | 2013-10-17 18:53:26 -0700 |
|---|---|---|
| committer | Android Git Automerger <android-git-automerger@android.com> | 2013-10-17 18:53:26 -0700 |
| commit | 703840da8a33c703f2d787526438c650b0318845 (patch) | |
| tree | 0c7bce42920a11cc46384e4df003781fdff3e3e9 /packages/ExternalStorageProvider | |
| parent | 47938450b4d19f60edf774c8f451d53aa110ea0b (diff) | |
| parent | 1a5804d7aa253432f37fbd5d8eb89ac363501fe9 (diff) | |
| download | frameworks_base-703840da8a33c703f2d787526438c650b0318845.zip frameworks_base-703840da8a33c703f2d787526438c650b0318845.tar.gz frameworks_base-703840da8a33c703f2d787526438c650b0318845.tar.bz2 | |
am 1a5804d7: am 0c0f1e2e: Merge "Include external storage devices in DocumentsUI." into klp-dev
* commit '1a5804d7aa253432f37fbd5d8eb89ac363501fe9':
Include external storage devices in DocumentsUI.
Diffstat (limited to 'packages/ExternalStorageProvider')
3 files changed, 150 insertions, 41 deletions
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 @@ 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 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); + } + } +} |
