<!-- Notification detail shown when user profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=30] -->
<string name="user_encrypted_detail">User profile locked</string>
+ <!-- Title of notification shown after a MTP device is connected to Android. -->
+ <string name="usb_mtp_launch_notification_title">Connected to <xliff:g id="product_name">%1$s</xliff:g></string>
+ <!-- Description of notification shown after a MTP device is connected to Android. -->
+ <string name="usb_mtp_launch_notification_description">Tap to view files</string>
</resources>
<java-symbol type="string" name="user_encrypted_message" />
<java-symbol type="string" name="user_encrypted_detail" />
+ <java-symbol type="string" name="usb_mtp_launch_notification_title" />
+ <java-symbol type="string" name="usb_mtp_launch_notification_description" />
</resources>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
+
<service android:name=".MtpDocumentsService" />
+
+ <activity android:name=".ReceiverActivity"
+ android:theme="@android:style/Theme.NoDisplay"
+ android:screenOrientation="locked"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+ </intent-filter>
+ <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
+ android:resource="@xml/device_filter" />
+ </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" />
+ android:resource="@xml/device_filter" />
+ <intent-filter>
+ <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
+ </intent-filter>
</receiver>
</application>
</manifest>
--- /dev/null
+/*
+ * 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.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+
+/**
+ * Invisible activity to receive intents.
+ * To show Files app 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 DocumentsUI.
+ */
+public class ReceiverActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(getIntent().getAction())) {
+ // TODO: To obtain data URI for the attached device, we need to wait until RootScanner
+ // found the device and add it to database. Set correct root URI, and use ACTION_BROWSE
+ // to launch Documents UI.
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(new ComponentName(
+ "com.android.documentsui", "com.android.documentsui.LauncherActivity"));
+ this.startActivity(intent);
+ }
+ finish();
+ }
+}
--- /dev/null
+/*
+ * 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+ * Manager for MTP storage notification.
+ */
+class MtpNotificationManager {
+ private static final String TAG = "UsbMtpNotificationManager";
+
+ /**
+ * Subclass for PTP.
+ */
+ private static final int SUBCLASS_STILL_IMAGE_CAPTURE = 1;
+
+ /**
+ * Subclass for Android style MTP.
+ */
+ private static final int SUBCLASS_MTP = 0xff;
+
+ /**
+ * Protocol for Picture Transfer Protocol (PIMA 15470).
+ */
+ private static final int PROTOCOL_PTP = 1;
+
+ /**
+ * Protocol for Android style MTP.
+ */
+ private static final int PROTOCOL_MTP = 0;
+
+ private static final String ACTION_OPEN_IN_APPS = "com.android.server.usb.ACTION_OPEN_IN_APPS";
+
+ private final Context mContext;
+ private final OnOpenInAppListener mListener;
+
+ MtpNotificationManager(Context context, OnOpenInAppListener listener) {
+ mContext = context;
+ mListener = listener;
+ final Receiver receiver = new Receiver();
+ context.registerReceiver(receiver, new IntentFilter(ACTION_OPEN_IN_APPS));
+ }
+
+ void showNotification(UsbDevice device) {
+ final Resources resources = mContext.getResources();
+ final String title = resources.getString(
+ com.android.internal.R.string.usb_mtp_launch_notification_title,
+ device.getProductName());
+ final String description = resources.getString(
+ com.android.internal.R.string.usb_mtp_launch_notification_description);
+ final Notification.Builder builder = new Notification.Builder(mContext)
+ .setContentTitle(title)
+ .setContentText(description)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_data_usb)
+ .setCategory(Notification.CATEGORY_SYSTEM);
+
+ final Intent intent = new Intent(ACTION_OPEN_IN_APPS);
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+
+ final PendingIntent openIntent = PendingIntent.getBroadcastAsUser(
+ mContext,
+ device.getDeviceId(),
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT,
+ UserHandle.SYSTEM);
+ builder.setContentIntent(openIntent);
+
+ final Notification notification = builder.build();
+ notification.flags |= Notification.FLAG_LOCAL_ONLY;
+
+ mContext.getSystemService(NotificationManager.class).notify(
+ TAG, device.getDeviceId(), notification);
+ }
+
+ void hideNotification(UsbDevice device) {
+ mContext.getSystemService(NotificationManager.class).cancel(TAG, device.getDeviceId());
+ }
+
+ private class Receiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final UsbDevice device =
+ intent.getExtras().<UsbDevice>getParcelable(UsbManager.EXTRA_DEVICE);
+ if (device == null) {
+ return;
+ }
+ switch (intent.getAction()) {
+ case ACTION_OPEN_IN_APPS:
+ mListener.onOpenInApp(device);
+ break;
+ }
+ }
+ }
+
+ static boolean isMtpDevice(UsbDevice device) {
+ for (int i = 0; i < device.getInterfaceCount(); i++) {
+ final UsbInterface usbInterface = device.getInterface(i);
+ if ((usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE &&
+ usbInterface.getInterfaceSubclass() == SUBCLASS_STILL_IMAGE_CAPTURE &&
+ usbInterface.getInterfaceProtocol() == PROTOCOL_PTP)) {
+ return true;
+ }
+ if (usbInterface.getInterfaceClass() == UsbConstants.USB_SUBCLASS_VENDOR_SPEC &&
+ usbInterface.getInterfaceSubclass() == SUBCLASS_MTP &&
+ usbInterface.getInterfaceProtocol() == PROTOCOL_MTP &&
+ usbInterface.getName().equals("MTP")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static interface OnOpenInAppListener {
+ void onOpenInApp(UsbDevice device);
+ }
+}
MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
+ private final MtpNotificationManager mMtpNotificationManager;
+
public UsbSettingsManager(Context context, UserHandle user) {
if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
}
mPackageMonitor.register(mUserContext, null, true);
+ mMtpNotificationManager = new MtpNotificationManager(
+ context,
+ new MtpNotificationManager.OnOpenInAppListener() {
+ @Override
+ public void onOpenInApp(UsbDevice device) {
+ resolveActivity(createDeviceAttachedIntent(device), device);
+ }
+ });
}
private void readPreference(XmlPullParser parser)
}
public void deviceAttached(UsbDevice device) {
- Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
- intent.putExtra(UsbManager.EXTRA_DEVICE, device);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final Intent intent = createDeviceAttachedIntent(device);
+
+ // Send broadcast to running activity with registered intent
+ mUserContext.sendBroadcast(intent);
+
+ if (MtpNotificationManager.isMtpDevice(device)) {
+ // Show notification if the device is MTP storage.
+ mMtpNotificationManager.showNotification(device);
+ } else {
+ resolveActivity(intent, device);
+ }
+ }
+ private void resolveActivity(Intent intent, UsbDevice device) {
ArrayList<ResolveInfo> matches;
String defaultPackage;
synchronized (mLock) {
defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device));
}
- // Send broadcast to running activity with registered intent
- mUserContext.sendBroadcast(intent);
-
// Start activity with registered intent
resolveActivity(intent, matches, defaultPackage, device, null);
}
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
if (DEBUG) Slog.d(TAG, "usbDeviceRemoved, sending " + intent);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+ if (MtpNotificationManager.isMtpDevice(device)) {
+ mMtpNotificationManager.hideNotification(device);
+ }
}
public void accessoryAttached(UsbAccessory accessory) {
}
}
}
+
+ private static Intent createDeviceAttachedIntent(UsbDevice device) {
+ Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
}