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