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

Commit a57d9ed0 authored by Daichi Hirono's avatar Daichi Hirono
Browse files

Turn MtpDocumentsService foreground when devices are opend.

BUG=26047393

Change-Id: I70f69c5ddec500ed61d1a76fb9e2e296813b2a4e
parent a99fce54
Loading
Loading
Loading
Loading
+5 −8
Original line number Diff line number Diff line
@@ -15,17 +15,14 @@
                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
            </intent-filter>
        </provider>
        <activity android:name=".ReceiverActivity"
                  android:theme="@android:style/Theme.NoDisplay"
                  android:screenOrientation="locked"
                  android:excludeFromRecents="true"
                  android:enabled="false">
        <service android:name=".MtpDocumentsService" />
        <receiver android:name=".UsbIntentReceiver" android:exported="true">
            <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" />
        </activity>
        <service android:name=".MtpDocumentsService"></service>
        </receiver>
    </application>
</manifest>
+9 −3
Original line number Diff line number Diff line
@@ -253,9 +253,15 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        mRootScanner.notifyChange();
    }

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

    String getDeviceName(int deviceId) throws IOException {
        synchronized (mDeviceListLock) {
            return mMtpManager.getDeviceName(deviceId);
        }
    }

@@ -308,7 +314,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
        mDeviceToolkits.remove(deviceId);
        mMtpManager.closeDevice(deviceId);
        if (!hasOpenedDevices()) {
        if (getOpenedDeviceIds().length == 0) {
            mRootScanner.pause();
        }
    }
+74 −43
Original line number Diff line number Diff line
@@ -16,16 +16,16 @@

package com.android.mtp;

import android.app.Notification;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.app.NotificationManager;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.IBinder;
import android.util.Log;

import com.android.internal.util.Preconditions;

import java.io.IOException;

/**
@@ -34,10 +34,12 @@ import java.io.IOException;
 * starts to run when the first MTP device is opened, and stops when the last MTP device is closed.
 */
public class MtpDocumentsService extends Service {
    static final String ACTION_OPEN_DEVICE = "com.android.mtp.action.ACTION_OPEN_DEVICE";
    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 EXTRA_DEVICE = "device";
    private static final int FOREGROUND_NOTIFICATION_ID = 1;

    Receiver mReceiver;
    NotificationManager mNotificationManager;

