Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5e491c91 authored by Daichi Hirono's avatar Daichi Hirono Committed by Android (Google) Code Review
Browse files

Merge "Add RootScanner to MtpDocumentsProvider to handle new/removed MTP storages."

parents dfb25d3f 8b9024f0
Loading
Loading
Loading
Loading
+17 −27
Original line number Diff line number Diff line
@@ -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.
@@ -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;
    }

@@ -80,6 +82,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        mMtpManager = mtpManager;
        mResolver = resolver;
        mDocumentLoader = new DocumentLoader(mMtpManager, mResolver);
        mRootScanner = new RootScanner(mResolver, mMtpManager);
    }

    @Override
@@ -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);
@@ -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;
@@ -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() {
@@ -242,7 +237,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
            }
        }
        if (closed) {
            notifyRootsChange();
            mRootScanner.scanNow();
        }
    }

@@ -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),
+2 −2
Original line number Diff line number Diff line
@@ -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.");
        }

@@ -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;
    }
+31 −12
Original line number Diff line number Diff line
@@ -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;
@@ -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();
    }
}
+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;
            }
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -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