Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.gallery3d.ingest.data;
     18 
     19 import android.annotation.TargetApi;
     20 import android.app.PendingIntent;
     21 import android.content.BroadcastReceiver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.hardware.usb.UsbConstants;
     26 import android.hardware.usb.UsbDevice;
     27 import android.hardware.usb.UsbDeviceConnection;
     28 import android.hardware.usb.UsbInterface;
     29 import android.hardware.usb.UsbManager;
     30 import android.mtp.MtpDevice;
     31 import android.os.Build;
     32 import android.util.Log;
     33 
     34 import java.util.ArrayList;
     35 import java.util.HashMap;
     36 import java.util.List;
     37 
     38 /**
     39  * This class helps an application manage a list of connected MTP or PTP devices.
     40  * It listens for MTP devices being attached and removed from the USB host bus
     41  * and notifies the application when the MTP device list changes.
     42  */
     43 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
     44 public class MtpClient {
     45 
     46   private static final String TAG = "MtpClient";
     47 
     48   private static final String ACTION_USB_PERMISSION =
     49       "com.android.gallery3d.ingest.action.USB_PERMISSION";
     50 
     51   private final Context mContext;
     52   private final UsbManager mUsbManager;
     53   private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
     54   // mDevices contains all MtpDevices that have been seen by our client,
     55   // so we can inform when the device has been detached.
     56   // mDevices is also used for synchronization in this class.
     57   private final HashMap<String, MtpDevice> mDevices = new HashMap<String, MtpDevice>();
     58   // List of MTP devices we should not try to open for which we are currently
     59   // asking for permission to open.
     60   private final ArrayList<String> mRequestPermissionDevices = new ArrayList<String>();
     61   // List of MTP devices we should not try to open.
     62   // We add devices to this list if the user canceled a permission request or we were
     63   // unable to open the device.
     64   private final ArrayList<String> mIgnoredDevices = new ArrayList<String>();
     65 
     66   private final PendingIntent mPermissionIntent;
     67 
     68   private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
     69     @Override
     70     public void onReceive(Context context, Intent intent) {
     71       String action = intent.getAction();
     72       UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
     73       String deviceName = usbDevice.getDeviceName();
     74 
     75       synchronized (mDevices) {
     76         MtpDevice mtpDevice = mDevices.get(deviceName);
     77 
     78         if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
     79           if (mtpDevice == null) {
     80             mtpDevice = openDeviceLocked(usbDevice);
     81           }
     82           if (mtpDevice != null) {
     83             for (Listener listener : mListeners) {
     84               listener.deviceAdded(mtpDevice);
     85             }
     86           }
     87         } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
     88           if (mtpDevice != null) {
     89             mDevices.remove(deviceName);
     90             mRequestPermissionDevices.remove(deviceName);
     91             mIgnoredDevices.remove(deviceName);
     92             for (Listener listener : mListeners) {
     93               listener.deviceRemoved(mtpDevice);
     94             }
     95           }
     96         } else if (ACTION_USB_PERMISSION.equals(action)) {
     97           mRequestPermissionDevices.remove(deviceName);
     98           boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,
     99               false);
    100           Log.d(TAG, "ACTION_USB_PERMISSION: " + permission);
    101           if (permission) {
    102             if (mtpDevice == null) {
    103               mtpDevice = openDeviceLocked(usbDevice);
    104             }
    105             if (mtpDevice != null) {
    106               for (Listener listener : mListeners) {
    107                 listener.deviceAdded(mtpDevice);
    108               }
    109             }
    110           } else {
    111             // so we don't ask for permission again
    112             mIgnoredDevices.add(deviceName);
    113           }
    114         }
    115       }
    116     }
    117   };
    118 
    119   /**
    120    * An interface for being notified when MTP or PTP devices are attached
    121    * or removed.  In the current implementation, only PTP devices are supported.
    122    */
    123   public interface Listener {
    124     /**
    125      * Called when a new device has been added
    126      *
    127      * @param device the new device that was added
    128      */
    129     public void deviceAdded(MtpDevice device);
    130 
    131     /**
    132      * Called when a new device has been removed
    133      *
    134      * @param device the device that was removed
    135      */
    136     public void deviceRemoved(MtpDevice device);
    137   }
    138 
    139   /**
    140    * Tests to see if a {@link android.hardware.usb.UsbDevice}
    141    * supports the PTP protocol (typically used by digital cameras)
    142    *
    143    * @param device the device to test
    144    * @return true if the device is a PTP device.
    145    */
    146   public static boolean isCamera(UsbDevice device) {
    147     int count = device.getInterfaceCount();
    148     for (int i = 0; i < count; i++) {
    149       UsbInterface intf = device.getInterface(i);
    150       if (intf.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE &&
    151           intf.getInterfaceSubclass() == 1 &&
    152           intf.getInterfaceProtocol() == 1) {
    153         return true;
    154       }
    155     }
    156     return false;
    157   }
    158 
    159   /**
    160    * MtpClient constructor
    161    *
    162    * @param context the {@link android.content.Context} to use for the MtpClient
    163    */
    164   public MtpClient(Context context) {
    165     mContext = context;
    166     mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
    167     mPermissionIntent = PendingIntent.getBroadcast(mContext, 0,
    168         new Intent(ACTION_USB_PERMISSION), 0);
    169     IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
    170     filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
    171     filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
    172     filter.addAction(ACTION_USB_PERMISSION);
    173     context.registerReceiver(mUsbReceiver, filter);
    174   }
    175 
    176   /**
    177    * Opens the {@link android.hardware.usb.UsbDevice} for an MTP or PTP
    178    * device and return an {@link android.mtp.MtpDevice} for it.
    179    *
    180    * @param usbDevice the device to open
    181    * @return an MtpDevice for the device.
    182    */
    183   private MtpDevice openDeviceLocked(UsbDevice usbDevice) {
    184     String deviceName = usbDevice.getDeviceName();
    185 
    186     // don't try to open devices that we have decided to ignore
    187     // or are currently asking permission for
    188     if (isCamera(usbDevice) && !mIgnoredDevices.contains(deviceName)
    189         && !mRequestPermissionDevices.contains(deviceName)) {
    190       if (!mUsbManager.hasPermission(usbDevice)) {
    191         mUsbManager.requestPermission(usbDevice, mPermissionIntent);
    192         mRequestPermissionDevices.add(deviceName);
    193       } else {
    194         UsbDeviceConnection connection = mUsbManager.openDevice(usbDevice);
    195         if (connection != null) {
    196           MtpDevice mtpDevice = new MtpDevice(usbDevice);
    197           if (mtpDevice.open(connection)) {
    198             mDevices.put(usbDevice.getDeviceName(), mtpDevice);
    199             return mtpDevice;
    200           } else {
    201             // so we don't try to open it again
    202             mIgnoredDevices.add(deviceName);
    203           }
    204         } else {
    205           // so we don't try to open it again
    206           mIgnoredDevices.add(deviceName);
    207         }
    208       }
    209     }
    210     return null;
    211   }
    212 
    213   /**
    214    * Closes all resources related to the MtpClient object
    215    */
    216   public void close() {
    217     mContext.unregisterReceiver(mUsbReceiver);
    218   }
    219 
    220   /**
    221    * Registers a {@link com.android.gallery3d.data.MtpClient.Listener} interface to receive
    222    * notifications when MTP or PTP devices are added or removed.
    223    *
    224    * @param listener the listener to register
    225    */
    226   public void addListener(Listener listener) {
    227     synchronized (mDevices) {
    228       if (!mListeners.contains(listener)) {
    229         mListeners.add(listener);
    230       }
    231     }
    232   }
    233 
    234   /**
    235    * Unregisters a {@link com.android.gallery3d.data.MtpClient.Listener} interface.
    236    *
    237    * @param listener the listener to unregister
    238    */
    239   public void removeListener(Listener listener) {
    240     synchronized (mDevices) {
    241       mListeners.remove(listener);
    242     }
    243   }
    244 
    245 
    246   /**
    247    * Retrieves a list of all currently connected {@link android.mtp.MtpDevice}.
    248    *
    249    * @return the list of MtpDevices
    250    */
    251   public List<MtpDevice> getDeviceList() {
    252     synchronized (mDevices) {
    253       // Query the USB manager since devices might have attached
    254       // before we added our listener.
    255       for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
    256         if (mDevices.get(usbDevice.getDeviceName()) == null) {
    257           openDeviceLocked(usbDevice);
    258         }
    259       }
    260 
    261       return new ArrayList<MtpDevice>(mDevices.values());
    262     }
    263   }
    264 
    265 
    266 }
    267