blob: 37dc7616b5104de1921ca72c9f323277a4353d07 [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
68
Daichi Hirono21c20ad2015-07-23 16:56:19 +090069 private final UsbManager mManager;
70 // TODO: Save and restore the set of opened device.
Daichi Hirono2ff024f2015-08-11 20:02:58 +090071 private final SparseArray<MtpDevice> mDevices = new SparseArray<>();
Daichi Hirono21c20ad2015-07-23 16:56:19 +090072
Daichi Hironod5152422015-07-15 13:31:51 +090073 MtpManager(Context context) {
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +090074 mManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
Daichi Hironod5152422015-07-15 13:31:51 +090075 }
76
Daichi Hirono21c20ad2015-07-23 16:56:19 +090077 synchronized void 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 if (rawDevice == null) {
87 throw new IOException("Not found USB device: " + deviceId);
88 }
89
90 if (!mManager.hasPermission(rawDevice)) {
Daichi Hirono47518802015-12-08 09:51:19 +090091 mManager.grantPermission(rawDevice);
92 if (!mManager.hasPermission(rawDevice)) {
93 throw new IOException("Failed to grant a device permission.");
94 }
Daichi Hirono21c20ad2015-07-23 16:56:19 +090095 }
96
97 final MtpDevice device = new MtpDevice(rawDevice);
98
99 final UsbDeviceConnection connection = mManager.openDevice(rawDevice);
100 if (connection == null) {
101 throw new IOException("Failed to open a USB connection.");
102 }
103
104 if (!device.open(connection)) {
Daichi Hironoc18f8072016-02-10 14:59:52 -0800105 // We cannot open connection when another application use the device.
106 throw new BusyDeviceException();
Daichi Hirono21c20ad2015-07-23 16:56:19 +0900107 }
108
109 // Handle devices that fail to obtain storages just after opening a MTP session.
110 final int[] storageIds = device.getStorageIds();
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900111 if (storageIds == null) {
Daichi Hirono21c20ad2015-07-23 16:56:19 +0900112 throw new IOException("Not found MTP storages in the device.");
113 }
114
115 mDevices.put(deviceId, device);
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 Hirono1d4779c2016-01-06 16:43:32 +0900129 final MtpDevice mtpDevice = mDevices.get(device.getDeviceId());
130 final boolean opened = mtpDevice != null;
Daichi Hirono20754c52015-12-15 18:52:26 +0900131 final String name = device.getProductName();
132 MtpRoot[] roots;
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900133 int[] operationsSupported = null;
Daichi Hirono148954a2016-01-21 19:55:45 +0900134 int[] eventsSupported = null;
Daichi Hirono20754c52015-12-15 18:52:26 +0900135 if (opened) {
136 try {
137 roots = getRoots(device.getDeviceId());
138 } catch (IOException exp) {
Daichi Hironoc18f8072016-02-10 14:59:52 -0800139 Log.e(MtpDocumentsProvider.TAG, "Failed to open device", exp);
Daichi Hirono20754c52015-12-15 18:52:26 +0900140 // If we failed to fetch roots for the device, we still returns device model
141 // with an empty set of roots so that the device is shown DocumentsUI as long as
142 // the device is physically connected.
143 roots = new MtpRoot[0];
144 }
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900145 final MtpDeviceInfo info = mtpDevice.getDeviceInfo();
146 if (info != null) {
147 operationsSupported = mtpDevice.getDeviceInfo().getOperationsSupported();
Daichi Hirono148954a2016-01-21 19:55:45 +0900148 eventsSupported = mtpDevice.getDeviceInfo().getEventsSupported();
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900149 }
Daichi Hirono20754c52015-12-15 18:52:26 +0900150 } else {
151 roots = new MtpRoot[0];
152 }
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900153 devices.add(new MtpDeviceRecord(
Daichi Hironoebd24052016-02-06 21:05:57 +0900154 device.getDeviceId(), name, device.getSerialNumber(), opened, roots,
155 operationsSupported, eventsSupported));
Daichi Hirono20754c52015-12-15 18:52:26 +0900156 }
157 return devices.toArray(new MtpDeviceRecord[devices.size()]);
158 }
159
Daichi Hirono21c20ad2015-07-23 16:56:19 +0900160 synchronized int[] getOpenedDeviceIds() {
161 final int[] result = new int[mDevices.size()];
162 for (int i = 0; i < result.length; i++) {
163 result[i] = mDevices.keyAt(i);
164 }
165 return result;
Daichi Hironod5152422015-07-15 13:31:51 +0900166 }
Daichi Hirono50d17aa2015-07-28 15:49:01 +0900167
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900168 MtpObjectInfo getObjectInfo(int deviceId, int objectHandle)
Tomasz Mikolajewskibb430fa2015-08-25 18:34:30 +0900169 throws IOException {
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900170 final MtpDevice device = getDevice(deviceId);
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900171 synchronized (device) {
172 return device.getObjectInfo(objectHandle);
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900173 }
174 }
175
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900176 int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle)
177 throws IOException {
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900178 final MtpDevice device = getDevice(deviceId);
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900179 synchronized (device) {
180 return device.getObjectHandles(storageId, 0 /* all format */, parentObjectHandle);
Tomasz Mikolajewski87763e62015-08-10 10:10:22 +0900181 }
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900182 }
183
184 byte[] getObject(int deviceId, int objectHandle, int expectedSize)
185 throws IOException {
186 final MtpDevice device = getDevice(deviceId);
187 synchronized (device) {
188 return device.getObject(objectHandle, expectedSize);
189 }
190 }
191
Daichi Hironod426ed22016-01-11 17:14:41 +0900192 @VisibleForTesting
193 long getPartialObject(int deviceId, int objectHandle, long offset, long size, byte[] buffer)
194 throws IOException {
195 final MtpDevice device = getDevice(deviceId);
196 synchronized (device) {
197 return device.getPartialObject(objectHandle, offset, size, buffer);
198 }
199 }
200
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900201 byte[] getThumbnail(int deviceId, int objectHandle) throws IOException {
202 final MtpDevice device = getDevice(deviceId);
203 synchronized (device) {
204 return device.getThumbnail(objectHandle);
205 }
206 }
207
208 void deleteDocument(int deviceId, int objectHandle) throws IOException {
209 final MtpDevice device = getDevice(deviceId);
210 synchronized (device) {
211 if (!device.deleteObject(objectHandle)) {
212 throw new IOException("Failed to delete document");
Tomasz Mikolajewskidf544172015-08-31 10:59:43 +0900213 }
214 }
Tomasz Mikolajewski87763e62015-08-10 10:10:22 +0900215 }
216
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900217 int createDocument(int deviceId, MtpObjectInfo objectInfo,
218 ParcelFileDescriptor source) throws IOException {
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900219 final MtpDevice device = getDevice(deviceId);
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900220 synchronized (device) {
221 final MtpObjectInfo sendObjectInfoResult = device.sendObjectInfo(objectInfo);
222 if (sendObjectInfoResult == null) {
223 throw new IOException("Failed to create a document");
224 }
225 if (objectInfo.getFormat() != MtpConstants.FORMAT_ASSOCIATION) {
226 if (!device.sendObject(sendObjectInfoResult.getObjectHandle(),
227 sendObjectInfoResult.getCompressedSize(), source)) {
228 throw new IOException("Failed to send contents of a document");
229 }
230 }
231 return sendObjectInfoResult.getObjectHandle();
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900232 }
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900233 }
234
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900235 int getParent(int deviceId, int objectHandle) throws IOException {
236 final MtpDevice device = getDevice(deviceId);
237 synchronized (device) {
238 final int result = (int) device.getParent(objectHandle);
239 if (result < 0) {
240 throw new FileNotFoundException("Not found parent object");
241 }
242 return result;
243 }
244 }
245
246 void importFile(int deviceId, int objectHandle, ParcelFileDescriptor target)
Tomasz Mikolajewski52652ac2015-08-05 17:33:33 +0900247 throws IOException {
Daichi Hirono2ff024f2015-08-11 20:02:58 +0900248 final MtpDevice device = getDevice(deviceId);
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +0900249 synchronized (device) {
250 device.importFile(objectHandle, target);
251 }
Tomasz Mikolajewski52652ac2015-08-05 17:33:33 +0900252 }
253
Daichi Hirono0b494662015-09-10 20:38:15 +0900254 @VisibleForTesting
255 MtpEvent readEvent(int deviceId, CancellationSignal signal) throws IOException {
256 final MtpDevice device = getDevice(deviceId);
257 return device.readEvent(signal);
258 }
259
260 private synchronized MtpDevice getDevice(int deviceId) throws IOException {
Daichi Hirono50d17aa2015-07-28 15:49:01 +0900261 final MtpDevice device = mDevices.get(deviceId);
262 if (device == null) {
263 throw new IOException("USB device " + deviceId + " is not opened.");
264 }
265 return device;
266 }
Daichi Hirono20754c52015-12-15 18:52:26 +0900267
268 private MtpRoot[] getRoots(int deviceId) throws IOException {
269 final MtpDevice device = getDevice(deviceId);
270 synchronized (device) {
271 final int[] storageIds = device.getStorageIds();
272 if (storageIds == null) {
273 throw new IOException("Failed to obtain storage IDs.");
274 }
275 final MtpRoot[] results = new MtpRoot[storageIds.length];
276 for (int i = 0; i < storageIds.length; i++) {
277 results[i] = new MtpRoot(
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900278 device.getDeviceId(), device.getStorageInfo(storageIds[i]));
Daichi Hirono20754c52015-12-15 18:52:26 +0900279 }
280 return results;
281 }
282 }
283
284 static boolean isMtpDevice(UsbDevice device) {
285 for (int i = 0; i < device.getInterfaceCount(); i++) {
286 final UsbInterface usbInterface = device.getInterface(i);
287 if ((usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE &&
288 usbInterface.getInterfaceSubclass() == SUBCLASS_STILL_IMAGE_CAPTURE &&
289 usbInterface.getInterfaceProtocol() == PROTOCOL_PICTURE_TRANSFER)) {
290 return true;
291 }
292 if (usbInterface.getInterfaceClass() == UsbConstants.USB_SUBCLASS_VENDOR_SPEC &&
293 usbInterface.getInterfaceSubclass() == SUBCLASS_MTP &&
294 usbInterface.getInterfaceProtocol() == PROTOCOL_MTP &&
295 usbInterface.getName().equals("MTP")) {
296 return true;
297 }
298 }
299 return false;
300 }
Daichi Hironod5152422015-07-15 13:31:51 +0900301}