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