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