• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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