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

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

Merge "Open MTP device on demand."

parents 28310218 fda7474c
Loading
Loading
Loading
Loading
+1 −5
Original line number Diff line number Diff line
@@ -30,16 +30,12 @@
        </activity>

        <receiver android:name=".UsbIntentReceiver" android:exported="true">
            <!-- TODO: Remove an intent-filter for USB_DEVICE_ATTACHED after the provider becomes to
                       open devices on demand. -->
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
                <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
            </intent-filter>
            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                       android:resource="@xml/device_filter" />
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>
+32 −4
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
    private Resources mResources;
    private MtpDatabase mDatabase;
    private AppFuse mAppFuse;
    private ServiceIntentSender mIntentSender;

    /**
     * Provides singleton instance to MtpDocumentsService.
@@ -88,6 +89,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
        mIntentSender = new ServiceIntentSender(getContext());
        // TODO: Mount AppFuse on demands.
        try {
            mAppFuse.mount(getContext().getSystemService(StorageManager.class));
@@ -105,7 +107,8 @@ public class MtpDocumentsProvider extends DocumentsProvider {
            MtpManager mtpManager,
            ContentResolver resolver,
            MtpDatabase database,
            StorageManager storageManager) {
            StorageManager storageManager,
            ServiceIntentSender intentSender) {
        mResources = resources;
        mMtpManager = mtpManager;
        mResolver = resolver;
@@ -113,6 +116,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        mDatabase = database;
        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
        mIntentSender = intentSender;
        // TODO: Mount AppFuse on demands.
        try {
            mAppFuse.mount(storageManager);
@@ -152,6 +156,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        }
        Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
        try {
            openDevice(parentIdentifier.mDeviceId);
            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
                final Identifier singleStorageIdentifier =
                        mDatabase.getSingleStorageIdentifier(parentDocumentId);
@@ -176,11 +181,12 @@ public class MtpDocumentsProvider extends DocumentsProvider {
                    throws FileNotFoundException {
        final Identifier identifier = mDatabase.createIdentifier(documentId);
        try {
            openDevice(identifier.mDeviceId);
            switch (mode) {
                case "r":
                    final long fileSize = getFileSize(documentId);
                    // MTP getPartialObject operation does not support files that are larger than 4GB.
                    // Fallback to non-seekable file descriptor.
                    // MTP getPartialObject operation does not support files that are larger than
                    // 4GB. Fallback to non-seekable file descriptor.
                    // TODO: Use getPartialObject64 for MTP devices that support Android vendor
                    // extension.
                    if (fileSize <= 0xffffffffl) {
@@ -213,6 +219,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
            CancellationSignal signal) throws FileNotFoundException {
        final Identifier identifier = mDatabase.createIdentifier(documentId);
        try {
            openDevice(identifier.mDeviceId);
            return new AssetFileDescriptor(
                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
                    0,  // Start offset.
@@ -227,6 +234,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
    public void deleteDocument(String documentId) throws FileNotFoundException {
        try {
            final Identifier identifier = mDatabase.createIdentifier(documentId);
            openDevice(identifier.mDeviceId);
            final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
            mDatabase.deleteDocument(documentId);
@@ -259,6 +267,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
            throws FileNotFoundException {
        try {
            final Identifier parentId = mDatabase.createIdentifier(parentDocumentId);
            openDevice(parentId.mDeviceId);
            final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe();
            pipe[0].close();  // 0 bytes for a new document.
            final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
@@ -286,11 +295,19 @@ public class MtpDocumentsProvider extends DocumentsProvider {

    void openDevice(int deviceId) throws IOException {
        synchronized (mDeviceListLock) {
            if (mDeviceToolkits.containsKey(deviceId)) {
                return;
            }
            mMtpManager.openDevice(deviceId);
            mDeviceToolkits.put(
                    deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
            mIntentSender.sendUpdateNotificationIntent();
            try {
                mRootScanner.resume().await();
            } catch (InterruptedException error) {
                Log.e(TAG, "openDevice", error);
            }
        }
        mRootScanner.resume();
    }

    void closeDevice(int deviceId) throws IOException, InterruptedException {
@@ -298,6 +315,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
            closeDeviceInternal(deviceId);
        }
        mRootScanner.resume();
        mIntentSender.sendUpdateNotificationIntent();
    }

    int[] getOpenedDeviceIds() {
@@ -317,6 +335,13 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        }
    }

    /**
     * Resumes root scanner to handle the update of device list.
     */
    void resumeRootScanner() {
        mRootScanner.resume();
    }

    /**
     * Finalize the content provider for unit tests.
     */
