1/*
2 * Copyright (C) 2010 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 android.mtp;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.Context;
22import android.hardware.usb.UsbDevice;
23import android.hardware.usb.UsbDeviceConnection;
24import android.os.CancellationSignal;
25import android.os.ParcelFileDescriptor;
26
27import android.os.UserManager;
28import com.android.internal.annotations.GuardedBy;
29import com.android.internal.util.Preconditions;
30import dalvik.system.CloseGuard;
31
32import java.io.IOException;
33
34/**
35 * This class represents an MTP or PTP device connected on the USB host bus. An application can
36 * instantiate an object of this type, by referencing an attached {@link
37 * android.hardware.usb.UsbDevice} and then use methods in this class to get information about the
38 * device and objects stored on it, as well as open the connection and transfer data.
39 */
40public final class MtpDevice {
41
42    private static final String TAG = "MtpDevice";
43
44    private final UsbDevice mDevice;
45
46    static {
47        System.loadLibrary("media_jni");
48    }
49
50    /** Make sure that MTP device is closed properly */
51    @GuardedBy("mLock")
52    private CloseGuard mCloseGuard = CloseGuard.get();
53
54    /** Current connection to the {@link #mDevice}, or null if device is not connected */
55    @GuardedBy("mLock")
56    private UsbDeviceConnection mConnection;
57
58    private final Object mLock = new Object();
59
60    /**
61     * MtpClient constructor
62     *
63     * @param device the {@link android.hardware.usb.UsbDevice} for the MTP or PTP device
64     */
65    public MtpDevice(@NonNull UsbDevice device) {
66        Preconditions.checkNotNull(device);
67        mDevice = device;
68    }
69
70    /**
71     * Opens the MTP device.  Once the device is open it takes ownership of the
72     * {@link android.hardware.usb.UsbDeviceConnection}.
73     * The connection will be closed when you call {@link #close()}
74     * The connection will also be closed if this method fails.
75     *
76     * @param connection an open {@link android.hardware.usb.UsbDeviceConnection} for the device
77     * @return true if the device was successfully opened.
78     */
79    public boolean open(@NonNull UsbDeviceConnection connection) {
80        boolean result = false;
81
82        Context context = connection.getContext();
83
84        synchronized (mLock) {
85            if (context != null) {
86                UserManager userManager = (UserManager) context
87                        .getSystemService(Context.USER_SERVICE);
88
89                if (!userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
90                    result = native_open(mDevice.getDeviceName(), connection.getFileDescriptor());
91                }
92            }
93
94            if (!result) {
95                connection.close();
96            } else {
97                mConnection = connection;
98                mCloseGuard.open("close");
99            }
100        }
101
102        return result;
103    }
104
105    /**
106     * Closes all resources related to the MtpDevice object.
107     * After this is called, the object can not be used until {@link #open} is called again
108     * with a new {@link android.hardware.usb.UsbDeviceConnection}.
109     */
110    public void close() {
111        synchronized (mLock) {
112            if (mConnection != null) {
113                mCloseGuard.close();
114
115                native_close();
116
117                mConnection.close();
118                mConnection = null;
119            }
120        }
121    }
122
123    @Override
124    protected void finalize() throws Throwable {
125        try {
126            mCloseGuard.warnIfOpen();
127            close();
128        } finally {
129            super.finalize();
130        }
131    }
132
133    /**
134     * Returns the name of the USB device
135     * This returns the same value as {@link android.hardware.usb.UsbDevice#getDeviceName}
136     * for the device's {@link android.hardware.usb.UsbDevice}
137     *
138     * @return the device name
139     */
140    public @NonNull String getDeviceName() {
141        return mDevice.getDeviceName();
142    }
143
144    /**
145     * Returns the USB ID of the USB device.
146     * This returns the same value as {@link android.hardware.usb.UsbDevice#getDeviceId}
147     * for the device's {@link android.hardware.usb.UsbDevice}
148     *
149     * @return the device ID
150     */
151    public int getDeviceId() {
152        return mDevice.getDeviceId();
153    }
154
155    @Override
156    public @NonNull String toString() {
157        return mDevice.getDeviceName();
158    }
159
160    /**
161     * Returns the {@link MtpDeviceInfo} for this device
162     *
163     * @return the device info, or null if fetching device info fails
164     */
165    public @Nullable MtpDeviceInfo getDeviceInfo() {
166        return native_get_device_info();
167    }
168
169    /**
170     * Returns the list of IDs for all storage units on this device
171     * Information about each storage unit can be accessed via {@link #getStorageInfo}.
172     *
173     * @return the list of storage IDs, or null if fetching storage IDs fails
174     */
175    public @Nullable int[] getStorageIds() {
176        return native_get_storage_ids();
177    }
178
179    /**
180     * Returns the list of object handles for all objects on the given storage unit,
181     * with the given format and parent.
182     * Information about each object can be accessed via {@link #getObjectInfo}.
183     *
184     * @param storageId the storage unit to query
185     * @param format the format of the object to return, or zero for all formats
186     * @param objectHandle the parent object to query, -1 for the storage root,
187     *     or zero for all objects
188     * @return the object handles, or null if fetching object handles fails
189     */
190    public @Nullable int[] getObjectHandles(int storageId, int format, int objectHandle) {
191        return native_get_object_handles(storageId, format, objectHandle);
192    }
193
194    /**
195     * Returns the data for an object as a byte array.
196     * This call may block for an arbitrary amount of time depending on the size
197     * of the data and speed of the devices.
198     *
199     * @param objectHandle handle of the object to read
200     * @param objectSize the size of the object (this should match
201     *      {@link MtpObjectInfo#getCompressedSize})
202     * @return the object's data, or null if reading fails
203     */
204    public @Nullable byte[] getObject(int objectHandle, int objectSize) {
205        Preconditions.checkArgumentNonnegative(objectSize, "objectSize should not be negative");
206        return native_get_object(objectHandle, objectSize);
207    }
208
209    /**
210     * Obtains object bytes in the specified range and writes it to an array.
211     * This call may block for an arbitrary amount of time depending on the size
212     * of the data and speed of the devices.
213     *
214     * @param objectHandle handle of the object to read
215     * @param offset Start index of reading range. It must be a non-negative value at most
216     *     0xffffffff.
217     * @param size Size of reading range. It must be a non-negative value at most Integer.MAX_VALUE
218     *     or 0xffffffff. If 0xffffffff is specified, the method obtains the full bytes of object.
219     * @param buffer Array to write data.
220     * @return Size of bytes that are actually read.
221     */
222    public long getPartialObject(int objectHandle, long offset, long size, @NonNull byte[] buffer)
223            throws IOException {
224        return native_get_partial_object(objectHandle, offset, size, buffer);
225    }
226
227    /**
228     * Obtains object bytes in the specified range and writes it to an array.
229     * This call may block for an arbitrary amount of time depending on the size
230     * of the data and speed of the devices.
231     *
232     * This is a vender-extended operation supported by Android that enables us to pass
233     * unsigned 64-bit offset. Check if the MTP device supports the operation by using
234     * {@link MtpDeviceInfo#getOperationsSupported()}.
235     *
236     * @param objectHandle handle of the object to read
237     * @param offset Start index of reading range. It must be a non-negative value.
238     * @param size Size of reading range. It must be a non-negative value at most Integer.MAX_VALUE.
239     * @param buffer Array to write data.
240     * @return Size of bytes that are actually read.
241     * @see MtpConstants#OPERATION_GET_PARTIAL_OBJECT_64
242     */
243    public long getPartialObject64(int objectHandle, long offset, long size, @NonNull byte[] buffer)
244            throws IOException {
245        return native_get_partial_object_64(objectHandle, offset, size, buffer);
246    }
247
248    /**
249     * Returns the thumbnail data for an object as a byte array.
250     * The size and format of the thumbnail data can be determined via
251     * {@link MtpObjectInfo#getThumbCompressedSize} and
252     * {@link MtpObjectInfo#getThumbFormat}.
253     * For typical devices the format is JPEG.
254     *
255     * @param objectHandle handle of the object to read
256     * @return the object's thumbnail, or null if reading fails
257     */
258    public @Nullable byte[] getThumbnail(int objectHandle) {
259        return native_get_thumbnail(objectHandle);
260    }
261
262    /**
263     * Retrieves the {@link MtpStorageInfo} for a storage unit.
264     *
265     * @param storageId the ID of the storage unit
266     * @return the MtpStorageInfo, or null if fetching storage info fails
267     */
268    public @Nullable MtpStorageInfo getStorageInfo(int storageId) {
269        return native_get_storage_info(storageId);
270    }
271
272    /**
273     * Retrieves the {@link MtpObjectInfo} for an object.
274     *
275     * @param objectHandle the handle of the object
276     * @return the MtpObjectInfo, or null if fetching object info fails
277     */
278    public @Nullable MtpObjectInfo getObjectInfo(int objectHandle) {
279        return native_get_object_info(objectHandle);
280    }
281
282    /**
283     * Deletes an object on the device.  This call may block, since
284     * deleting a directory containing many files may take a long time
285     * on some devices.
286     *
287     * @param objectHandle handle of the object to delete
288     * @return true if the deletion succeeds
289     */
290    public boolean deleteObject(int objectHandle) {
291        return native_delete_object(objectHandle);
292    }
293
294    /**
295     * Retrieves the object handle for the parent of an object on the device.
296     *
297     * @param objectHandle handle of the object to query
298     * @return the parent's handle, or zero if it is in the root of the storage
299     */
300    public long getParent(int objectHandle) {
301        return native_get_parent(objectHandle);
302    }
303
304    /**
305     * Retrieves the ID of the storage unit containing the given object on the device.
306     *
307     * @param objectHandle handle of the object to query
308     * @return the object's storage unit ID
309     */
310    public long getStorageId(int objectHandle) {
311        return native_get_storage_id(objectHandle);
312    }
313
314    /**
315     * Copies the data for an object to a file in external storage.
316     * This call may block for an arbitrary amount of time depending on the size
317     * of the data and speed of the devices.
318     *
319     * @param objectHandle handle of the object to read
320     * @param destPath path to destination for the file transfer.
321     *      This path should be in the external storage as defined by
322     *      {@link android.os.Environment#getExternalStorageDirectory}
323     * @return true if the file transfer succeeds
324     */
325    public boolean importFile(int objectHandle, @NonNull String destPath) {
326        return native_import_file(objectHandle, destPath);
327    }
328
329    /**
330     * Copies the data for an object to a file descriptor.
331     * This call may block for an arbitrary amount of time depending on the size
332     * of the data and speed of the devices. The file descriptor is not closed
333     * on completion, and must be done by the caller.
334     *
335     * @param objectHandle handle of the object to read
336     * @param descriptor file descriptor to write the data to for the file transfer.
337     * @return true if the file transfer succeeds
338     */
339    public boolean importFile(int objectHandle, @NonNull ParcelFileDescriptor descriptor) {
340        return native_import_file(objectHandle, descriptor.getFd());
341    }
342
343    /**
344     * Copies the data for an object from a file descriptor.
345     * This call may block for an arbitrary amount of time depending on the size
346     * of the data and speed of the devices. The file descriptor is not closed
347     * on completion, and must be done by the caller.
348     *
349     * @param objectHandle handle of the target file
350     * @param size size of the file in bytes
351     * @param descriptor file descriptor to read the data from.
352     * @return true if the file transfer succeeds
353     */
354    public boolean sendObject(
355            int objectHandle, long size, @NonNull ParcelFileDescriptor descriptor) {
356        return native_send_object(objectHandle, size, descriptor.getFd());
357    }
358
359    /**
360     * Uploads an object metadata for a new entry. The {@link MtpObjectInfo} can be
361     * created with the {@link MtpObjectInfo.Builder} class.
362     *
363     * The returned {@link MtpObjectInfo} has the new object handle field filled in.
364     *
365     * @param info metadata of the entry
366     * @return object info of the created entry, or null if sending object info fails
367     */
368    public @Nullable MtpObjectInfo sendObjectInfo(@NonNull MtpObjectInfo info) {
369        return native_send_object_info(info);
370    }
371
372    /**
373     * Reads an event from the device. It blocks the current thread until it gets an event.
374     * It throws OperationCanceledException if it is cancelled by signal.
375     *
376     * @param signal signal for cancellation
377     * @return obtained event
378     * @throws IOException
379     */
380    public @NonNull MtpEvent readEvent(@Nullable CancellationSignal signal) throws IOException {
381        final int handle = native_submit_event_request();
382        Preconditions.checkState(handle >= 0, "Other thread is reading an event.");
383
384        if (signal != null) {
385            signal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
386                @Override
387                public void onCancel() {
388                    native_discard_event_request(handle);
389                }
390            });
391        }
392
393        try {
394            return native_reap_event_request(handle);
395        } finally {
396            if (signal != null) {
397                signal.setOnCancelListener(null);
398            }
399        }
400    }
401
402    /**
403     * Returns object size in 64-bit integer.
404     *
405     * Though MtpObjectInfo#getCompressedSize returns the object size in 32-bit unsigned integer,
406     * this method returns the object size in 64-bit integer from the object property. Thus it can
407     * fetch 4GB+ object size correctly. If the device does not support objectSize property, it
408     * throws IOException.
409     * @hide
410     */
411    public long getObjectSizeLong(int handle, int format) throws IOException {
412        return native_get_object_size_long(handle, format);
413    }
414
415    // used by the JNI code
416    private long mNativeContext;
417
418    private native boolean native_open(String deviceName, int fd);
419    private native void native_close();
420    private native MtpDeviceInfo native_get_device_info();
421    private native int[] native_get_storage_ids();
422    private native MtpStorageInfo native_get_storage_info(int storageId);
423    private native int[] native_get_object_handles(int storageId, int format, int objectHandle);
424    private native MtpObjectInfo native_get_object_info(int objectHandle);
425    private native byte[] native_get_object(int objectHandle, long objectSize);
426    private native long native_get_partial_object(
427            int objectHandle, long offset, long objectSize, byte[] buffer) throws IOException;
428    private native int native_get_partial_object_64(
429            int objectHandle, long offset, long objectSize, byte[] buffer) throws IOException;
430    private native byte[] native_get_thumbnail(int objectHandle);
431    private native boolean native_delete_object(int objectHandle);
432    private native int native_get_parent(int objectHandle);
433    private native int native_get_storage_id(int objectHandle);
434    private native boolean native_import_file(int objectHandle, String destPath);
435    private native boolean native_import_file(int objectHandle, int fd);
436    private native boolean native_send_object(int objectHandle, long size, int fd);
437    private native MtpObjectInfo native_send_object_info(MtpObjectInfo info);
438    private native int native_submit_event_request() throws IOException;
439    private native MtpEvent native_reap_event_request(int handle) throws IOException;
440    private native void native_discard_event_request(int handle);
441    private native long native_get_object_size_long(int handle, int format) throws IOException;
442}
443