[go: nahoru, domu]

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
17package com.android.mtp;
18
19import android.annotation.Nullable;
20import android.content.Context;
21import android.hardware.usb.UsbConstants;
22import android.hardware.usb.UsbDevice;
23import android.hardware.usb.UsbDeviceConnection;
24import android.hardware.usb.UsbInterface;
25import android.hardware.usb.UsbManager;
26import android.mtp.MtpConstants;
27import android.mtp.MtpDevice;
28import android.mtp.MtpDeviceInfo;
29import android.mtp.MtpEvent;
30import android.mtp.MtpObjectInfo;
31import android.mtp.MtpStorageInfo;
32import android.os.CancellationSignal;
33import android.os.ParcelFileDescriptor;
34import android.util.Log;
35import android.util.SparseArray;
36
37import com.android.internal.annotations.VisibleForTesting;
38
39import java.io.FileNotFoundException;
40import java.io.IOException;
41import java.util.ArrayList;
42
43/**
44 * The model wrapping android.mtp API.
45 */
46class 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
72    MtpManager(Context context) {
73        mManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
74    }
75
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
114    synchronized void closeDevice(int deviceId) throws IOException {
115        getDevice(deviceId).close();
116        mDevices.remove(deviceId);
117    }
118
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
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
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
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
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
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
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
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
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
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
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
233    MtpEvent readEvent(int deviceId, CancellationSignal signal) throws IOException {
234        final MtpDevice device = getDevice(deviceId);
235        return device.readEvent(signal);
236    }
237
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
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
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
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 = mtpDevice.getDeviceInfo().getOperationsSupported();
286                eventsSupported = mtpDevice.getDeviceInfo().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
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
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