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

Commit 163e4b6a authored by Daichi Hirono's avatar Daichi Hirono
Browse files

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
parent 7fa765a4
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -4202,4 +4202,8 @@
    <!-- 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>
+2 −0
Original line number Diff line number Diff line
@@ -2520,4 +2520,6 @@
  <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>
+19 −2
Original line number Diff line number Diff line
@@ -15,14 +15,31 @@
                <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" />
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>
+47 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}
+146 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
Loading