@@ -356,6 +381,9 @@ public class MtpDocumentsProvider extends DocumentsProvider {

    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
        // TODO: Flush the device before closing (if not closed externally).
        if (!mDeviceToolkits.containsKey(deviceId)) {
            return;
        }
        getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
        mDeviceToolkits.remove(deviceId);
        mMtpManager.closeDevice(deviceId);
+7 −28
Original line number Diff line number Diff line
@@ -19,13 +19,12 @@ package com.android.mtp;
import android.app.Notification;
import android.app.Service;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbDevice;
import android.os.IBinder;
import android.util.Log;

import com.android.internal.util.Preconditions;

import java.io.IOException;

/**
@@ -36,6 +35,7 @@ import java.io.IOException;
public class MtpDocumentsService extends Service {
    static final String ACTION_OPEN_DEVICE = "com.android.mtp.OPEN_DEVICE";
    static final String ACTION_CLOSE_DEVICE = "com.android.mtp.CLOSE_DEVICE";
    static final String ACTION_UPDATE_NOTIFICATION = "com.android.mtp.UPDATE_NOTIFICATION";
    static final String EXTRA_DEVICE = "device";

    NotificationManager mNotificationManager;
@@ -55,33 +55,11 @@ public class MtpDocumentsService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // If intent is null, the service was restarted.
        if (intent != null) {
            final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
            final UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE);
            try {
                Preconditions.checkNotNull(device);
                switch (intent.getAction()) {
                    case ACTION_OPEN_DEVICE:
                        provider.openDevice(device.getDeviceId());
                        break;

                    case ACTION_CLOSE_DEVICE:
                        mNotificationManager.cancel(device.getDeviceId());
                        provider.closeDevice(device.getDeviceId());
                        break;

                    default:
                        throw new IllegalArgumentException("Received unknown intent action.");
                }
            } catch (IOException | InterruptedException | IllegalArgumentException error) {
                logErrorMessage(error);
            }
        } else {
            // TODO: Fetch devices again.
        }

        if (intent == null || ACTION_UPDATE_NOTIFICATION.equals(intent.getAction())) {
            return updateForegroundState() ? START_STICKY : START_NOT_STICKY;
        }
        return START_NOT_STICKY;
    }

    /**
     * Updates the foreground state of the service.
@@ -92,6 +70,7 @@ public class MtpDocumentsService extends Service {
        final int[] deviceIds = provider.getOpenedDeviceIds();
        int notificationId = 0;
        Notification notification = null;
        // TODO: Hide notification if the device has already been removed.
        for (final int deviceId : deviceIds) {
            try {
                final String title = getResources().getString(
+24 −3
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.mtp;

import android.content.ContentResolver;
@@ -7,6 +23,7 @@ import android.os.Process;
import android.provider.DocumentsContract;
import android.util.Log;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
@@ -64,9 +81,8 @@ final class RootScanner {

    /**
     * Starts to check new changes right away.
     * If the background thread has already gone, it restarts another background thread.
     */
    synchronized void resume() {
    synchronized CountDownLatch resume() {
        if (mExecutor == null) {
            // Only single thread updates the database.
            mExecutor = Executors.newSingleThreadExecutor();
@@ -75,8 +91,10 @@ final class RootScanner {
            // Cancel previous task.
            mCurrentTask.cancel(true);
        }
        mCurrentTask = new FutureTask<Void>(new UpdateRootsRunnable(), null);
        final UpdateRootsRunnable runnable = new UpdateRootsRunnable();
        mCurrentTask = new FutureTask<Void>(runnable, null);
        mExecutor.submit(mCurrentTask);
        return runnable.mFirstScanCompleted;
    }

    /**
@@ -98,6 +116,8 @@ final class RootScanner {
     * Runnable to scan roots and update the database information.
     */
    private final class UpdateRootsRunnable implements Runnable {
        final CountDownLatch mFirstScanCompleted = new CountDownLatch(1);

        @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
@@ -136,6 +156,7 @@ final class RootScanner {
                if (changed) {
                    notifyChange();
                }
                mFirstScanCompleted.countDown();
                pollingCount++;
                try {
                    // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is
+38 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.mtp;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;

/**
 * Sends intent to MtpDocumentsService.
 */
class ServiceIntentSender {
    private Context mContext;

    ServiceIntentSender(Context context) {
        mContext = context;
    }

    void sendUpdateNotificationIntent() {
        final Intent intent = new Intent(MtpDocumentsService.ACTION_UPDATE_NOTIFICATION);
        intent.setComponent(new ComponentName(mContext, MtpDocumentsService.class));
        mContext.startService(intent);
    }
}
Loading