/* * Copyright (C) 2010 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.gallery3d.ingest.data; import android.annotation.TargetApi; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.mtp.MtpDevice; import android.os.Build; import android.util.Log; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * This class helps an application manage a list of connected MTP or PTP devices. * It listens for MTP devices being attached and removed from the USB host bus * and notifies the application when the MTP device list changes. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) public class MtpClient { private static final String TAG = "MtpClient"; private static final String ACTION_USB_PERMISSION = "com.android.gallery3d.ingest.action.USB_PERMISSION"; private final Context mContext; private final UsbManager mUsbManager; private final ArrayList mListeners = new ArrayList(); // mDevices contains all MtpDevices that have been seen by our client, // so we can inform when the device has been detached. // mDevices is also used for synchronization in this class. private final HashMap mDevices = new HashMap(); // List of MTP devices we should not try to open for which we are currently // asking for permission to open. private final ArrayList mRequestPermissionDevices = new ArrayList(); // List of MTP devices we should not try to open. // We add devices to this list if the user canceled a permission request or we were // unable to open the device. private final ArrayList mIgnoredDevices = new ArrayList(); private final PendingIntent mPermissionIntent; private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); String deviceName = usbDevice.getDeviceName(); synchronized (mDevices) { MtpDevice mtpDevice = mDevices.get(deviceName); if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { if (mtpDevice == null) { mtpDevice = openDeviceLocked(usbDevice); } if (mtpDevice != null) { for (Listener listener : mListeners) { listener.deviceAdded(mtpDevice); } } } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { if (mtpDevice != null) { mDevices.remove(deviceName); mRequestPermissionDevices.remove(deviceName); mIgnoredDevices.remove(deviceName); for (Listener listener : mListeners) { listener.deviceRemoved(mtpDevice); } } } else if (ACTION_USB_PERMISSION.equals(action)) { mRequestPermissionDevices.remove(deviceName); boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); Log.d(TAG, "ACTION_USB_PERMISSION: " + permission); if (permission) { if (mtpDevice == null) { mtpDevice = openDeviceLocked(usbDevice); } if (mtpDevice != null) { for (Listener listener : mListeners) { listener.deviceAdded(mtpDevice); } } } else { // so we don't ask for permission again mIgnoredDevices.add(deviceName); } } } } }; /** * An interface for being notified when MTP or PTP devices are attached * or removed. In the current implementation, only PTP devices are supported. */ public interface Listener { /** * Called when a new device has been added * * @param device the new device that was added */ public void deviceAdded(MtpDevice device); /** * Called when a new device has been removed * * @param device the device that was removed */ public void deviceRemoved(MtpDevice device); } /** * Tests to see if a {@link android.hardware.usb.UsbDevice} * supports the PTP protocol (typically used by digital cameras) * * @param device the device to test * @return true if the device is a PTP device. */ public static boolean isCamera(UsbDevice device) { int count = device.getInterfaceCount(); for (int i = 0; i < count; i++) { UsbInterface intf = device.getInterface(i); if (intf.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE && intf.getInterfaceSubclass() == 1 && intf.getInterfaceProtocol() == 1) { return true; } } return false; } /** * MtpClient constructor * * @param context the {@link android.content.Context} to use for the MtpClient */ public MtpClient(Context context) { mContext = context; mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); mPermissionIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_USB_PERMISSION), 0); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); filter.addAction(ACTION_USB_PERMISSION); context.registerReceiver(mUsbReceiver, filter); } /** * Opens the {@link android.hardware.usb.UsbDevice} for an MTP or PTP * device and return an {@link android.mtp.MtpDevice} for it. * * @param usbDevice the device to open * @return an MtpDevice for the device. */ private MtpDevice openDeviceLocked(UsbDevice usbDevice) { String deviceName = usbDevice.getDeviceName(); // don't try to open devices that we have decided to ignore // or are currently asking permission for if (isCamera(usbDevice) && !mIgnoredDevices.contains(deviceName) && !mRequestPermissionDevices.contains(deviceName)) { if (!mUsbManager.hasPermission(usbDevice)) { mUsbManager.requestPermission(usbDevice, mPermissionIntent); mRequestPermissionDevices.add(deviceName); } else { UsbDeviceConnection connection = mUsbManager.openDevice(usbDevice); if (connection != null) { MtpDevice mtpDevice = new MtpDevice(usbDevice); if (mtpDevice.open(connection)) { mDevices.put(usbDevice.getDeviceName(), mtpDevice); return mtpDevice; } else { // so we don't try to open it again mIgnoredDevices.add(deviceName); } } else { // so we don't try to open it again mIgnoredDevices.add(deviceName); } } } return null; } /** * Closes all resources related to the MtpClient object */ public void close() { mContext.unregisterReceiver(mUsbReceiver); } /** * Registers a {@link com.android.gallery3d.data.MtpClient.Listener} interface to receive * notifications when MTP or PTP devices are added or removed. * * @param listener the listener to register */ public void addListener(Listener listener) { synchronized (mDevices) { if (!mListeners.contains(listener)) { mListeners.add(listener); } } } /** * Unregisters a {@link com.android.gallery3d.data.MtpClient.Listener} interface. * * @param listener the listener to unregister */ public void removeListener(Listener listener) { synchronized (mDevices) { mListeners.remove(listener); } } /** * Retrieves a list of all currently connected {@link android.mtp.MtpDevice}. * * @return the list of MtpDevices */ public List getDeviceList() { synchronized (mDevices) { // Query the USB manager since devices might have attached // before we added our listener. for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { if (mDevices.get(usbDevice.getDeviceName()) == null) { openDeviceLocked(usbDevice); } } return new ArrayList(mDevices.values()); } } }