Loading packages/MtpDocumentsProvider/AndroidManifest.xml +5 −8 Original line number Diff line number Diff line Loading @@ -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> packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +9 −3 Original line number Diff line number Diff line Loading @@ -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); } } Loading Loading @@ -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(); } } Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java +74 −43 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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) { Loading @@ -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()); } } } packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +6 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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) { Loading packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java→packages/MtpDocumentsProvider/src/com/android/mtp/UsbIntentReceiver.java +45 −0 Original line number Diff line number Diff line Loading @@ -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
packages/MtpDocumentsProvider/AndroidManifest.xml +5 −8 Original line number Diff line number Diff line Loading @@ -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>
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +9 −3 Original line number Diff line number Diff line Loading @@ -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); } } Loading Loading @@ -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(); } } Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java +74 −43 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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) { Loading @@ -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()); } } }
packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +6 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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) { Loading
packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java→packages/MtpDocumentsProvider/src/com/android/mtp/UsbIntentReceiver.java +45 −0 Original line number Diff line number Diff line Loading @@ -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); } }