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      */
deviceAdded(MtpDevice device)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      */
deviceRemoved(MtpDevice device)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    */
isCamera(UsbDevice device)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    */
MtpClient(Context context)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    */
openDeviceLocked(UsbDevice usbDevice)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    */
close()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    */
addListener(Listener listener)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    */
removeListener(Listener listener)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    */
getDeviceList()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