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