    @Override
    public IBinder onBind(Intent intent) {
@@ -48,60 +50,89 @@ public class MtpDocumentsService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        final IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED);
        mReceiver = new Receiver();
        registerReceiver(mReceiver, filter);
        mNotificationManager = getSystemService(NotificationManager.class);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // If intent is null, the service was restarted.
        if (intent != null) {
            if (intent.getAction().equals(ACTION_OPEN_DEVICE)) {
            final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
            final UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE);
            try {
                    final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
                Preconditions.checkNotNull(device);
                switch (intent.getAction()) {
                    case ACTION_OPEN_DEVICE:
                        provider.openDevice(device.getDeviceId());
                    return START_STICKY;
                } catch (IOException error) {
                    Log.e(MtpDocumentsProvider.TAG, error.getMessage());
                }
            } else {
                Log.e(MtpDocumentsProvider.TAG, "Received unknown intent action.");
                        break;

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

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

    @Override
    public void onDestroy() {
        unregisterReceiver(mReceiver);
        mReceiver = null;
        super.onDestroy();
        return updateForegroundState() ? START_STICKY : START_NOT_STICKY;
    }

    private void stopSelfIfNeeded() {
    /**
     * Updates the foreground state of the service.
     * @return Whether the service is foreground or not.
     */
    private boolean updateForegroundState() {
        final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
        if (!provider.hasOpenedDevices()) {
            stopSelf();
        final int[] deviceIds = provider.getOpenedDeviceIds();
        String message = null;
        if (deviceIds.length != 0) {
            // TODO: Localize the message.
            // TODO: Add buttons "Open in Files" and "Open in Apps" if needed.
            if (deviceIds.length > 1) {
                message = deviceIds.length + " devices are being connected.";
            } else {
                try {
                    message = provider.getDeviceName(deviceIds[0]) + " is being connected.";
                } catch (IOException exp) {
                    logErrorMessage(exp);
                    // If we failed to obtain device name, it looks the device is unusable.
                    // Because this is the last device we opened, we should hide the notification
                    // for the case.
                    try {
                        provider.closeDevice(deviceIds[0]);
                    } catch (IOException | InterruptedException closeError) {
                        logErrorMessage(closeError);
                    }
                }

    private class Receiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
                final UsbDevice device =
                        (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
                try {
                    provider.closeDevice(device.getDeviceId());
                } catch (IOException | InterruptedException error) {
                    Log.e(MtpDocumentsProvider.TAG, error.getMessage());
            }
                stopSelfIfNeeded();
        }
        if (message != null) {
            final Notification notification = new Notification.Builder(this)
                    .setContentTitle(message)
                    .setSmallIcon(android.R.drawable.ic_menu_camera)
                    .setCategory(Notification.CATEGORY_SYSTEM)
                    .setPriority(Notification.PRIORITY_LOW)
                    .build();
            startForeground(FOREGROUND_NOTIFICATION_ID, notification);
            return true;
        } else {
            stopForeground(true /* removeNotification */);
            stopSelf();
            return false;
        }
    }

    private static void logErrorMessage(Exception exp) {
        if (exp.getMessage() != null) {
            Log.e(MtpDocumentsProvider.TAG, exp.getMessage());
        } else {
            Log.e(MtpDocumentsProvider.TAG, exp.toString());
        }
    }
}
+6 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.mtp.MtpEvent;
import android.mtp.MtpObjectInfo;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;
@@ -63,7 +64,7 @@ class MtpManager {

        if (!mManager.hasPermission(rawDevice)) {
            // Permission should be obtained via app selection dialog for intent.
            throw new IOException("No parmission to operate USB device.");
            throw new IOException("No permission to operate USB device.");
        }

        final MtpDevice device = new MtpDevice(rawDevice);
@@ -99,6 +100,10 @@ class MtpManager {
        return result;
    }

    String getDeviceName(int deviceId) throws IOException {
        return getDevice(deviceId).getDeviceInfo().getModel();
    }

    MtpRoot[] getRoots(int deviceId) throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
+45 −0
Original line number Diff line number Diff line
@@ -16,32 +16,30 @@

package com.android.mtp;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.net.Uri;

/**
 * Invisible activity to receive intents.
 * To show the application chooser for the UsbManager.ACTION_USB_DEVICE_ATTACHED intent, the intent
 * should be received by activity. The activity has NoDisplay theme and immediately terminate after
 * routing intent to MtpDocumentsService.
 */
public class ReceiverActivity extends Activity {
public class UsbIntentReceiver extends BroadcastReceiver {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(getIntent().getAction())) {
            final Intent serviceIntent = new Intent(
                    MtpDocumentsService.ACTION_OPEN_DEVICE,
                    null,
                    this,
                    MtpDocumentsService.class);
            serviceIntent.putExtra(
                    UsbManager.EXTRA_DEVICE,
                    getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE));
            startService(serviceIntent);
    public void onReceive(Context context, Intent intent) {
        final UsbDevice device = intent.getExtras().getParcelable(UsbManager.EXTRA_DEVICE);
        switch (intent.getAction()) {
            case UsbManager.ACTION_USB_DEVICE_ATTACHED:
                startService(context, MtpDocumentsService.ACTION_OPEN_DEVICE, device);
                break;
            case UsbManager.ACTION_USB_DEVICE_DETACHED:
                startService(context, MtpDocumentsService.ACTION_CLOSE_DEVICE, device);
                break;
        }
    }
        finish();

    private void startService(Context context, String action, UsbDevice device) {
        final Intent intent = new Intent(action, Uri.EMPTY, context, MtpDocumentsService.class);
        intent.putExtra(MtpDocumentsService.EXTRA_DEVICE, device);
        context.startService(intent);
    }
}
Loading