From 163e4b6ae8a51421c8a9cccfdc2fe872afd09971 Mon Sep 17 00:00:00 2001 From: Daichi Hirono Date: Mon, 18 Jan 2016 18:05:17 +0900 Subject: [PATCH] Add launch notification for MTP devices. The CL adds launch notification that are shown after MTP/PTP device is connected to Android device. By tapping the notification, Users can launch an applicaiton that can handle USB_DEVICE_ATTACHED intent of MTP device. BUG=26611224 Change-Id: I6fd179ccd436531dfff6ff9a50dc2b754c20f190 --- core/res/res/values/strings.xml | 4 + core/res/res/values/symbols.xml | 2 + packages/MtpDocumentsProvider/AndroidManifest.xml | 21 ++- .../src/com/android/mtp/ReceiverActivity.java | 47 +++++++ .../android/server/usb/MtpNotificationManager.java | 146 +++++++++++++++++++++ .../com/android/server/usb/UsbSettingsManager.java | 40 +++++- 6 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java create mode 100644 services/usb/java/com/android/server/usb/MtpNotificationManager.java diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index f415a42ccf0b..523826bf79ec 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4202,4 +4202,8 @@ User profile locked + + Connected to %1$s + + Tap to view files diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index bbfe48adaf3b..28c01d23ae7f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2520,4 +2520,6 @@ + + diff --git a/packages/MtpDocumentsProvider/AndroidManifest.xml b/packages/MtpDocumentsProvider/AndroidManifest.xml index 2090d20c94de..7ea54c9774bf 100644 --- a/packages/MtpDocumentsProvider/AndroidManifest.xml +++ b/packages/MtpDocumentsProvider/AndroidManifest.xml @@ -15,14 +15,31 @@ + + + + + + + + + + - + android:resource="@xml/device_filter" /> + + + diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java b/packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java new file mode 100644 index 000000000000..c7206a7929e0 --- /dev/null +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/ReceiverActivity.java @@ -0,0 +1,47 @@ +/* + * 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(); + } +} diff --git a/services/usb/java/com/android/server/usb/MtpNotificationManager.java b/services/usb/java/com/android/server/usb/MtpNotificationManager.java new file mode 100644 index 000000000000..203d35ed1fc4 --- /dev/null +++ b/services/usb/java/com/android/server/usb/MtpNotificationManager.java @@ -0,0 +1,146 @@ +/* + * 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().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); + } +} diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java index 7976cb895780..c4d7336b3f49 100644 --- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java @@ -494,6 +494,8 @@ class UsbSettingsManager { MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); + private final MtpNotificationManager mMtpNotificationManager; + public UsbSettingsManager(Context context, UserHandle user) { if (DEBUG) Slog.v(TAG, "Creating settings for " + user); @@ -522,6 +524,14 @@ class UsbSettingsManager { } 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) @@ -723,10 +733,20 @@ class UsbSettingsManager { } 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 matches; String defaultPackage; synchronized (mLock) { @@ -736,9 +756,6 @@ class UsbSettingsManager { 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); } @@ -751,6 +768,10 @@ class UsbSettingsManager { 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) { @@ -1224,4 +1245,11 @@ class UsbSettingsManager { } } } + + 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; + } } -- 2.11.0