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