Loading core/java/android/os/storage/StorageVolume.java +12 −0 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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(); } Loading Loading @@ -298,5 +309,6 @@ public class StorageVolume implements Parcelable { parcel.writeParcelable(mOwner, flags); parcel.writeString(mUuid); parcel.writeString(mUserLabel); parcel.writeString(mState); } } packages/ExternalStorageProvider/AndroidManifest.xml +8 −0 Original line number Diff line number Diff line Loading @@ -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" Loading packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +107 −41 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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[] { Loading @@ -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) { Loading @@ -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 Loading @@ -120,6 +176,7 @@ public class ExternalStorageProvider extends DocumentsProvider { mostSpecific = root; } } } if (mostSpecific == null) { throw new FileNotFoundException("Failed to find root that contains " + path); Loading @@ -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); } Loading Loading @@ -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); Loading @@ -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; } Loading Loading @@ -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); Loading packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java 0 → 100644 +35 −0 Original line number Diff line number Diff line /* * 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); } } } services/java/com/android/server/MountService.java +3 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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)) { Loading Loading @@ -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); } } Loading Loading @@ -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); } } Loading Loading
core/java/android/os/storage/StorageVolume.java +12 −0 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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(); } Loading Loading @@ -298,5 +309,6 @@ public class StorageVolume implements Parcelable { parcel.writeParcelable(mOwner, flags); parcel.writeString(mUuid); parcel.writeString(mUserLabel); parcel.writeString(mState); } }
packages/ExternalStorageProvider/AndroidManifest.xml +8 −0 Original line number Diff line number Diff line Loading @@ -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" Loading
packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +107 −41 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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[] { Loading @@ -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) { Loading @@ -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 Loading @@ -120,6 +176,7 @@ public class ExternalStorageProvider extends DocumentsProvider { mostSpecific = root; } } } if (mostSpecific == null) { throw new FileNotFoundException("Failed to find root that contains " + path); Loading @@ -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); } Loading Loading @@ -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); Loading @@ -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; } Loading Loading @@ -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); Loading
packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java 0 → 100644 +35 −0 Original line number Diff line number Diff line /* * 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); } } }
services/java/com/android/server/MountService.java +3 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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)) { Loading Loading @@ -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); } } Loading Loading @@ -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); } } Loading