<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>
+ android:resource="@xml/device_filter" />
+ </receiver>
</application>
</manifest>
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);
}
}
getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
mDeviceToolkits.remove(deviceId);
mMtpManager.closeDevice(deviceId);
- if (!hasOpenedDevices()) {
+ if (getOpenedDeviceIds().length == 0) {
mRootScanner.pause();
}
}
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;
/**
* 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) {
@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 UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE);
- try {
- final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
- provider.openDevice(device.getDeviceId());
- return START_STICKY;
- } catch (IOException error) {
- Log.e(MtpDocumentsProvider.TAG, error.getMessage());
+ 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:
+ provider.closeDevice(device.getDeviceId());
+ break;
+
+ default:
+ throw new IllegalArgumentException("Received unknown intent action.");
}
- } else {
- Log.e(MtpDocumentsProvider.TAG, "Received unknown intent action.");
+ } catch (IOException | InterruptedException | IllegalArgumentException error) {
+ logErrorMessage(error);
}
+ } else {
+ // TODO: Fetch devices again.
}
- stopSelfIfNeeded();
- return Service.START_NOT_STICKY;
- }
- @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()) {
+ 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);
+ }
+ }
+ }
+ }
+ 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 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();
- }
+ private static void logErrorMessage(Exception exp) {
+ if (exp.getMessage() != null) {
+ Log.e(MtpDocumentsProvider.TAG, exp.getMessage());
+ } else {
+ Log.e(MtpDocumentsProvider.TAG, exp.toString());
}
}
}
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;
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);
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) {
+++ /dev/null
-/*
- * Copyright (C) 2015 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.app.Activity;
-import android.content.Intent;
-import android.hardware.usb.UsbManager;
-import android.os.Bundle;
-
-/**
- * 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 {
- @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);
- }
- finish();
- }
-}
--- /dev/null
+/*
+ * Copyright (C) 2015 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.net.Uri;
+
+public class UsbIntentReceiver extends BroadcastReceiver {
+ @Override
+ 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;
+ }
+ }
+
+ 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);
+ }
+}