1 /* 2 * Copyright (C) 2015 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.mtp; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.hardware.usb.UsbConstants; 22 import android.hardware.usb.UsbDevice; 23 import android.hardware.usb.UsbDeviceConnection; 24 import android.hardware.usb.UsbInterface; 25 import android.hardware.usb.UsbManager; 26 import android.mtp.MtpConstants; 27 import android.mtp.MtpDevice; 28 import android.mtp.MtpDeviceInfo; 29 import android.mtp.MtpEvent; 30 import android.mtp.MtpObjectInfo; 31 import android.mtp.MtpStorageInfo; 32 import android.os.CancellationSignal; 33 import android.os.ParcelFileDescriptor; 34 import android.util.Log; 35 import android.util.SparseArray; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 39 import java.io.FileNotFoundException; 40 import java.io.IOException; 41 import java.util.ArrayList; 42 43 /** 44 * The model wrapping android.mtp API. 45 */ 46 class MtpManager { 47 final static int OBJECT_HANDLE_ROOT_CHILDREN = -1; 48 49 /** 50 * Subclass for PTP. 51 */ 52 private static final int SUBCLASS_STILL_IMAGE_CAPTURE = 1; 53 54 /** 55 * Subclass for Android style MTP. 56 */ 57 private static final int SUBCLASS_MTP = 0xff; 58 59 /** 60 * Protocol for Picture Transfer Protocol (PIMA 15470). 61 */ 62 private static final int PROTOCOL_PICTURE_TRANSFER = 1; 63 64 /** 65 * Protocol for Android style MTP. 66 */ 67 private static final int PROTOCOL_MTP = 0; 68 69 private final UsbManager mManager; 70 private final SparseArray<MtpDevice> mDevices = new SparseArray<>(); 71 MtpManager(Context context)72 MtpManager(Context context) { 73 mManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); 74 } 75 openDevice(int deviceId)76 synchronized MtpDeviceRecord openDevice(int deviceId) throws IOException { 77 UsbDevice rawDevice = null; 78 for (final UsbDevice candidate : mManager.getDeviceList().values()) { 79 if (candidate.getDeviceId() == deviceId) { 80 rawDevice = candidate; 81 break; 82 } 83 } 84 85 ensureNotNull(rawDevice, "Not found USB device: " + deviceId); 86 87 if (!mManager.hasPermission(rawDevice)) { 88 mManager.grantPermission(rawDevice); 89 if (!mManager.hasPermission(rawDevice)) { 90 throw new IOException("Failed to grant a device permission."); 91 } 92 } 93 94 final MtpDevice device = new MtpDevice(rawDevice); 95 96 final UsbDeviceConnection connection = ensureNotNull( 97 mManager.openDevice(rawDevice), 98 "Failed to open a USB connection."); 99 100 if (!device.open(connection)) { 101 // We cannot open connection when another application use the device. 102 throw new BusyDeviceException(); 103 } 104 105 // Handle devices that fail to obtain storages just after opening a MTP session. 106 final int[] storageIds = ensureNotNull( 107 device.getStorageIds(), 108 "Not found MTP storages in the device."); 109 110 mDevices.put(deviceId, device); 111 return createDeviceRecord(rawDevice); 112 } 113 closeDevice(int deviceId)114 synchronized void closeDevice(int deviceId) throws IOException { 115 getDevice(deviceId).close(); 116 mDevices.remove(deviceId); 117 } 118 getDevices()119 synchronized MtpDeviceRecord[] getDevices() { 120 final ArrayList<MtpDeviceRecord> devices = new ArrayList<>(); 121 for (UsbDevice device : mManager.getDeviceList().values()) { 122 if (!isMtpDevice(device)) { 123 continue; 124 } 125 devices.add(createDeviceRecord(device)); 126 } 127 return devices.toArray(new MtpDeviceRecord[devices.size()]); 128 } 129 getObjectInfo(int deviceId, int objectHandle)130 MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { 131 final MtpDevice device = getDevice(deviceId); 132 synchronized (device) { 133 return ensureNotNull( 134 device.getObjectInfo(objectHandle), 135 "Failed to get object info: " + objectHandle); 136 } 137 } 138 getObjectHandles(int deviceId, int storageId, int parentObjectHandle)139 int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle) 140 throws IOException { 141 final MtpDevice device = getDevice(deviceId); 142 synchronized (device) { 143 return ensureNotNull( 144 device.getObjectHandles(storageId, 0 /* all format */, parentObjectHandle), 145 "Failed to fetch object handles."); 146 } 147 } 148 getObject(int deviceId, int objectHandle, int expectedSize)149 byte[] getObject(int deviceId, int objectHandle, int expectedSize) 150 throws IOException { 151 final MtpDevice device = getDevice(deviceId); 152 synchronized (device) { 153 return ensureNotNull( 154 device.getObject(objectHandle, expectedSize), 155 "Failed to fetch object bytes"); 156 } 157 } 158 getPartialObject(int deviceId, int objectHandle, long offset, long size, byte[] buffer)159 long getPartialObject(int deviceId, int objectHandle, long offset, long size, byte[] buffer) 160 throws IOException { 161 final MtpDevice device = getDevice(deviceId); 162 synchronized (device) { 163 return device.getPartialObject(objectHandle, offset, size, buffer); 164 } 165 } 166 getPartialObject64(int deviceId, int objectHandle, long offset, long size, byte[] buffer)167 long getPartialObject64(int deviceId, int objectHandle, long offset, long size, byte[] buffer) 168 throws IOException { 169 final MtpDevice device = getDevice(deviceId); 170 synchronized (device) { 171 return device.getPartialObject64(objectHandle, offset, size, buffer); 172 } 173 } 174 getThumbnail(int deviceId, int objectHandle)175 byte[] getThumbnail(int deviceId, int objectHandle) throws IOException { 176 final MtpDevice device = getDevice(deviceId); 177 synchronized (device) { 178 return ensureNotNull( 179 device.getThumbnail(objectHandle), 180 "Failed to obtain thumbnail bytes"); 181 } 182 } 183 deleteDocument(int deviceId, int objectHandle)184 void deleteDocument(int deviceId, int objectHandle) throws IOException { 185 final MtpDevice device = getDevice(deviceId); 186 synchronized (device) { 187 if (!device.deleteObject(objectHandle)) { 188 throw new IOException("Failed to delete document"); 189 } 190 } 191 } 192 createDocument(int deviceId, MtpObjectInfo objectInfo, ParcelFileDescriptor source)193 int createDocument(int deviceId, MtpObjectInfo objectInfo, 194 ParcelFileDescriptor source) throws IOException { 195 final MtpDevice device = getDevice(deviceId); 196 synchronized (device) { 197 final MtpObjectInfo sendObjectInfoResult = device.sendObjectInfo(objectInfo); 198 if (sendObjectInfoResult == null) { 199 throw new SendObjectInfoFailure(); 200 } 201 if (objectInfo.getFormat() != MtpConstants.FORMAT_ASSOCIATION) { 202 if (!device.sendObject(sendObjectInfoResult.getObjectHandle(), 203 sendObjectInfoResult.getCompressedSize(), source)) { 204 throw new IOException("Failed to send contents of a document"); 205 } 206 } 207 return sendObjectInfoResult.getObjectHandle(); 208 } 209 } 210 getParent(int deviceId, int objectHandle)211 int getParent(int deviceId, int objectHandle) throws IOException { 212 final MtpDevice device = getDevice(deviceId); 213 synchronized (device) { 214 final int result = (int) device.getParent(objectHandle); 215 if (result == 0xffffffff) { 216 throw new FileNotFoundException("Not found parent object"); 217 } 218 return result; 219 } 220 } 221 importFile(int deviceId, int objectHandle, ParcelFileDescriptor target)222 void importFile(int deviceId, int objectHandle, ParcelFileDescriptor target) 223 throws IOException { 224 final MtpDevice device = getDevice(deviceId); 225 synchronized (device) { 226 if (!device.importFile(objectHandle, target)) { 227 throw new IOException("Failed to import file to FD"); 228 } 229 } 230 } 231 232 @VisibleForTesting readEvent(int deviceId, CancellationSignal signal)233 MtpEvent readEvent(int deviceId, CancellationSignal signal) throws IOException { 234 final MtpDevice device = getDevice(deviceId); 235 return device.readEvent(signal); 236 } 237 getObjectSizeLong(int deviceId, int objectHandle, int format)238 long getObjectSizeLong(int deviceId, int objectHandle, int format) throws IOException { 239 final MtpDevice device = getDevice(deviceId); 240 return device.getObjectSizeLong(objectHandle, format); 241 } 242 getDevice(int deviceId)243 private synchronized MtpDevice getDevice(int deviceId) throws IOException { 244 return ensureNotNull( 245 mDevices.get(deviceId), 246 "USB device " + deviceId + " is not opened."); 247 } 248 getRoots(int deviceId)249 private MtpRoot[] getRoots(int deviceId) throws IOException { 250 final MtpDevice device = getDevice(deviceId); 251 synchronized (device) { 252 final int[] storageIds = 253 ensureNotNull(device.getStorageIds(), "Failed to obtain storage IDs."); 254 final ArrayList<MtpRoot> roots = new ArrayList<>(); 255 for (int i = 0; i < storageIds.length; i++) { 256 final MtpStorageInfo info = device.getStorageInfo(storageIds[i]); 257 if (info == null) { 258 continue; 259 } 260 roots.add(new MtpRoot(device.getDeviceId(), info)); 261 } 262 return roots.toArray(new MtpRoot[roots.size()]); 263 } 264 } 265 createDeviceRecord(UsbDevice device)266 private MtpDeviceRecord createDeviceRecord(UsbDevice device) { 267 final MtpDevice mtpDevice = mDevices.get(device.getDeviceId()); 268 final boolean opened = mtpDevice != null; 269 final String name = device.getProductName(); 270 MtpRoot[] roots; 271 int[] operationsSupported = null; 272 int[] eventsSupported = null; 273 if (opened) { 274 try { 275 roots = getRoots(device.getDeviceId()); 276 } catch (IOException exp) { 277 Log.e(MtpDocumentsProvider.TAG, "Failed to open device", exp); 278 // If we failed to fetch roots for the device, we still returns device model 279 // with an empty set of roots so that the device is shown DocumentsUI as long as 280 // the device is physically connected. 281 roots = new MtpRoot[0]; 282 } 283 final MtpDeviceInfo info = mtpDevice.getDeviceInfo(); 284 if (info != null) { 285 operationsSupported = info.getOperationsSupported(); 286 eventsSupported = info.getEventsSupported(); 287 } 288 } else { 289 roots = new MtpRoot[0]; 290 } 291 return new MtpDeviceRecord( 292 device.getDeviceId(), name, device.getSerialNumber(), opened, roots, 293 operationsSupported, eventsSupported); 294 } 295 isMtpDevice(UsbDevice device)296 static boolean isMtpDevice(UsbDevice device) { 297 for (int i = 0; i < device.getInterfaceCount(); i++) { 298 final UsbInterface usbInterface = device.getInterface(i); 299 if ((usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE && 300 usbInterface.getInterfaceSubclass() == SUBCLASS_STILL_IMAGE_CAPTURE && 301 usbInterface.getInterfaceProtocol() == PROTOCOL_PICTURE_TRANSFER)) { 302 return true; 303 } 304 if (usbInterface.getInterfaceClass() == UsbConstants.USB_SUBCLASS_VENDOR_SPEC && 305 usbInterface.getInterfaceSubclass() == SUBCLASS_MTP && 306 usbInterface.getInterfaceProtocol() == PROTOCOL_MTP && 307 "MTP".equals(usbInterface.getName())) { 308 return true; 309 } 310 } 311 return false; 312 } 313 ensureNotNull(@ullable T t, String errorMessage)314 private static <T> T ensureNotNull(@Nullable T t, String errorMessage) throws IOException { 315 if (t != null) { 316 return t; 317 } else { 318 throw new IOException(errorMessage); 319 } 320 } 321 } 322