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            if (mCloseGuard != null) {
127                mCloseGuard.warnIfOpen();
128            }
129
130            close();
131        } finally {
132            super.finalize();
133        }
134    }
135
136    /**
137     * Returns the name of the USB device
138     * This returns the same value as {@link android.hardware.usb.UsbDevice#getDeviceName}
139     * for the device's {@link android.hardware.usb.UsbDevice}
140     *
141     * @return the device name
142     */
143    public @NonNull String getDeviceName() {
144        return mDevice.getDeviceName();
145    }
146
147    /**
148     * Returns the USB ID of the USB device.
149     * This returns the same value as {@link android.hardware.usb.UsbDevice#getDeviceId}
150     * for the device's {@link android.hardware.usb.UsbDevice}
151     *
152     * @return the device ID
153     */
154    public int getDeviceId() {
155        return mDevice.getDeviceId();
156    }
157
158    @Override
159    public @NonNull String toString() {
160        return mDevice.getDeviceName();
161    }
162
163    /**
164     * Returns the {@link MtpDeviceInfo} for this device
165     *
166     * @return the device info, or null if fetching device info fails
167     */
168    public @Nullable MtpDeviceInfo getDeviceInfo() {
169        return native_get_device_info();
170    }
171
172    /**
173     * Returns the list of IDs for all storage units on this device
174     * Information about each storage unit can be accessed via {@link #getStorageInfo}.
175     *
176     * @return the list of storage IDs, or null if fetching storage IDs fails
177     */
178    public @Nullable int[] getStorageIds() {
179        return native_get_storage_ids();
180    }
181
182    /**
183     * Returns the list of object handles for all objects on the given storage unit,
184     * with the given format and parent.
185     * Information about each object can be accessed via {@link #getObjectInfo}.
186     *
187     * @param storageId the storage unit to query
188     * @param format the format of the object to return, or zero for all formats
189     * @param objectHandle the parent object to query, -1 for the storage root,
190     *     or zero for all objects
191     * @return the object handles, or null if fetching object handles fails
192     */
193    public @Nullable int[] getObjectHandles(int storageId, int format, int objectHandle) {
194        return native_get_object_handles(storageId, format, objectHandle);
195    }
196
197    /**
198     * Returns the data for an object as a byte array.
199     * This call may block for an arbitrary amount of time depending on the size
200     * of the data and speed of the devices.
201     *
202     * @param objectHandle handle of the object to read
203     * @param objectSize the size of the object (this should match
204     *      {@link MtpObjectInfo#getCompressedSize})
205     * @return the object's data, or null if reading fails
206     */
207    public @Nullable byte[] getObject(int objectHandle, int objectSize) {
208        Preconditions.checkArgumentNonnegative(objectSize, "objectSize should not be negative");
209        return native_get_object(objectHandle, objectSize);
210    }
211
212    /**
213     * Obtains object bytes in the specified range and writes it to an array.
214     * This call may block for an arbitrary amount of time depending on the size
215     * of the data and speed of the devices.
216     *
217     * @param objectHandle handle of the object to read
218     * @param offset Start index of reading range. It must be a non-negative value at most
219     *     0xffffffff.
220     * @param size Size of reading range. It must be a non-negative value at most Integer.MAX_VALUE
221     *     or 0xffffffff. If 0xffffffff is specified, the method obtains the full bytes of object.
222     * @param buffer Array to write data.
223     * @return Size of bytes that are actually read.
224     */
225    public long getPartialObject(int objectHandle, long offset, long size, @NonNull byte[] buffer)
226            throws IOException {
227        return native_get_partial_object(objectHandle, offset, size, buffer);
228    }
229
230    /**
231     * Obtains object bytes in the specified range and writes it to an array.
232     * This call may block for an arbitrary amount of time depending on the size
233     * of the data and speed of the devices.
234     *
235     * This is a vender-extended operation supported by Android that enables us to pass
236     * unsigned 64-bit offset. Check if the MTP device supports the operation by using
237     * {@link MtpDeviceInfo#getOperationsSupported()}.
238     *
239     * @param objectHandle handle of the object to read
240     * @param offset Start index of reading range. It must be a non-negative value.
241     * @param size Size of reading range. It must be a non-negative value at most Integer.MAX_VALUE.
242     * @param buffer Array to write data.
243     * @return Size of bytes that are actually read.
244     * @see MtpConstants#OPERATION_GET_PARTIAL_OBJECT_64
245     */
246    public long getPartialObject64(int objectHandle, long offset, long size, @NonNull byte[] buffer)
247            throws IOException {
248        return native_get_partial_object_64(objectHandle, offset, size, buffer);
249    }
250
251    /**
252     * Returns the thumbnail data for an object as a byte array.
253     * The size and format of the thumbnail data can be determined via
254     * {@link MtpObjectInfo#getThumbCompressedSize} and
255     * {@link MtpObjectInfo#getThumbFormat}.
256     * For typical devices the format is JPEG.
257     *
258     * @param objectHandle handle of the object to read
259     * @return the object's thumbnail, or null if reading fails
260     */
261    public @Nullable byte[] getThumbnail(int objectHandle) {
262        return native_get_thumbnail(objectHandle);
263    }
264
265    /**
266     * Retrieves the {@link MtpStorageInfo} for a storage unit.
267     *
268     * @param storageId the ID of the storage unit
269     * @return the MtpStorageInfo, or null if fetching storage info fails
270     */
271    public @Nullable MtpStorageInfo getStorageInfo(int storageId) {
272        return native_get_storage_info(storageId);
273    }
274
275    /**
276     * Retrieves the {@link MtpObjectInfo} for an object.
277     *
278     * @param objectHandle the handle of the object
279     * @return the MtpObjectInfo, or null if fetching object info fails
280     */
281    public @Nullable MtpObjectInfo getObjectInfo(int objectHandle) {
282        return native_get_object_info(objectHandle);
283    }
284
285    /**
286     * Deletes an object on the device.  This call may block, since
287     * deleting a directory containing many files may take a long time
288     * on some devices.
289     *
290     * @param objectHandle handle of the object to delete
291     * @return true if the deletion succeeds
292     */
293    public boolean deleteObject(int objectHandle) {
294        return native_delete_object(objectHandle);
295    }
296
297    /**
298     * Retrieves the object handle for the parent of an object on the device.
299     *
300     * @param objectHandle handle of the object to query
301     * @return the parent's handle, or zero if it is in the root of the storage
302     */
303    public long getParent(int objectHandle) {
304        return native_get_parent(objectHandle);
305    }
306
307    /**
308     * Retrieves the ID of the storage unit containing the given object on the device.
309     *
310     * @param objectHandle handle of the object to query
311     * @return the object's storage unit ID
312     */
313    public long getStorageId(int objectHandle) {
314        return native_get_storage_id(objectHandle);
315    }
316
317    /**
318     * Copies the data for an object to a file in external storage.
319     * This call may block for an arbitrary amount of time depending on the size
320     * of the data and speed of the devices.
321     *
322     * @param objectHandle handle of the object to read
323     * @param destPath path to destination for the file transfer.
324     *      This path should be in the external storage as defined by
325     *      {@link android.os.Environment#getExternalStorageDirectory}
326     * @return true if the file transfer succeeds
327     */
328    public boolean importFile(int objectHandle, @NonNull String destPath) {
329        return native_import_file(objectHandle, destPath);
330    }
331
332    /**
333     * Copies the data for an object to a file descriptor.
334     * This call may block for an arbitrary amount of time depending on the size
335     * of the data and speed of the devices. The file descriptor is not closed
336     * on completion, and must be done by the caller.
337     *
338     * @param objectHandle handle of the object to read
339     * @param descriptor file descriptor to write the data to for the file transfer.
340     * @return true if the file transfer succeeds
341     */
342    public boolean importFile(int objectHandle, @NonNull ParcelFileDescriptor descriptor) {
343        return native_import_file(objectHandle, descriptor.getFd());
344    }
345
346    /**
347     * Copies the data for an object from a file descriptor.
348     * This call may block for an arbitrary amount of time depending on the size
349     * of the data and speed of the devices. The file descriptor is not closed
350     * on completion, and must be done by the caller.
351     *
352     * @param objectHandle handle of the target file
353     * @param size size of the file in bytes
354     * @param descriptor file descriptor to read the data from.
355     * @return true if the file transfer succeeds
356     */
357    public boolean sendObject(
358            int objectHandle, long size, @NonNull ParcelFileDescriptor descriptor) {
359        return native_send_object(objectHandle, size, descriptor.getFd());
360    }
361
362    /**
363     * Uploads an object metadata for a new entry. The {@link MtpObjectInfo} can be
364     * created with the {@link MtpObjectInfo.Builder} class.
365     *
366     * The returned {@link MtpObjectInfo} has the new object handle field filled in.
367     *
368     * @param info metadata of the entry
369     * @return object info of the created entry, or null if sending object info fails
370     */
371    public @Nullable MtpObjectInfo sendObjectInfo(@NonNull MtpObjectInfo info) {
372        return native_send_object_info(info);
373    }
374
375    /**
376     * Reads an event from the device. It blocks the current thread until it gets an event.
377     * It throws OperationCanceledException if it is cancelled by signal.
378     *
379     * @param signal signal for cancellation
380     * @return obtained event
381     * @throws IOException
382     */
383    public @NonNull MtpEvent readEvent(@Nullable CancellationSignal signal) throws IOException {
384        final int handle = native_submit_event_request();
385        Preconditions.checkState(handle >= 0, "Other thread is reading an event.");
386
387        if (signal != null) {
388            signal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
389                @Override
390                public void onCancel() {
391                    native_discard_event_request(handle);
392                }
393            });
394        }
395
396        try {
397            return native_reap_event_request(handle);
398        } finally {
399            if (signal != null) {
400                signal.setOnCancelListener(null);
401            }
402        }
403    }
404
405    /**
406     * Returns object size in 64-bit integer.
407     *
408     * Though MtpObjectInfo#getCompressedSize returns the object size in 32-bit unsigned integer,
409     * this method returns the object size in 64-bit integer from the object property. Thus it can
410     * fetch 4GB+ object size correctly. If the device does not support objectSize property, it
411     * throws IOException.
412     * @hide
413     */
414    public long getObjectSizeLong(int handle, int format) throws IOException {
415        return native_get_object_size_long(handle, format);
416    }
417
418    // used by the JNI code
419    private long mNativeContext;
420
421    private native boolean native_open(String deviceName, int fd);
422    private native void native_close();
423    private native MtpDeviceInfo native_get_device_info();
424    private native int[] native_get_storage_ids();
425    private native MtpStorageInfo native_get_storage_info(int storageId);
426    private native int[] native_get_object_handles(int storageId, int format, int objectHandle);
427    private native MtpObjectInfo native_get_object_info(int objectHandle);
428    private native byte[] native_get_object(int objectHandle, long objectSize);
429    private native long native_get_partial_object(
430            int objectHandle, long offset, long objectSize, byte[] buffer) throws IOException;
431    private native int native_get_partial_object_64(
432            int objectHandle, long offset, long objectSize, byte[] buffer) throws IOException;
433    private native byte[] native_get_thumbnail(int objectHandle);
434    private native boolean native_delete_object(int objectHandle);
435    private native int native_get_parent(int objectHandle);
436    private native int native_get_storage_id(int objectHandle);
437    private native boolean native_import_file(int objectHandle, String destPath);
438    private native boolean native_import_file(int objectHandle, int fd);
439    private native boolean native_send_object(int objectHandle, long size, int fd);
440    private native MtpObjectInfo native_send_object_info(MtpObjectInfo info);
441    private native int native_submit_event_request() throws IOException;
442    private native MtpEvent native_reap_event_request(int handle) throws IOException;
443    private native void native_discard_event_request(int handle);
444    private native long native_get_object_size_long(int handle, int format) throws IOException;
445}
446