• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.data;
18 
19 import android.app.PendingIntent;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.hardware.usb.UsbConstants;
25 import android.hardware.usb.UsbDevice;
26 import android.hardware.usb.UsbDeviceConnection;
27 import android.hardware.usb.UsbInterface;
28 import android.hardware.usb.UsbManager;
29 import android.mtp.MtpDevice;
30 import android.mtp.MtpObjectInfo;
31 import android.mtp.MtpStorageInfo;
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 public class MtpClient {
44 
45     private static final String TAG = "MtpClient";
46 
47     private static final String ACTION_USB_PERMISSION =
48             "android.mtp.MtpClient.action.USB_PERMISSION";
49 
50     private final Context mContext;
51     private final UsbManager mUsbManager;
52     private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
53     // mDevices contains all MtpDevices that have been seen by our client,
54     // so we can inform when the device has been detached.
55     // mDevices is also used for synchronization in this class.
56     private final HashMap<String, MtpDevice> mDevices = new HashMap<String, MtpDevice>();
57     // List of MTP devices we should not try to open for which we are currently
58     // asking for permission to open.
59     private final ArrayList<String> mRequestPermissionDevices = new ArrayList<String>();
60     // List of MTP devices we should not try to open.
61     // We add devices to this list if the user canceled a permission request or we were
62     // unable to open the device.
63     private final ArrayList<String> mIgnoredDevices = new ArrayList<String>();
64 
65     private final PendingIntent mPermissionIntent;
66 
67     private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
68         @Override
69         public void onReceive(Context context, Intent intent) {
70             String action = intent.getAction();
71             UsbDevice usbDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
72             String deviceName = usbDevice.getDeviceName();
73 
74             synchronized (mDevices) {
75                 MtpDevice mtpDevice = mDevices.get(deviceName);
76 
77                 if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
78                     if (mtpDevice == null) {
79                         mtpDevice = openDeviceLocked(usbDevice);
80                     }
81                     if (mtpDevice != null) {
82                         for (Listener listener : mListeners) {
83                             listener.deviceAdded(mtpDevice);
84                         }
85                     }
86                 } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
87                     if (mtpDevice != null) {
88                         mDevices.remove(deviceName);
89                         mRequestPermissionDevices.remove(deviceName);
90                         mIgnoredDevices.remove(deviceName);
91                         for (Listener listener : mListeners) {
92                             listener.deviceRemoved(mtpDevice);
93                         }
94                     }
95                 } else if (ACTION_USB_PERMISSION.equals(action)) {
96                     mRequestPermissionDevices.remove(deviceName);
97                     boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,
98                             false);
99                     Log.d(TAG, "ACTION_USB_PERMISSION: " + permission);
100                     if (permission) {
101                         if (mtpDevice == null) {
102                             mtpDevice = openDeviceLocked(usbDevice);
103                         }
104                         if (mtpDevice != null) {
105                             for (Listener listener : mListeners) {
106                                 listener.deviceAdded(mtpDevice);
107                             }
108                         }
109                     } else {
110                         // so we don't ask for permission again
111                         mIgnoredDevices.add(deviceName);
112                     }
113                 }
114             }
115         }
116     };
117 
118     /**
119      * An interface for being notified when MTP or PTP devices are attached
120      * or removed.  In the current implementation, only PTP devices are supported.
121      */
122     public interface Listener {
123         /**
124          * Called when a new device has been added
125          *
126          * @param device the new device that was added
127          */
deviceAdded(MtpDevice device)128         public void deviceAdded(MtpDevice device);
129 
130         /**
131          * Called when a new device has been removed
132          *
133          * @param device the device that was removed
134          */
deviceRemoved(MtpDevice device)135         public void deviceRemoved(MtpDevice device);
136     }
137 
138     /**
139      * Tests to see if a {@link android.hardware.usb.UsbDevice}
140      * supports the PTP protocol (typically used by digital cameras)
141      *
142      * @param device the device to test
143      * @return true if the device is a PTP device.
144      */
isCamera(UsbDevice device)145     static public boolean isCamera(UsbDevice device) {
146         int count = device.getInterfaceCount();
147         for (int i = 0; i < count; i++) {
148             UsbInterface intf = device.getInterface(i);
149             if (intf.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE &&
150                     intf.getInterfaceSubclass() == 1 &&
151                     intf.getInterfaceProtocol() == 1) {
152                 return true;
153             }
154         }
155         return false;
156     }
157 
158     /**
159      * MtpClient constructor
160      *
161      * @param context the {@link android.content.Context} to use for the MtpClient
162      */
MtpClient(Context context)163     public MtpClient(Context context) {
164         mContext = context;
165         mUsbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE);
166         mPermissionIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_USB_PERMISSION), 0);
167         IntentFilter filter = new IntentFilter();
168         filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
169         filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
170         filter.addAction(ACTION_USB_PERMISSION);
171         context.registerReceiver(mUsbReceiver, filter);
172     }
173 
174     /**
175      * Opens the {@link android.hardware.usb.UsbDevice} for an MTP or PTP
176      * device and return an {@link android.mtp.MtpDevice} for it.
177      *
178      * @param device the device to open
179      * @return an MtpDevice for the device.
180      */
openDeviceLocked(UsbDevice usbDevice)181     private MtpDevice openDeviceLocked(UsbDevice usbDevice) {
182         String deviceName = usbDevice.getDeviceName();
183 
184         // don't try to open devices that we have decided to ignore
185         // or are currently asking permission for
186         if (isCamera(usbDevice) && !mIgnoredDevices.contains(deviceName)
187                 && !mRequestPermissionDevices.contains(deviceName)) {
188             if (!mUsbManager.hasPermission(usbDevice)) {
189                 mUsbManager.requestPermission(usbDevice, mPermissionIntent);
190                 mRequestPermissionDevices.add(deviceName);
191             } else {
192                 UsbDeviceConnection connection = mUsbManager.openDevice(usbDevice);
193                 if (connection != null) {
194                     MtpDevice mtpDevice = new MtpDevice(usbDevice);
195                     if (mtpDevice.open(connection)) {
196                         mDevices.put(usbDevice.getDeviceName(), mtpDevice);
197                         return mtpDevice;
198                     } else {
199                         // so we don't try to open it again
200                         mIgnoredDevices.add(deviceName);
201                     }
202                 } else {
203                     // so we don't try to open it again
204                     mIgnoredDevices.add(deviceName);
205                 }
206             }
207         }
208         return null;
209     }
210 
211     /**
212      * Closes all resources related to the MtpClient object
213      */
close()214     public void close() {
215         mContext.unregisterReceiver(mUsbReceiver);
216     }
217 
218     /**
219      * Registers a {@link android.mtp.MtpClient.Listener} interface to receive
220      * notifications when MTP or PTP devices are added or removed.
221      *
222      * @param listener the listener to register
223      */
addListener(Listener listener)224     public void addListener(Listener listener) {
225         synchronized (mDevices) {
226             if (!mListeners.contains(listener)) {
227                 mListeners.add(listener);
228             }
229         }
230     }
231 
232     /**
233      * Unregisters a {@link android.mtp.MtpClient.Listener} interface.
234      *
235      * @param listener the listener to unregister
236      */
removeListener(Listener listener)237     public void removeListener(Listener listener) {
238         synchronized (mDevices) {
239             mListeners.remove(listener);
240         }
241     }
242 
243     /**
244      * Retrieves an {@link android.mtp.MtpDevice} object for the USB device
245      * with the given name.
246      *
247      * @param deviceName the name of the USB device
248      * @return the MtpDevice, or null if it does not exist
249      */
getDevice(String deviceName)250     public MtpDevice getDevice(String deviceName) {
251         synchronized (mDevices) {
252             return mDevices.get(deviceName);
253         }
254     }
255 
256     /**
257      * Retrieves an {@link android.mtp.MtpDevice} object for the USB device
258      * with the given ID.
259      *
260      * @param id the ID of the USB device
261      * @return the MtpDevice, or null if it does not exist
262      */
getDevice(int id)263     public MtpDevice getDevice(int id) {
264         synchronized (mDevices) {
265             return mDevices.get(UsbDevice.getDeviceName(id));
266         }
267     }
268 
269     /**
270      * Retrieves a list of all currently connected {@link android.mtp.MtpDevice}.
271      *
272      * @return the list of MtpDevices
273      */
getDeviceList()274     public List<MtpDevice> getDeviceList() {
275         synchronized (mDevices) {
276             // Query the USB manager since devices might have attached
277             // before we added our listener.
278             for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
279                 if (mDevices.get(usbDevice.getDeviceName()) == null) {
280                     openDeviceLocked(usbDevice);
281                 }
282             }
283 
284             return new ArrayList<MtpDevice>(mDevices.values());
285         }
286     }
287 
288     /**
289      * Retrieves a list of all {@link android.mtp.MtpStorageInfo}
290      * for the MTP or PTP device with the given USB device name
291      *
292      * @param deviceName the name of the USB device
293      * @return the list of MtpStorageInfo
294      */
getStorageList(String deviceName)295     public List<MtpStorageInfo> getStorageList(String deviceName) {
296         MtpDevice device = getDevice(deviceName);
297         if (device == null) {
298             return null;
299         }
300         int[] storageIds = device.getStorageIds();
301         if (storageIds == null) {
302             return null;
303         }
304 
305         int length = storageIds.length;
306         ArrayList<MtpStorageInfo> storageList = new ArrayList<MtpStorageInfo>(length);
307         for (int i = 0; i < length; i++) {
308             MtpStorageInfo info = device.getStorageInfo(storageIds[i]);
309             if (info == null) {
310                 Log.w(TAG, "getStorageInfo failed");
311             } else {
312                 storageList.add(info);
313             }
314         }
315         return storageList;
316     }
317 
318     /**
319      * Retrieves the {@link android.mtp.MtpObjectInfo} for an object on
320      * the MTP or PTP device with the given USB device name with the given
321      * object handle
322      *
323      * @param deviceName the name of the USB device
324      * @param objectHandle handle of the object to query
325      * @return the MtpObjectInfo
326      */
getObjectInfo(String deviceName, int objectHandle)327     public MtpObjectInfo getObjectInfo(String deviceName, int objectHandle) {
328         MtpDevice device = getDevice(deviceName);
329         if (device == null) {
330             return null;
331         }
332         return device.getObjectInfo(objectHandle);
333     }
334 
335     /**
336      * Deletes an object on the MTP or PTP device with the given USB device name.
337      *
338      * @param deviceName the name of the USB device
339      * @param objectHandle handle of the object to delete
340      * @return true if the deletion succeeds
341      */
deleteObject(String deviceName, int objectHandle)342     public boolean deleteObject(String deviceName, int objectHandle) {
343         MtpDevice device = getDevice(deviceName);
344         if (device == null) {
345             return false;
346         }
347         return device.deleteObject(objectHandle);
348     }
349 
350     /**
351      * Retrieves a list of {@link android.mtp.MtpObjectInfo} for all objects
352      * on the MTP or PTP device with the given USB device name and given storage ID
353      * and/or object handle.
354      * If the object handle is zero, then all objects in the root of the storage unit
355      * will be returned. Otherwise, all immediate children of the object will be returned.
356      * If the storage ID is also zero, then all objects on all storage units will be returned.
357      *
358      * @param deviceName the name of the USB device
359      * @param storageId the ID of the storage unit to query, or zero for all
360      * @param objectHandle the handle of the parent object to query, or zero for the storage root
361      * @return the list of MtpObjectInfo
362      */
getObjectList(String deviceName, int storageId, int objectHandle)363     public List<MtpObjectInfo> getObjectList(String deviceName, int storageId, int objectHandle) {
364         MtpDevice device = getDevice(deviceName);
365         if (device == null) {
366             return null;
367         }
368         if (objectHandle == 0) {
369             // all objects in root of storage
370             objectHandle = 0xFFFFFFFF;
371         }
372         int[] handles = device.getObjectHandles(storageId, 0, objectHandle);
373         if (handles == null) {
374             return null;
375         }
376 
377         int length = handles.length;
378         ArrayList<MtpObjectInfo> objectList = new ArrayList<MtpObjectInfo>(length);
379         for (int i = 0; i < length; i++) {
380             MtpObjectInfo info = device.getObjectInfo(handles[i]);
381             if (info == null) {
382                 Log.w(TAG, "getObjectInfo failed");
383             } else {
384                 objectList.add(info);
385             }
386         }
387         return objectList;
388     }
389 
390     /**
391      * Returns the data for an object as a byte array.
392      *
393      * @param deviceName the name of the USB device containing the object
394      * @param objectHandle handle of the object to read
395      * @param objectSize the size of the object (this should match
396      *      {@link android.mtp.MtpObjectInfo#getCompressedSize}
397      * @return the object's data, or null if reading fails
398      */
getObject(String deviceName, int objectHandle, int objectSize)399     public byte[] getObject(String deviceName, int objectHandle, int objectSize) {
400         MtpDevice device = getDevice(deviceName);
401         if (device == null) {
402             return null;
403         }
404         return device.getObject(objectHandle, objectSize);
405     }
406 
407     /**
408      * Returns the thumbnail data for an object as a byte array.
409      *
410      * @param deviceName the name of the USB device containing the object
411      * @param objectHandle handle of the object to read
412      * @return the object's thumbnail, or null if reading fails
413      */
getThumbnail(String deviceName, int objectHandle)414     public byte[] getThumbnail(String deviceName, int objectHandle) {
415         MtpDevice device = getDevice(deviceName);
416         if (device == null) {
417             return null;
418         }
419         return device.getThumbnail(objectHandle);
420     }
421 
422     /**
423      * Copies the data for an object to a file in external storage.
424      *
425      * @param deviceName the name of the USB device containing the object
426      * @param objectHandle handle of the object to read
427      * @param destPath path to destination for the file transfer.
428      *      This path should be in the external storage as defined by
429      *      {@link android.os.Environment#getExternalStorageDirectory}
430      * @return true if the file transfer succeeds
431      */
importFile(String deviceName, int objectHandle, String destPath)432     public boolean importFile(String deviceName, int objectHandle, String destPath) {
433         MtpDevice device = getDevice(deviceName);
434         if (device == null) {
435             return false;
436         }
437         return device.importFile(objectHandle, destPath);
438     }
439 }
440