blob: 1966e61fbc536b82018febf2b96d25a14841718c [file] [log] [blame]
Daichi Hironod5152422015-07-15 13:31:51 +09001/*
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
17package com.android.mtp;
18
19import android.content.Context;
Daichi Hirono20754c52015-12-15 18:52:26 +090020import android.hardware.usb.UsbConstants;
Daichi Hirono21c20ad2015-07-23 16:56:19 +090021import android.hardware.usb.UsbDevice;
22import android.hardware.usb.UsbDeviceConnection;
Daichi Hirono20754c52015-12-15 18:52:26 +090023import android.hardware.usb.UsbInterface;
Daichi Hirono21c20ad2015-07-23 16:56:19 +090024import android.hardware.usb.UsbManager;
Tomasz Mikolajewskidf544172015-08-31 10:59:43 +090025import android.mtp.MtpConstants;
Daichi Hirono21c20ad2015-07-23 16:56:19 +090026import android.mtp.MtpDevice;
Daichi Hirono1d4779c2016-01-06 16:43:32 +090027import android.mtp.MtpDeviceInfo;
Daichi Hirono0b494662015-09-10 20:38:15 +090028import android.mtp.MtpEvent;
Tomasz Mikolajewski87763e62015-08-10 10:10:22 +090029import android.mtp.MtpObjectInfo;
Daichi Hirono0b494662015-09-10 20:38:15 +090030import android.os.CancellationSignal;
Tomasz Mikolajewski52652ac2015-08-05 17:33:33 +090031import android.os.ParcelFileDescriptor;
Daichi Hirono20754c52015-12-15 18:52:26 +090032import android.util.Log;
Daichi Hirono21c20ad2015-07-23 16:56:19 +090033import android.util.SparseArray;
Daichi Hironod5152422015-07-15 13:31:51 +090034
Daichi Hirono0b494662015-09-10 20:38:15 +090035import com.android.internal.annotations.VisibleForTesting;
Daichi Hironoc18f8072016-02-10 14:59:52 -080036import com.android.mtp.exceptions.BusyDeviceException;
Daichi Hirono0b494662015-09-10 20:38:15 +090037
Daichi Hirono5fecc6c2015-08-04 17:45:51 +090038import java.io.FileNotFoundException;
Daichi Hironod5152422015-07-15 13:31:51 +090039import java.io.IOException;
Daichi Hirono20754c52015-12-15 18:52:26 +090040import java.util.ArrayList;
Daichi Hironod5152422015-07-15 13:31:51 +090041
42/**
43 * The model wrapping android.mtp API.
44 */
45class MtpManager {
Daichi Hirono124d0602015-08-11 17:08:35 +090046 final static int OBJECT_HANDLE_ROOT_CHILDREN = -1;
47
Daichi Hirono20754c52015-12-15 18:52:26 +090048 /**
49 * Subclass for PTP.
50 */
51 private static final int SUBCLASS_STILL_IMAGE_CAPTURE = 1;
52
53 /**
54 * Subclass for Android style MTP.
55 */
56 private static final int SUBCLASS_MTP = 0xff;
57
58 /**
59 * Protocol for Picture Transfer Protocol (PIMA 15470).
60 */
61 private static final int PROTOCOL_PICTURE_TRANSFER = 1;
62
63 /**
64 * Protocol for Android style MTP.
65 */
66 private static final int PROTOCOL_MTP = 0;
67
Daichi Hirono21c20ad2015-07-23 16:56:19 +090068 private final UsbManager mManager;
Daichi Hirono2ff024f2015-08-11 20:02:58 +090069 private final SparseArray<MtpDevice> mDevices = new SparseArray<>();
Daichi Hirono21c20ad2015-07-23 16:56:19 +090070
Daichi Hironod5152422015-07-15 13:31:51 +090071 MtpManager(Context context) {
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +090072 mManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
Daichi Hironod5152422015-07-15 13:31:51 +090073 }
74
Daichi Hirono0f325372016-02-21 15:50:30 +090075 synchronized MtpDeviceRecord openDevice(int deviceId) throws IOException {
Daichi Hirono21c20ad2015-07-23 16:56:19 +090076 UsbDevice rawDevice = null;
77 for (final UsbDevice candidate : mManager.getDeviceList().values()) {
78 if (candidate.getDeviceId() == deviceId) {
79 rawDevice = candidate;
80 break;
81 }
82 }
83
84 if (rawDevice == null) {
85 throw new IOException("Not found USB device: " + deviceId);
86 }
87
88 if (!mManager.hasPermission(rawDevice)) {
Daichi Hirono47518802015-12-08 09:51:19 +090089 mManager.grantPermission(rawDevice);
90 if (!mManager.hasPermission(rawDevice)) {
91 throw new IOException("Failed to grant a device permission.");
92 }
Daichi Hirono21c20ad2015-07-23 16:56:19 +090093 }
94
95 final MtpDevice device = new MtpDevice(rawDevice);
96
97 final UsbDeviceConnection connection = mManager.openDevice(rawDevice);
98 if (connection == null) {
99 throw new IOException("Failed to open a USB connection.");
100 }
101
102 if (!device.open(connection)) {
Daichi Hironoc18f8072016-02-10 14:59:52 -0800103 // We cannot open connection when another application use the device.
104 throw new BusyDeviceException();
Daichi Hirono21c20ad2015-07-23 16:56:19 +0900105 }
106
107 // Handle devices that fail to obtain storages just after opening a MTP session.
108 final int[] storageIds = device.getStorageIds();
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900109 if (storageIds == null) {
Daichi Hirono21c20ad2015-07-23 16:56:19 +0900110 throw new IOException("Not found MTP storages in the device.");
111 }
112
113 mDevices.put(deviceId, device);
Daichi Hirono0f325372016-02-21 15:50:30 +0900114
115 return createDeviceRecord(rawDevice);
Daichi Hironod5152422015-07-15 13:31:51 +0900116 }
117
Daichi Hirono21c20ad2015-07-23 16:56:19 +0900118 synchronized void closeDevice(int deviceId) throws IOException {
Daichi Hirono50d17aa2015-07-28 15:49:01 +0900119 getDevice(deviceId).close();
Daichi Hirono21c20ad2015-07-23 16:56:19 +0900120 mDevices.remove(deviceId);
Daichi Hironod5152422015-07-15 13:31:51 +0900121 }
122
Daichi Hirono20754c52015-12-15 18:52:26 +0900123 synchronized MtpDeviceRecord[] getDevices() {
124 final ArrayList<MtpDeviceRecord> devices = new ArrayList<>();
125 for (UsbDevice device : mManager.getDeviceList().values()) {
126 if (!isMtpDevice(device)) {
127 continue;
128 }
Daichi Hirono0f325372016-02-21 15:50:30 +0900129 devices.add(createDeviceRecord(device));
Daichi Hirono20754c52015-12-15 18:52:26 +0900130 }
131 return devices.toArray(new MtpDeviceRecord[devices.size()]);
132 }
133
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900134 MtpObjectInfo getObjectInfo(int deviceId, int objectHandle)
Tomasz Mikolajewskibb430fa2015-08-25 18:34:30 +0900135 throws IOException {
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900136 final MtpDevice device = getDevice(deviceId);
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900137 synchronized (device) {
138 return device.getObjectInfo(objectHandle);
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900139 }
140 }
141
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900142 int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle)
143 throws IOException {
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900144 final MtpDevice device = getDevice(deviceId);
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900145 synchronized (device) {
146 return device.getObjectHandles(storageId, 0 /* all format */, parentObjectHandle);
Tomasz Mikolajewski87763e62015-08-10 10:10:22 +0900147 }
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900148 }
149
150 byte[] getObject(int deviceId, int objectHandle, int expectedSize)
151 throws IOException {
152 final MtpDevice device = getDevice(deviceId);
153 synchronized (device) {
154 return device.getObject(objectHandle, expectedSize);
155 }
156 }
157
Daichi Hironod426ed22016-01-11 17:14:41 +0900158 long getPartialObject(int deviceId, int objectHandle, long offset, long size, byte[] buffer)
159 throws IOException {
160 final MtpDevice device = getDevice(deviceId);
161 synchronized (device) {
162 return device.getPartialObject(objectHandle, offset, size, buffer);
163 }
164 }
165
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900166 byte[] getThumbnail(int deviceId, int objectHandle) throws IOException {
167 final MtpDevice device = getDevice(deviceId);
168 synchronized (device) {
169 return device.getThumbnail(objectHandle);
170 }
171 }
172
173 void deleteDocument(int deviceId, int objectHandle) throws IOException {
174 final MtpDevice device = getDevice(deviceId);
175 synchronized (device) {
176 if (!device.deleteObject(objectHandle)) {
177 throw new IOException("Failed to delete document");
Tomasz Mikolajewskidf544172015-08-31 10:59:43 +0900178 }
179 }
Tomasz Mikolajewski87763e62015-08-10 10:10:22 +0900180 }
181
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900182 int createDocument(int deviceId, MtpObjectInfo objectInfo,
183 ParcelFileDescriptor source) throws IOException {
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900184 final MtpDevice device = getDevice(deviceId);
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900185 synchronized (device) {
186 final MtpObjectInfo sendObjectInfoResult = device.sendObjectInfo(objectInfo);
187 if (sendObjectInfoResult == null) {
188 throw new IOException("Failed to create a document");
189 }
190 if (objectInfo.getFormat() != MtpConstants.FORMAT_ASSOCIATION) {
191 if (!device.sendObject(sendObjectInfoResult.getObjectHandle(),
192 sendObjectInfoResult.getCompressedSize(), source)) {
193 throw new IOException("Failed to send contents of a document");
194 }
195 }
196 return sendObjectInfoResult.getObjectHandle();
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900197 }
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900198 }
199
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900200 int getParent(int deviceId, int objectHandle) throws IOException {
201 final MtpDevice device = getDevice(deviceId);
202 synchronized (device) {
203 final int result = (int) device.getParent(objectHandle);
204 if (result < 0) {
205 throw new FileNotFoundException("Not found parent object");
206 }
207 return result;
208 }
209 }
210
211 void importFile(int deviceId, int objectHandle, ParcelFileDescriptor target)
Tomasz Mikolajewski52652ac2015-08-05 17:33:33 +0900212 throws IOException {
Daichi Hirono2ff024f2015-08-11 20:02:58 +0900213 final MtpDevice device = getDevice(deviceId);
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900214 synchronized (device) {
215 device.importFile(objectHandle, target);
216 }
Tomasz Mikolajewski52652ac2015-08-05 17:33:33 +0900217 }
218
Daichi Hirono0b494662015-09-10 20:38:15 +0900219 @VisibleForTesting
220 MtpEvent readEvent(int deviceId, CancellationSignal signal) throws IOException {
221 final MtpDevice device = getDevice(deviceId);
222 return device.readEvent(signal);
223 }
224
225 private synchronized MtpDevice getDevice(int deviceId) throws IOException {
Daichi Hirono50d17aa2015-07-28 15:49:01 +0900226 final MtpDevice device = mDevices.get(deviceId);
227 if (device == null) {
228 throw new IOException("USB device " + deviceId + " is not opened.");
229 }
230 return device;
231 }
Daichi Hirono20754c52015-12-15 18:52:26 +0900232
233 private MtpRoot[] getRoots(int deviceId) throws IOException {
234 final MtpDevice device = getDevice(deviceId);
235 synchronized (device) {
236 final int[] storageIds = device.getStorageIds();
237 if (storageIds == null) {
238 throw new IOException("Failed to obtain storage IDs.");
239 }
240 final MtpRoot[] results = new MtpRoot[storageIds.length];
241 for (int i = 0; i < storageIds.length; i++) {
242 results[i] = new MtpRoot(
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900243 device.getDeviceId(), device.getStorageInfo(storageIds[i]));
Daichi Hirono20754c52015-12-15 18:52:26 +0900244 }
245 return results;
246 }
247 }
248
Daichi Hirono0f325372016-02-21 15:50:30 +0900249 private MtpDeviceRecord createDeviceRecord(UsbDevice device) {
250 final MtpDevice mtpDevice = mDevices.get(device.getDeviceId());
251 final boolean opened = mtpDevice != null;
252 final String name = device.getProductName();
253 MtpRoot[] roots;
254 int[] operationsSupported = null;
255 int[] eventsSupported = null;
256 if (opened) {
257 try {
258 roots = getRoots(device.getDeviceId());
259 } catch (IOException exp) {
260 Log.e(MtpDocumentsProvider.TAG, "Failed to open device", exp);
261 // If we failed to fetch roots for the device, we still returns device model
262 // with an empty set of roots so that the device is shown DocumentsUI as long as
263 // the device is physically connected.
264 roots = new MtpRoot[0];
265 }
266 final MtpDeviceInfo info = mtpDevice.getDeviceInfo();
267 if (info != null) {
268 operationsSupported = mtpDevice.getDeviceInfo().getOperationsSupported();
269 eventsSupported = mtpDevice.getDeviceInfo().getEventsSupported();
270 }
271 } else {
272 roots = new MtpRoot[0];
273 }
274 return new MtpDeviceRecord(
275 device.getDeviceId(), name, device.getSerialNumber(), opened, roots,
276 operationsSupported, eventsSupported);
277 }
278
Daichi Hirono20754c52015-12-15 18:52:26 +0900279 static boolean isMtpDevice(UsbDevice device) {
280 for (int i = 0; i < device.getInterfaceCount(); i++) {
281 final UsbInterface usbInterface = device.getInterface(i);
282 if ((usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE &&
283 usbInterface.getInterfaceSubclass() == SUBCLASS_STILL_IMAGE_CAPTURE &&
284 usbInterface.getInterfaceProtocol() == PROTOCOL_PICTURE_TRANSFER)) {
285 return true;
286 }
287 if (usbInterface.getInterfaceClass() == UsbConstants.USB_SUBCLASS_VENDOR_SPEC &&
288 usbInterface.getInterfaceSubclass() == SUBCLASS_MTP &&
289 usbInterface.getInterfaceProtocol() == PROTOCOL_MTP &&
290 usbInterface.getName().equals("MTP")) {
291 return true;
292 }
293 }
294 return false;
295 }
Daichi Hironod5152422015-07-15 13:31:51 +0900296}