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