Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +17 −27 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private ContentResolver mResolver; private PipeManager mPipeManager; private DocumentLoader mDocumentLoader; private RootScanner mRootScanner; /** * Provides singleton instance to MtpDocumentsService. Loading @@ -72,6 +73,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mResolver = getContext().getContentResolver(); mPipeManager = new PipeManager(); mDocumentLoader = new DocumentLoader(mMtpManager, mResolver); mRootScanner = new RootScanner(mResolver, mMtpManager); return true; } Loading @@ -80,6 +82,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mMtpManager = mtpManager; mResolver = resolver; mDocumentLoader = new DocumentLoader(mMtpManager, mResolver); mRootScanner = new RootScanner(mResolver, mMtpManager); } @Override Loading @@ -88,13 +91,9 @@ public class MtpDocumentsProvider extends DocumentsProvider { projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; } final MatrixCursor cursor = new MatrixCursor(projection); for (final int deviceId : mMtpManager.getOpenedDeviceIds()) { try { final MtpRoot[] roots = mMtpManager.getRoots(deviceId); // TODO: Add retry logic here. final MtpRoot[] roots = mRootScanner.getRoots(); for (final MtpRoot root : roots) { final Identifier rootIdentifier = new Identifier(deviceId, root.mStorageId); final Identifier rootIdentifier = new Identifier(root.mDeviceId, root.mStorageId); final MatrixCursor.RowBuilder builder = cursor.newRow(); builder.add(Root.COLUMN_ROOT_ID, rootIdentifier.toRootId()); builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD); Loading @@ -104,10 +103,6 @@ public class MtpDocumentsProvider extends DocumentsProvider { rootIdentifier.toDocumentId()); builder.add(Root.COLUMN_AVAILABLE_BYTES , root.mFreeSpace); } } catch (IOException error) { Log.d(TAG, error.getMessage()); } } cursor.setNotificationUri( mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); return cursor; Loading Loading @@ -221,13 +216,13 @@ public class MtpDocumentsProvider extends DocumentsProvider { void openDevice(int deviceId) throws IOException { mMtpManager.openDevice(deviceId); notifyRootsChange(); mRootScanner.scanNow(); } void closeDevice(int deviceId) throws IOException { mMtpManager.closeDevice(deviceId); mDocumentLoader.clearCache(deviceId); notifyRootsChange(); mRootScanner.scanNow(); } void closeAllDevices() { Loading @@ -242,7 +237,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { } } if (closed) { notifyRootsChange(); mRootScanner.scanNow(); } } Loading @@ -250,11 +245,6 @@ public class MtpDocumentsProvider extends DocumentsProvider { return mMtpManager.getOpenedDeviceIds().length != 0; } private void notifyRootsChange() { mResolver.notifyChange( DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY), null, false); } private void notifyChildDocumentsChange(String parentDocumentId) { mResolver.notifyChange( DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId), Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +2 −2 Original line number Diff line number Diff line Loading @@ -72,7 +72,7 @@ class MtpManager { // Handle devices that fail to obtain storages just after opening a MTP session. final int[] storageIds = device.getStorageIds(); if (storageIds == null || storageIds.length == 0) { if (storageIds == null) { throw new IOException("Not found MTP storages in the device."); } Loading @@ -97,7 +97,7 @@ class MtpManager { final int[] storageIds = device.getStorageIds(); final MtpRoot[] results = new MtpRoot[storageIds.length]; for (int i = 0; i < storageIds.length; i++) { results[i] = new MtpRoot(device.getStorageInfo(storageIds[i])); results[i] = new MtpRoot(deviceId, device.getStorageInfo(storageIds[i])); } return results; } Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java +31 −12 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.mtp.MtpStorageInfo; import com.android.internal.annotations.VisibleForTesting; class MtpRoot { final int mDeviceId; final int mStorageId; final String mDescription; final long mFreeSpace; Loading @@ -28,27 +29,45 @@ class MtpRoot { final String mVolumeIdentifier; @VisibleForTesting MtpRoot(int storageId, MtpRoot(int deviceId, int storageId, String description, long freeSpace, long maxCapacity, String volumeIdentifier) { this.mStorageId = storageId; this.mDescription = description; this.mFreeSpace = freeSpace; this.mMaxCapacity = maxCapacity; this.mVolumeIdentifier = volumeIdentifier; mDeviceId = deviceId; mStorageId = storageId; mDescription = description; mFreeSpace = freeSpace; mMaxCapacity = maxCapacity; mVolumeIdentifier = volumeIdentifier; } MtpRoot(MtpStorageInfo storageInfo) { MtpRoot(int deviceId, MtpStorageInfo storageInfo) { mDeviceId = deviceId; mStorageId = storageInfo.getStorageId(); mDescription = storageInfo.getDescription(); mFreeSpace = storageInfo.getFreeSpace(); mMaxCapacity = storageInfo.getMaxCapacity(); if (!storageInfo.getVolumeIdentifier().equals("")) { mVolumeIdentifier = storageInfo.getVolumeIdentifier(); } else { mVolumeIdentifier = null; } @Override public boolean equals(Object object) { if (!(object instanceof MtpRoot)) return false; final MtpRoot other = (MtpRoot) object; return mDeviceId == other.mDeviceId && mStorageId == other.mStorageId && mDescription.equals(other.mDescription) && mFreeSpace == other.mFreeSpace && mMaxCapacity == other.mMaxCapacity && mVolumeIdentifier.equals(other.mVolumeIdentifier); } @Override public int hashCode() { return mDeviceId ^ mStorageId ^ mDescription.hashCode() ^ ((int) mFreeSpace) ^ ((int) mMaxCapacity) ^ mVolumeIdentifier.hashCode(); } } packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java 0 → 100644 +106 −0 Original line number Diff line number Diff line package com.android.mtp; import android.content.ContentResolver; import android.net.Uri; import android.os.Process; import android.provider.DocumentsContract; import android.util.Log; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; final class RootScanner { /** * Polling interval in milliseconds used for first SHORT_POLLING_TIMES because it is more * likely to add new root just after the device is added. */ private final static long SHORT_POLLING_INTERVAL = 2000; /** * Polling interval in milliseconds for low priority polling, when changes are not expected. */ private final static long LONG_POLLING_INTERVAL = 30 * 1000; /** * @see #SHORT_POLLING_INTERVAL */ private final static long SHORT_POLLING_TIMES = 10; final ContentResolver mResolver; final MtpManager mManager; MtpRoot[] mLastRoots = new MtpRoot[0]; int mPollingCount; boolean mHasBackgroundTask = false; RootScanner(ContentResolver resolver, MtpManager manager) { mResolver = resolver; mManager = manager; } synchronized MtpRoot[] getRoots() { return mLastRoots; } /** * Starts to check new changes right away. * If the background thread has already gone, it restarts another background thread. */ synchronized void scanNow() { mPollingCount = 0; if (!mHasBackgroundTask) { mHasBackgroundTask = true; new BackgroundLoaderThread().start(); } else { notify(); } } private MtpRoot[] createRoots(int[] deviceIds) { final ArrayList<MtpRoot> roots = new ArrayList<>(); for (final int deviceId : deviceIds) { try { roots.addAll(Arrays.asList(mManager.getRoots(deviceId))); } catch (IOException error) { // Skip device that return error. Log.d(MtpDocumentsProvider.TAG, error.getMessage()); } } return roots.toArray(new MtpRoot[roots.size()]); } private final class BackgroundLoaderThread extends Thread { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); synchronized (RootScanner.this) { while (true) { final int[] deviceIds = mManager.getOpenedDeviceIds(); final MtpRoot[] newRoots = createRoots(deviceIds); if (!Arrays.equals(mLastRoots, newRoots)) { final Uri uri = DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY); mResolver.notifyChange(uri, null, false); mLastRoots = newRoots; } if (deviceIds.length == 0) { break; } mPollingCount++; try { // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is // more likely to add new root just after the device is added. // TODO: Use short interval only for a device that is just added. RootScanner.this.wait( mPollingCount > SHORT_POLLING_TIMES ? LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL); } catch (InterruptedException exception) { break; } } mHasBackgroundTask = false; } } } } packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -43,7 +43,7 @@ public class DocumentLoaderTest extends AndroidTestCase { mLoader = new DocumentLoader(mManager, mResolver); } public void testBasic() throws IOException, InterruptedException { public void testBasic() throws Exception { final Uri uri = DocumentsContract.buildChildDocumentsUri( MtpDocumentsProvider.AUTHORITY, mParentIdentifier.toDocumentId()); setUpDocument(mManager, 40); Loading Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +17 −27 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private ContentResolver mResolver; private PipeManager mPipeManager; private DocumentLoader mDocumentLoader; private RootScanner mRootScanner; /** * Provides singleton instance to MtpDocumentsService. Loading @@ -72,6 +73,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mResolver = getContext().getContentResolver(); mPipeManager = new PipeManager(); mDocumentLoader = new DocumentLoader(mMtpManager, mResolver); mRootScanner = new RootScanner(mResolver, mMtpManager); return true; } Loading @@ -80,6 +82,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mMtpManager = mtpManager; mResolver = resolver; mDocumentLoader = new DocumentLoader(mMtpManager, mResolver); mRootScanner = new RootScanner(mResolver, mMtpManager); } @Override Loading @@ -88,13 +91,9 @@ public class MtpDocumentsProvider extends DocumentsProvider { projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; } final MatrixCursor cursor = new MatrixCursor(projection); for (final int deviceId : mMtpManager.getOpenedDeviceIds()) { try { final MtpRoot[] roots = mMtpManager.getRoots(deviceId); // TODO: Add retry logic here. final MtpRoot[] roots = mRootScanner.getRoots(); for (final MtpRoot root : roots) { final Identifier rootIdentifier = new Identifier(deviceId, root.mStorageId); final Identifier rootIdentifier = new Identifier(root.mDeviceId, root.mStorageId); final MatrixCursor.RowBuilder builder = cursor.newRow(); builder.add(Root.COLUMN_ROOT_ID, rootIdentifier.toRootId()); builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD); Loading @@ -104,10 +103,6 @@ public class MtpDocumentsProvider extends DocumentsProvider { rootIdentifier.toDocumentId()); builder.add(Root.COLUMN_AVAILABLE_BYTES , root.mFreeSpace); } } catch (IOException error) { Log.d(TAG, error.getMessage()); } } cursor.setNotificationUri( mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); return cursor; Loading Loading @@ -221,13 +216,13 @@ public class MtpDocumentsProvider extends DocumentsProvider { void openDevice(int deviceId) throws IOException { mMtpManager.openDevice(deviceId); notifyRootsChange(); mRootScanner.scanNow(); } void closeDevice(int deviceId) throws IOException { mMtpManager.closeDevice(deviceId); mDocumentLoader.clearCache(deviceId); notifyRootsChange(); mRootScanner.scanNow(); } void closeAllDevices() { Loading @@ -242,7 +237,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { } } if (closed) { notifyRootsChange(); mRootScanner.scanNow(); } } Loading @@ -250,11 +245,6 @@ public class MtpDocumentsProvider extends DocumentsProvider { return mMtpManager.getOpenedDeviceIds().length != 0; } private void notifyRootsChange() { mResolver.notifyChange( DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY), null, false); } private void notifyChildDocumentsChange(String parentDocumentId) { mResolver.notifyChange( DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId), Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +2 −2 Original line number Diff line number Diff line Loading @@ -72,7 +72,7 @@ class MtpManager { // Handle devices that fail to obtain storages just after opening a MTP session. final int[] storageIds = device.getStorageIds(); if (storageIds == null || storageIds.length == 0) { if (storageIds == null) { throw new IOException("Not found MTP storages in the device."); } Loading @@ -97,7 +97,7 @@ class MtpManager { final int[] storageIds = device.getStorageIds(); final MtpRoot[] results = new MtpRoot[storageIds.length]; for (int i = 0; i < storageIds.length; i++) { results[i] = new MtpRoot(device.getStorageInfo(storageIds[i])); results[i] = new MtpRoot(deviceId, device.getStorageInfo(storageIds[i])); } return results; } Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java +31 −12 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.mtp.MtpStorageInfo; import com.android.internal.annotations.VisibleForTesting; class MtpRoot { final int mDeviceId; final int mStorageId; final String mDescription; final long mFreeSpace; Loading @@ -28,27 +29,45 @@ class MtpRoot { final String mVolumeIdentifier; @VisibleForTesting MtpRoot(int storageId, MtpRoot(int deviceId, int storageId, String description, long freeSpace, long maxCapacity, String volumeIdentifier) { this.mStorageId = storageId; this.mDescription = description; this.mFreeSpace = freeSpace; this.mMaxCapacity = maxCapacity; this.mVolumeIdentifier = volumeIdentifier; mDeviceId = deviceId; mStorageId = storageId; mDescription = description; mFreeSpace = freeSpace; mMaxCapacity = maxCapacity; mVolumeIdentifier = volumeIdentifier; } MtpRoot(MtpStorageInfo storageInfo) { MtpRoot(int deviceId, MtpStorageInfo storageInfo) { mDeviceId = deviceId; mStorageId = storageInfo.getStorageId(); mDescription = storageInfo.getDescription(); mFreeSpace = storageInfo.getFreeSpace(); mMaxCapacity = storageInfo.getMaxCapacity(); if (!storageInfo.getVolumeIdentifier().equals("")) { mVolumeIdentifier = storageInfo.getVolumeIdentifier(); } else { mVolumeIdentifier = null; } @Override public boolean equals(Object object) { if (!(object instanceof MtpRoot)) return false; final MtpRoot other = (MtpRoot) object; return mDeviceId == other.mDeviceId && mStorageId == other.mStorageId && mDescription.equals(other.mDescription) && mFreeSpace == other.mFreeSpace && mMaxCapacity == other.mMaxCapacity && mVolumeIdentifier.equals(other.mVolumeIdentifier); } @Override public int hashCode() { return mDeviceId ^ mStorageId ^ mDescription.hashCode() ^ ((int) mFreeSpace) ^ ((int) mMaxCapacity) ^ mVolumeIdentifier.hashCode(); } }
packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java 0 → 100644 +106 −0 Original line number Diff line number Diff line package com.android.mtp; import android.content.ContentResolver; import android.net.Uri; import android.os.Process; import android.provider.DocumentsContract; import android.util.Log; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; final class RootScanner { /** * Polling interval in milliseconds used for first SHORT_POLLING_TIMES because it is more * likely to add new root just after the device is added. */ private final static long SHORT_POLLING_INTERVAL = 2000; /** * Polling interval in milliseconds for low priority polling, when changes are not expected. */ private final static long LONG_POLLING_INTERVAL = 30 * 1000; /** * @see #SHORT_POLLING_INTERVAL */ private final static long SHORT_POLLING_TIMES = 10; final ContentResolver mResolver; final MtpManager mManager; MtpRoot[] mLastRoots = new MtpRoot[0]; int mPollingCount; boolean mHasBackgroundTask = false; RootScanner(ContentResolver resolver, MtpManager manager) { mResolver = resolver; mManager = manager; } synchronized MtpRoot[] getRoots() { return mLastRoots; } /** * Starts to check new changes right away. * If the background thread has already gone, it restarts another background thread. */ synchronized void scanNow() { mPollingCount = 0; if (!mHasBackgroundTask) { mHasBackgroundTask = true; new BackgroundLoaderThread().start(); } else { notify(); } } private MtpRoot[] createRoots(int[] deviceIds) { final ArrayList<MtpRoot> roots = new ArrayList<>(); for (final int deviceId : deviceIds) { try { roots.addAll(Arrays.asList(mManager.getRoots(deviceId))); } catch (IOException error) { // Skip device that return error. Log.d(MtpDocumentsProvider.TAG, error.getMessage()); } } return roots.toArray(new MtpRoot[roots.size()]); } private final class BackgroundLoaderThread extends Thread { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); synchronized (RootScanner.this) { while (true) { final int[] deviceIds = mManager.getOpenedDeviceIds(); final MtpRoot[] newRoots = createRoots(deviceIds); if (!Arrays.equals(mLastRoots, newRoots)) { final Uri uri = DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY); mResolver.notifyChange(uri, null, false); mLastRoots = newRoots; } if (deviceIds.length == 0) { break; } mPollingCount++; try { // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is // more likely to add new root just after the device is added. // TODO: Use short interval only for a device that is just added. RootScanner.this.wait( mPollingCount > SHORT_POLLING_TIMES ? LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL); } catch (InterruptedException exception) { break; } } mHasBackgroundTask = false; } } } }
packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -43,7 +43,7 @@ public class DocumentLoaderTest extends AndroidTestCase { mLoader = new DocumentLoader(mManager, mResolver); } public void testBasic() throws IOException, InterruptedException { public void testBasic() throws Exception { final Uri uri = DocumentsContract.buildChildDocumentsUri( MtpDocumentsProvider.AUTHORITY, mParentIdentifier.toDocumentId()); setUpDocument(mManager, 40); Loading