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

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

Merge "Reopen MTP devices when the provider is created."

parents 291a136a e0282dd7
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
@@ -34,6 +35,7 @@ import android.provider.DocumentsContract.Root;

import com.android.internal.annotations.VisibleForTesting;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Objects;

@@ -305,6 +307,32 @@ class MtpDatabase {
        }
    }

    /**
     * Returns the set of device ID stored in the database.
     */
    int[] getDeviceIds() {
        final Cursor cursor = mDatabase.query(
                true,
                TABLE_DOCUMENTS,
                strings(COLUMN_DEVICE_ID),
                null,
                null,
                null,
                null,
                null,
                null);
        try {
            final int[] ids = new int[cursor.getCount()];
            for (int i = 0; i < ids.length; i++) {
                cursor.moveToNext();
                ids[i] = cursor.getInt(0);
            }
            return ids;
        } finally {
            cursor.close();
        }
    }

    private boolean deleteDocumentsAndRoots(String selection, String[] args) {
        mDatabase.beginTransaction();
        try {
@@ -351,6 +379,11 @@ class MtpDatabase {
        }
    }

    @VisibleForTesting
    static void deleteDatabase(Context context) {
        context.deleteDatabase(DATABASE_NAME);
    }

    /**
     * Gets {@link ContentValues} for the given root.
     * @param values {@link ContentValues} that receives values.
+1 −1
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ import android.provider.DocumentsContract.Root;
 */
class MtpDatabaseConstants {
    static final int DATABASE_VERSION = 1;
    static final String DATABASE_NAME = null;
    static final String DATABASE_NAME = "database";

    static final int FLAG_DATABASE_IN_MEMORY = 1;
    static final int FLAG_DATABASE_IN_FILE = 0;
+54 −41
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Point;
import android.media.MediaFile;
import android.mtp.MtpConstants;
@@ -58,11 +57,13 @@ public class MtpDocumentsProvider extends DocumentsProvider {
            Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
    };

    private final Object mDeviceListLock = new Object();

    private static MtpDocumentsProvider sSingleton;

    private MtpManager mMtpManager;
    private ContentResolver mResolver;
    @GuardedBy("mDeviceToolkits")
    @GuardedBy("mDeviceListLock")
    private Map<Integer, DeviceToolkit> mDeviceToolkits;
    private RootScanner mRootScanner;
    private Resources mResources;
@@ -84,6 +85,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
        resume();
        return true;
    }

@@ -99,6 +101,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
        mDatabase = database;
        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
        resume();
    }

    @Override
@@ -196,7 +199,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {

    @Override
    public void onTrimMemory(int level) {
        synchronized (mDeviceToolkits) {
        synchronized (mDeviceListLock) {
            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
                toolkit.mDocumentLoader.clearCompletedTasks();
            }
@@ -234,64 +237,47 @@ public class MtpDocumentsProvider extends DocumentsProvider {
    }

    void openDevice(int deviceId) throws IOException {
        synchronized (mDeviceToolkits) {
        synchronized (mDeviceListLock) {
            mMtpManager.openDevice(deviceId);
            mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
            mDeviceToolkits.put(
                    deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
        }
        mRootScanner.resume();
    }

    void closeDevice(int deviceId) throws IOException, InterruptedException {
        // TODO: Flush the device before closing (if not closed externally).
        synchronized (mDeviceToolkits) {
            getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
            mDeviceToolkits.remove(deviceId);
            mDatabase.removeDeviceRows(deviceId);
            mMtpManager.closeDevice(deviceId);
        }
        mRootScanner.notifyChange();
        if (!hasOpenedDevices()) {
            mRootScanner.pause();
        }
    }

    synchronized void closeAllDevices() throws InterruptedException {
        boolean closed = false;
        for (int deviceId : mMtpManager.getOpenedDeviceIds()) {
            try {
        synchronized (mDeviceListLock) {
            closeDeviceInternal(deviceId);
            mDatabase.removeDeviceRows(deviceId);
                mMtpManager.closeDevice(deviceId);
                getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
                closed = true;
            } catch (IOException d) {
                Log.d(TAG, "Failed to close the MTP device: " + deviceId);
            }
        }
        if (closed) {
        mRootScanner.notifyChange();
            mRootScanner.pause();
        }
    }

    boolean hasOpenedDevices() {
        synchronized (mDeviceListLock) {
            return mMtpManager.getOpenedDeviceIds().length != 0;
        }
    }

    /**
     * Finalize the content provider for unit tests.
     */
    @Override
    public void shutdown() {
        synchronized (mDeviceListLock) {
            try {
            closeAllDevices();
        } catch (InterruptedException e) {
                for (final int id : mMtpManager.getOpenedDeviceIds()) {
                    closeDeviceInternal(id);
                }
            } catch (InterruptedException|IOException e) {
                // It should fail unit tests by throwing runtime exception.
            throw new RuntimeException(e.getMessage());
                throw new RuntimeException(e);
            } finally {
                mDatabase.close();
                super.shutdown();
            }
        }
    }

    private void notifyChildDocumentsChange(String parentDocumentId) {
        mResolver.notifyChange(
@@ -300,8 +286,35 @@ public class MtpDocumentsProvider extends DocumentsProvider {
                false);
    }

    /**
     * Reopens MTP devices based on database state.
     */
    private void resume() {
        synchronized (mDeviceListLock) {
            mDatabase.getMapper().clearMapping();
            final int[] ids = mDatabase.getDeviceIds();
            for (final int id : ids) {
                try {
                    openDevice(id);
                } catch (IOException exception) {
                    mDatabase.removeDeviceRows(id);
                }
            }
        }
    }

    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
        // TODO: Flush the device before closing (if not closed externally).
        getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
        mDeviceToolkits.remove(deviceId);
        mMtpManager.closeDevice(deviceId);
        if (!hasOpenedDevices()) {
            mRootScanner.pause();
        }
    }

    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
        synchronized (mDeviceToolkits) {
        synchronized (mDeviceListLock) {
            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
            if (toolkit == null) {
                throw new FileNotFoundException();
+13 −21
Original line number Diff line number Diff line
@@ -55,11 +55,8 @@ public class MtpDocumentsService extends Service {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent == null) {
        // If intent is null, the service was restarted.
            // TODO: Recover opened devices here.
            return START_STICKY;
        }
        if (intent != null) {
            if (intent.getAction().equals(ACTION_OPEN_DEVICE)) {
                final UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE);
                try {
@@ -70,7 +67,8 @@ public class MtpDocumentsService extends Service {
                    Log.e(MtpDocumentsProvider.TAG, error.getMessage());
                }
            } else {
            Log.w(MtpDocumentsProvider.TAG, "Received unknown intent action.");
                Log.e(MtpDocumentsProvider.TAG, "Received unknown intent action.");
            }
        }
        stopSelfIfNeeded();
        return Service.START_NOT_STICKY;
@@ -78,14 +76,8 @@ public class MtpDocumentsService extends Service {

    @Override
    public void onDestroy() {
        final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
        unregisterReceiver(mReceiver);
        mReceiver = null;
        try {
            provider.closeAllDevices();
        } catch (InterruptedException e) {
            Log.e(MtpDocumentsProvider.TAG, e.getMessage());
        }
        super.onDestroy();
    }

+1 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import com.android.internal.annotations.VisibleForTesting;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;

/**
 * The model wrapping android.mtp API.
Loading