diff options
Diffstat (limited to 'src/com/android/gallery3d/data/MtpClient.java')
-rw-r--r-- | src/com/android/gallery3d/data/MtpClient.java | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/data/MtpClient.java b/src/com/android/gallery3d/data/MtpClient.java new file mode 100644 index 000000000..737b5b60d --- /dev/null +++ b/src/com/android/gallery3d/data/MtpClient.java @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.data; + +import android.annotation.TargetApi; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.mtp.MtpDevice; +import android.mtp.MtpObjectInfo; +import android.mtp.MtpStorageInfo; +import android.util.Log; + +import com.android.gallery3d.common.ApiHelper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * This class helps an application manage a list of connected MTP or PTP devices. + * It listens for MTP devices being attached and removed from the USB host bus + * and notifies the application when the MTP device list changes. + */ +@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1) +public class MtpClient { + + private static final String TAG = "MtpClient"; + + private static final String ACTION_USB_PERMISSION = + "android.mtp.MtpClient.action.USB_PERMISSION"; + + private final Context mContext; + private final UsbManager mUsbManager; + private final ArrayList<Listener> mListeners = new ArrayList<Listener>(); + // mDevices contains all MtpDevices that have been seen by our client, + // so we can inform when the device has been detached. + // mDevices is also used for synchronization in this class. + private final HashMap<String, MtpDevice> mDevices = new HashMap<String, MtpDevice>(); + // List of MTP devices we should not try to open for which we are currently + // asking for permission to open. + private final ArrayList<String> mRequestPermissionDevices = new ArrayList<String>(); + // List of MTP devices we should not try to open. + // We add devices to this list if the user canceled a permission request or we were + // unable to open the device. + private final ArrayList<String> mIgnoredDevices = new ArrayList<String>(); + + private final PendingIntent mPermissionIntent; + + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + UsbDevice usbDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + String deviceName = usbDevice.getDeviceName(); + + synchronized (mDevices) { + MtpDevice mtpDevice = mDevices.get(deviceName); + + if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { + if (mtpDevice == null) { + mtpDevice = openDeviceLocked(usbDevice); + } + if (mtpDevice != null) { + for (Listener listener : mListeners) { + listener.deviceAdded(mtpDevice); + } + } + } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { + if (mtpDevice != null) { + mDevices.remove(deviceName); + mRequestPermissionDevices.remove(deviceName); + mIgnoredDevices.remove(deviceName); + for (Listener listener : mListeners) { + listener.deviceRemoved(mtpDevice); + } + } + } else if (ACTION_USB_PERMISSION.equals(action)) { + mRequestPermissionDevices.remove(deviceName); + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + Log.d(TAG, "ACTION_USB_PERMISSION: " + permission); + if (permission) { + if (mtpDevice == null) { + mtpDevice = openDeviceLocked(usbDevice); + } + if (mtpDevice != null) { + for (Listener listener : mListeners) { + listener.deviceAdded(mtpDevice); + } + } + } else { + // so we don't ask for permission again + mIgnoredDevices.add(deviceName); + } + } + } + } + }; + + /** + * An interface for being notified when MTP or PTP devices are attached + * or removed. In the current implementation, only PTP devices are supported. + */ + public interface Listener { + /** + * Called when a new device has been added + * + * @param device the new device that was added + */ + public void deviceAdded(MtpDevice device); + + /** + * Called when a new device has been removed + * + * @param device the device that was removed + */ + public void deviceRemoved(MtpDevice device); + } + + /** + * Tests to see if a {@link android.hardware.usb.UsbDevice} + * supports the PTP protocol (typically used by digital cameras) + * + * @param device the device to test + * @return true if the device is a PTP device. + */ + static public boolean isCamera(UsbDevice device) { + int count = device.getInterfaceCount(); + for (int i = 0; i < count; i++) { + UsbInterface intf = device.getInterface(i); + if (intf.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE && + intf.getInterfaceSubclass() == 1 && + intf.getInterfaceProtocol() == 1) { + return true; + } + } + return false; + } + + /** + * MtpClient constructor + * + * @param context the {@link android.content.Context} to use for the MtpClient + */ + public MtpClient(Context context) { + mContext = context; + mUsbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE); + mPermissionIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_USB_PERMISSION), 0); + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + filter.addAction(ACTION_USB_PERMISSION); + context.registerReceiver(mUsbReceiver, filter); + } + + /** + * Opens the {@link android.hardware.usb.UsbDevice} for an MTP or PTP + * device and return an {@link android.mtp.MtpDevice} for it. + * + * @param usbDevice the device to open + * @return an MtpDevice for the device. + */ + private MtpDevice openDeviceLocked(UsbDevice usbDevice) { + String deviceName = usbDevice.getDeviceName(); + + // don't try to open devices that we have decided to ignore + // or are currently asking permission for + if (isCamera(usbDevice) && !mIgnoredDevices.contains(deviceName) + && !mRequestPermissionDevices.contains(deviceName)) { + if (!mUsbManager.hasPermission(usbDevice)) { + mUsbManager.requestPermission(usbDevice, mPermissionIntent); + mRequestPermissionDevices.add(deviceName); + } else { + UsbDeviceConnection connection = mUsbManager.openDevice(usbDevice); + if (connection != null) { + MtpDevice mtpDevice = new MtpDevice(usbDevice); + if (mtpDevice.open(connection)) { + mDevices.put(usbDevice.getDeviceName(), mtpDevice); + return mtpDevice; + } else { + // so we don't try to open it again + mIgnoredDevices.add(deviceName); + } + } else { + // so we don't try to open it again + mIgnoredDevices.add(deviceName); + } + } + } + return null; + } + + /** + * Closes all resources related to the MtpClient object + */ + public void close() { + mContext.unregisterReceiver(mUsbReceiver); + } + + /** + * Registers a {@link com.android.gallery3d.data.MtpClient.Listener} interface to receive + * notifications when MTP or PTP devices are added or removed. + * + * @param listener the listener to register + */ + public void addListener(Listener listener) { + synchronized (mDevices) { + if (!mListeners.contains(listener)) { + mListeners.add(listener); + } + } + } + + /** + * Unregisters a {@link com.android.gallery3d.data.MtpClient.Listener} interface. + * + * @param listener the listener to unregister + */ + public void removeListener(Listener listener) { + synchronized (mDevices) { + mListeners.remove(listener); + } + } + + /** + * Retrieves an {@link android.mtp.MtpDevice} object for the USB device + * with the given name. + * + * @param deviceName the name of the USB device + * @return the MtpDevice, or null if it does not exist + */ + public MtpDevice getDevice(String deviceName) { + synchronized (mDevices) { + return mDevices.get(deviceName); + } + } + + /** + * Retrieves an {@link android.mtp.MtpDevice} object for the USB device + * with the given ID. + * + * @param id the ID of the USB device + * @return the MtpDevice, or null if it does not exist + */ + public MtpDevice getDevice(int id) { + synchronized (mDevices) { + return mDevices.get(UsbDevice.getDeviceName(id)); + } + } + + /** + * Retrieves a list of all currently connected {@link android.mtp.MtpDevice}. + * + * @return the list of MtpDevices + */ + public List<MtpDevice> getDeviceList() { + synchronized (mDevices) { + // Query the USB manager since devices might have attached + // before we added our listener. + for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { + if (mDevices.get(usbDevice.getDeviceName()) == null) { + openDeviceLocked(usbDevice); + } + } + + return new ArrayList<MtpDevice>(mDevices.values()); + } + } + + /** + * Retrieves a list of all {@link android.mtp.MtpStorageInfo} + * for the MTP or PTP device with the given USB device name + * + * @param deviceName the name of the USB device + * @return the list of MtpStorageInfo + */ + public List<MtpStorageInfo> getStorageList(String deviceName) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return null; + } + int[] storageIds = device.getStorageIds(); + if (storageIds == null) { + return null; + } + + int length = storageIds.length; + ArrayList<MtpStorageInfo> storageList = new ArrayList<MtpStorageInfo>(length); + for (int i = 0; i < length; i++) { + MtpStorageInfo info = device.getStorageInfo(storageIds[i]); + if (info == null) { + Log.w(TAG, "getStorageInfo failed"); + } else { + storageList.add(info); + } + } + return storageList; + } + + /** + * Retrieves the {@link android.mtp.MtpObjectInfo} for an object on + * the MTP or PTP device with the given USB device name with the given + * object handle + * + * @param deviceName the name of the USB device + * @param objectHandle handle of the object to query + * @return the MtpObjectInfo + */ + public MtpObjectInfo getObjectInfo(String deviceName, int objectHandle) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return null; + } + return device.getObjectInfo(objectHandle); + } + + /** + * Deletes an object on the MTP or PTP device with the given USB device name. + * + * @param deviceName the name of the USB device + * @param objectHandle handle of the object to delete + * @return true if the deletion succeeds + */ + public boolean deleteObject(String deviceName, int objectHandle) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return false; + } + return device.deleteObject(objectHandle); + } + + /** + * Retrieves a list of {@link android.mtp.MtpObjectInfo} for all objects + * on the MTP or PTP device with the given USB device name and given storage ID + * and/or object handle. + * If the object handle is zero, then all objects in the root of the storage unit + * will be returned. Otherwise, all immediate children of the object will be returned. + * If the storage ID is also zero, then all objects on all storage units will be returned. + * + * @param deviceName the name of the USB device + * @param storageId the ID of the storage unit to query, or zero for all + * @param objectHandle the handle of the parent object to query, or zero for the storage root + * @return the list of MtpObjectInfo + */ + public List<MtpObjectInfo> getObjectList(String deviceName, int storageId, int objectHandle) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return null; + } + if (objectHandle == 0) { + // all objects in root of storage + objectHandle = 0xFFFFFFFF; + } + int[] handles = device.getObjectHandles(storageId, 0, objectHandle); + if (handles == null) { + return null; + } + + int length = handles.length; + ArrayList<MtpObjectInfo> objectList = new ArrayList<MtpObjectInfo>(length); + for (int i = 0; i < length; i++) { + MtpObjectInfo info = device.getObjectInfo(handles[i]); + if (info == null) { + Log.w(TAG, "getObjectInfo failed"); + } else { + objectList.add(info); + } + } + return objectList; + } + + /** + * Returns the data for an object as a byte array. + * + * @param deviceName the name of the USB device containing the object + * @param objectHandle handle of the object to read + * @param objectSize the size of the object (this should match + * {@link android.mtp.MtpObjectInfo#getCompressedSize} + * @return the object's data, or null if reading fails + */ + public byte[] getObject(String deviceName, int objectHandle, int objectSize) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return null; + } + return device.getObject(objectHandle, objectSize); + } + + /** + * Returns the thumbnail data for an object as a byte array. + * + * @param deviceName the name of the USB device containing the object + * @param objectHandle handle of the object to read + * @return the object's thumbnail, or null if reading fails + */ + public byte[] getThumbnail(String deviceName, int objectHandle) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return null; + } + return device.getThumbnail(objectHandle); + } + + /** + * Copies the data for an object to a file in external storage. + * + * @param deviceName the name of the USB device containing the object + * @param objectHandle handle of the object to read + * @param destPath path to destination for the file transfer. + * This path should be in the external storage as defined by + * {@link android.os.Environment#getExternalStorageDirectory} + * @return true if the file transfer succeeds + */ + public boolean importFile(String deviceName, int objectHandle, String destPath) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return false; + } + return device.importFile(objectHandle, destPath); + } +} |