diff options
Diffstat (limited to 'media')
48 files changed, 4033 insertions, 413 deletions
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java index 6a3ff7c..ee2c1e8 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -46,8 +46,9 @@ public class MediaFile { public static final int FILE_TYPE_OGG = 7; public static final int FILE_TYPE_AAC = 8; public static final int FILE_TYPE_MKA = 9; + public static final int FILE_TYPE_FLAC = 10; private static final int FIRST_AUDIO_FILE_TYPE = FILE_TYPE_MP3; - private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_MKA; + private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_FLAC; // MIDI file types public static final int FILE_TYPE_MID = 11; @@ -99,8 +100,7 @@ public class MediaFile { public static final int FILE_TYPE_MS_WORD = 104; public static final int FILE_TYPE_MS_EXCEL = 105; public static final int FILE_TYPE_MS_POWERPOINT = 106; - public static final int FILE_TYPE_FLAC = 107; - public static final int FILE_TYPE_ZIP = 108; + public static final int FILE_TYPE_ZIP = 107; static class MediaFileType { diff --git a/media/java/android/mtp/MtpClient.java b/media/java/android/mtp/MtpClient.java new file mode 100644 index 0000000..19ee92a --- /dev/null +++ b/media/java/android/mtp/MtpClient.java @@ -0,0 +1,270 @@ +/* + * 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 android.mtp; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.UsbConstants; +import android.hardware.UsbDevice; +import android.hardware.UsbInterface; +import android.hardware.UsbManager; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * This class helps an application manage a list of connected MTP 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. + * {@hide} + */ +public class MtpClient { + + private static final String TAG = "MtpClient"; + + private final Context mContext; + private final UsbManager mUsbManager; + private final ArrayList<Listener> mListeners = new ArrayList<Listener>(); + private final ArrayList<MtpDevice> mDeviceList = new ArrayList<MtpDevice>(); + + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String deviceName = intent.getStringExtra(UsbManager.EXTRA_DEVICE_NAME); + + synchronized (mDeviceList) { + MtpDevice mtpDevice = getDeviceLocked(deviceName); + + if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { + if (mtpDevice == null) { + UsbDevice usbDevice = + (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + mtpDevice = openDevice(usbDevice); + } + if (mtpDevice != null) { + mDeviceList.add(mtpDevice); + for (Listener listener : mListeners) { + listener.deviceAdded(mtpDevice); + } + } + } else if (mtpDevice != null) { + mDeviceList.remove(mtpDevice); + for (Listener listener : mListeners) { + listener.deviceRemoved(mtpDevice); + } + } + } + } + }; + + public interface Listener { + public void deviceAdded(MtpDevice device); + public void deviceRemoved(MtpDevice 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; + } + + private MtpDevice openDevice(UsbDevice usbDevice) { + if (isCamera(usbDevice)) { + MtpDevice mtpDevice = new MtpDevice(usbDevice); + if (mtpDevice.open(mUsbManager)) { + return mtpDevice; + } + } + return null; + } + + public MtpClient(Context context) { + mContext = context; + mUsbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE); + + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + context.registerReceiver(mUsbReceiver, filter); + + for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { + MtpDevice mtpDevice = getDeviceLocked(usbDevice.getDeviceName()); + if (mtpDevice == null) { + mtpDevice = openDevice(usbDevice); + } + if (mtpDevice != null) { + mDeviceList.add(mtpDevice); + } + } + } + + public void close() { + mContext.unregisterReceiver(mUsbReceiver); + } + + @Override + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + public void addListener(Listener listener) { + synchronized (mDeviceList) { + if (!mListeners.contains(listener)) { + mListeners.add(listener); + } + } + } + + public void removeListener(Listener listener) { + synchronized (mDeviceList) { + mListeners.remove(listener); + } + } + + public MtpDevice getDevice(String deviceName) { + synchronized (mDeviceList) { + return getDeviceLocked(deviceName); + } + } + + public MtpDevice getDevice(int id) { + synchronized (mDeviceList) { + return getDeviceLocked(UsbDevice.getDeviceName(id)); + } + } + + private MtpDevice getDeviceLocked(String deviceName) { + for (MtpDevice device : mDeviceList) { + if (device.getDeviceName().equals(deviceName)) { + return device; + } + } + return null; + } + + public List<MtpDevice> getDeviceList() { + synchronized (mDeviceList) { + return new ArrayList<MtpDevice>(mDeviceList); + } + } + + 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; + } + + public MtpObjectInfo getObjectInfo(String deviceName, int objectHandle) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return null; + } + return device.getObjectInfo(objectHandle); + } + + public boolean deleteObject(String deviceName, int objectHandle) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return false; + } + return device.deleteObject(objectHandle); + } + + 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; + } + + public byte[] getObject(String deviceName, int objectHandle, int objectSize) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return null; + } + return device.getObject(objectHandle, objectSize); + } + + public byte[] getThumbnail(String deviceName, int objectHandle) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return null; + } + return device.getThumbnail(objectHandle); + } + + public boolean importFile(String deviceName, int objectHandle, String destPath) { + MtpDevice device = getDevice(deviceName); + if (device == null) { + return false; + } + return device.importFile(objectHandle, destPath); + } +} diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index a595562..98de2f7 100644 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -309,6 +309,7 @@ public class MtpDatabase { MtpConstants.FORMAT_M3U_PLAYLIST, MtpConstants.FORMAT_PLS_PLAYLIST, MtpConstants.FORMAT_XML_DOCUMENT, + MtpConstants.FORMAT_FLAC, }; } diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java new file mode 100644 index 0000000..2d726c2 --- /dev/null +++ b/media/java/android/mtp/MtpDevice.java @@ -0,0 +1,141 @@ +/* + * 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 android.mtp; + +import android.hardware.UsbDevice; +import android.hardware.UsbManager; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +/** + * This class represents an MTP device connected on the USB host bus. + * + * {@hide} + */ +public final class MtpDevice { + + private static final String TAG = "MtpDevice"; + + private final UsbDevice mDevice; + + static { + System.loadLibrary("media_jni"); + } + + public MtpDevice(UsbDevice device) { + mDevice = device; + } + + public boolean open(UsbManager manager) { + if (manager.openDevice(mDevice)) { + return native_open(mDevice.getDeviceName(), mDevice.getFileDescriptor()); + } else { + return false; + } + } + + public void close() { + Log.d(TAG, "close"); + native_close(); + } + + @Override + protected void finalize() throws Throwable { + Log.d(TAG, "finalize"); + try { + native_close(); + } finally { + super.finalize(); + } + } + + public String getDeviceName() { + return mDevice.getDeviceName(); + } + + public int getDeviceId() { + return mDevice.getDeviceId(); + } + + @Override + public String toString() { + return mDevice.getDeviceName(); + } + + public MtpDeviceInfo getDeviceInfo() { + return native_get_device_info(); + } + + public int[] getStorageIds() { + return native_get_storage_ids(); + } + + public int[] getObjectHandles(int storageId, int format, int objectHandle) { + return native_get_object_handles(storageId, format, objectHandle); + } + + public byte[] getObject(int objectHandle, int objectSize) { + return native_get_object(objectHandle, objectSize); + } + + public byte[] getThumbnail(int objectHandle) { + return native_get_thumbnail(objectHandle); + } + + public MtpStorageInfo getStorageInfo(int storageId) { + return native_get_storage_info(storageId); + } + + public MtpObjectInfo getObjectInfo(int objectHandle) { + return native_get_object_info(objectHandle); + } + + public boolean deleteObject(int objectHandle) { + return native_delete_object(objectHandle); + } + + public long getParent(int objectHandle) { + return native_get_parent(objectHandle); + } + + public long getStorageID(int objectHandle) { + return native_get_storage_id(objectHandle); + } + + // Reads a file from device to host to the specified destination. + // Returns true if the transfer succeeds. + public boolean importFile(int objectHandle, String destPath) { + return native_import_file(objectHandle, destPath); + } + + // used by the JNI code + private int mNativeContext; + + private native boolean native_open(String deviceName, int fd); + private native void native_close(); + private native MtpDeviceInfo native_get_device_info(); + private native int[] native_get_storage_ids(); + private native MtpStorageInfo native_get_storage_info(int storageId); + private native int[] native_get_object_handles(int storageId, int format, int objectHandle); + private native MtpObjectInfo native_get_object_info(int objectHandle); + private native byte[] native_get_object(int objectHandle, int objectSize); + private native byte[] native_get_thumbnail(int objectHandle); + private native boolean native_delete_object(int objectHandle); + private native long native_get_parent(int objectHandle); + private native long native_get_storage_id(int objectHandle); + private native boolean native_import_file(int objectHandle, String destPath); +} diff --git a/media/java/android/mtp/MtpDeviceInfo.java b/media/java/android/mtp/MtpDeviceInfo.java new file mode 100644 index 0000000..d918c20 --- /dev/null +++ b/media/java/android/mtp/MtpDeviceInfo.java @@ -0,0 +1,72 @@ +/* + * 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 android.mtp; + +/** + * This class encapsulates information about an MTP device. + * This corresponds to the DeviceInfo Dataset described in + * section 5.1.1 of the MTP specification. + * + * {@hide} + */ +public class MtpDeviceInfo { + + private String mManufacturer; + private String mModel; + private String mVersion; + private String mSerialNumber; + + // only instantiated via JNI + private MtpDeviceInfo() { + } + + /** + * Returns the manufacturer's name for the MTP device + * + * @return the manufacturer name + */ + public final String getManufacturer() { + return mManufacturer; + } + + /** + * Returns the model name for the MTP device + * + * @return the model name + */ + public final String getModel() { + return mModel; + } + + /** + * Returns the version string the MTP device + * + * @return the device version + */ + public final String getVersion() { + return mVersion; + } + + /** + * Returns the unique serial number for the MTP device + * + * @return the serial number + */ + public final String getSerialNumber() { + return mSerialNumber; + } +}
\ No newline at end of file diff --git a/media/java/android/mtp/MtpObjectInfo.java b/media/java/android/mtp/MtpObjectInfo.java new file mode 100644 index 0000000..309d524 --- /dev/null +++ b/media/java/android/mtp/MtpObjectInfo.java @@ -0,0 +1,257 @@ +/* + * 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 android.mtp; + +/** + * This class encapsulates information about an object on an MTP device. + * This corresponds to the ObjectInfo Dataset described in + * section 5.3.1 of the MTP specification. + * + * {@hide} + */ +public final class MtpObjectInfo { + private int mHandle; + private int mStorageId; + private int mFormat; + private int mProtectionStatus; + private int mCompressedSize; + private int mThumbFormat; + private int mThumbCompressedSize; + private int mThumbPixWidth; + private int mThumbPixHeight; + private int mImagePixWidth; + private int mImagePixHeight; + private int mImagePixDepth; + private int mParent; + private int mAssociationType; + private int mAssociationDesc; + private int mSequenceNumber; + private String mName; + private long mDateCreated; + private long mDateModified; + private String mKeywords; + + // only instantiated via JNI + private MtpObjectInfo() { + } + + /** + * Returns the object handle for the MTP object + * + * @return the object handle + */ + public final int getObjectHandle() { + return mHandle; + } + + /** + * Returns the storage ID for the MTP object's storage unit + * + * @return the storage ID + */ + public final int getStorageId() { + return mStorageId; + } + + /** + * Returns the format code for the MTP object + * + * @return the format code + */ + public final int getFormat() { + return mFormat; + } + + /** + * Returns the protection status for the MTP object + * Possible values are: + * + * <ul> + * <li> {@link android.mtp.MtpConstants#PROTECTION_STATUS_NONE} + * <li> {@link android.mtp.MtpConstants#PROTECTION_STATUS_READ_ONLY} + * <li> {@link android.mtp.MtpConstants#PROTECTION_STATUS_NON_TRANSFERABLE_DATA} + * </ul> + * + * @return the protection status + */ + public final int getProtectionStatus() { + return mProtectionStatus; + } + + /** + * Returns the size of the MTP object + * + * @return the object size + */ + public final int getCompressedSize() { + return mCompressedSize; + } + + /** + * Returns the format code for the MTP object's thumbnail + * Will be zero for objects with no thumbnail + * + * @return the thumbnail format code + */ + public final int getThumbFormat() { + return mThumbFormat; + } + + /** + * Returns the size of the MTP object's thumbnail + * Will be zero for objects with no thumbnail + * + * @return the thumbnail size + */ + public final int getThumbCompressedSize() { + return mThumbCompressedSize; + } + + /** + * Returns the width of the MTP object's thumbnail in pixels + * Will be zero for objects with no thumbnail + * + * @return the thumbnail width + */ + public final int getThumbPixWidth() { + return mThumbPixWidth; + } + + /** + * Returns the height of the MTP object's thumbnail in pixels + * Will be zero for objects with no thumbnail + * + * @return the thumbnail height + */ + public final int getThumbPixHeight() { + return mThumbPixHeight; + } + + /** + * Returns the width of the MTP object in pixels + * Will be zero for non-image objects + * + * @return the image width + */ + public final int getImagePixWidth() { + return mImagePixWidth; + } + + /** + * Returns the height of the MTP object in pixels + * Will be zero for non-image objects + * + * @return the image height + */ + public final int getImagePixHeight() { + return mImagePixHeight; + } + + /** + * Returns the depth of the MTP object in bits per pixel + * Will be zero for non-image objects + * + * @return the image depth + */ + public final int getImagePixDepth() { + return mImagePixDepth; + } + + /** + * Returns the object handle for the object's parent + * Will be zero for the root directory of a storage unit + * + * @return the object's parent + */ + public final int getParent() { + return mParent; + } + + /** + * Returns the association type for the MTP object + * Will be zero objects that are not of format + * {@link android.mtp.MtpConstants#FORMAT_ASSOCIATION} + * For directories the association type is typically + * {@link android.mtp.MtpConstants#ASSOCIATION_TYPE_GENERIC_FOLDER} + * + * @return the object's association type + */ + public final int getAssociationType() { + return mAssociationType; + } + + /** + * Returns the association description for the MTP object + * Will be zero objects that are not of format + * {@link android.mtp.MtpConstants#FORMAT_ASSOCIATION} + * + * @return the object's association description + */ + public final int getAssociationDesc() { + return mAssociationDesc; + } + + /** + * Returns the sequence number for the MTP object + * This field is typically not used for MTP devices, + * but is sometimes used to define a sequence of photos + * on PTP cameras. + * + * @return the object's sequence number + */ + public final int getSequenceNumber() { + return mSequenceNumber; + } + + /** + * Returns the name of the MTP object + * + * @return the object's name + */ + public final String getName() { + return mName; + } + + /** + * Returns the creation date of the MTP object + * The value is represented as milliseconds since January 1, 1970 + * + * @return the object's creation date + */ + public final long getDateCreated() { + return mDateCreated; + } + + /** + * Returns the modification date of the MTP object + * The value is represented as milliseconds since January 1, 1970 + * + * @return the object's modification date + */ + public final long getDateModified() { + return mDateModified; + } + + /** + * Returns a comma separated list of keywords for the MTP object + * + * @return the object's keyword list + */ + public final String getKeywords() { + return mKeywords; + } +} diff --git a/media/java/android/mtp/MtpStorageInfo.java b/media/java/android/mtp/MtpStorageInfo.java new file mode 100644 index 0000000..811455a --- /dev/null +++ b/media/java/android/mtp/MtpStorageInfo.java @@ -0,0 +1,82 @@ +/* + * 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 android.mtp; + +/** + * This class encapsulates information about a storage unit on an MTP device. + * This corresponds to the StorageInfo Dataset described in + * section 5.2.2 of the MTP specification. + * + * {@hide} + */ +public final class MtpStorageInfo { + + private int mStorageId; + private long mMaxCapacity; + private long mFreeSpace; + private String mDescription; + private String mVolumeIdentifier; + + // only instantiated via JNI + private MtpStorageInfo() { + } + + /** + * Returns the storage ID for the storage unit + * + * @return the storage ID + */ + public final int getStorageId() { + return mStorageId; + } + + /** + * Returns the maximum storage capacity for the storage unit in bytes + * + * @return the maximum capacity + */ + public final long getMaxCapacity() { + return mMaxCapacity; + } + + /** + * Returns the amount of free space in the storage unit in bytes + * + * @return the amount of free space + */ + public final long getFreeSpace() { + return mFreeSpace; + } + + /** + * Returns the description string for the storage unit + * + * @return the storage unit description + */ + public final String getDescription() { + return mDescription; + } + + /** + * Returns the volume identifier for the storage unit + * + * @return the storage volume identifier + */ + public final String getVolumeIdentifier() { + return mVolumeIdentifier; + } +} diff --git a/media/jni/Android.mk b/media/jni/Android.mk index ab6e512..2a89a2a 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -10,6 +10,7 @@ LOCAL_SRC_FILES:= \ android_media_MediaProfiles.cpp \ android_media_AmrInputStream.cpp \ android_mtp_MtpDatabase.cpp \ + android_mtp_MtpDevice.cpp \ android_mtp_MtpServer.cpp \ LOCAL_SHARED_LIBRARIES := \ diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index f0609b2..0884e35 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -776,6 +776,7 @@ extern int register_android_media_ResampleInputStream(JNIEnv *env); extern int register_android_media_MediaProfiles(JNIEnv *env); extern int register_android_media_AmrInputStream(JNIEnv *env); extern int register_android_mtp_MtpDatabase(JNIEnv *env); +extern int register_android_mtp_MtpDevice(JNIEnv *env); extern int register_android_mtp_MtpServer(JNIEnv *env); jint JNI_OnLoad(JavaVM* vm, void* reserved) @@ -829,6 +830,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) goto bail; } + if (register_android_mtp_MtpDevice(env) < 0) { + LOGE("ERROR: MtpDevice native registration failed"); + goto bail; + } + if (register_android_mtp_MtpServer(env) < 0) { LOGE("ERROR: MtpServer native registration failed"); goto bail; diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp new file mode 100644 index 0000000..9e67985 --- /dev/null +++ b/media/jni/android_mtp_MtpDevice.cpp @@ -0,0 +1,663 @@ +/* + * 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. + */ + +// #define LOG_NDEBUG 0 + +#define LOG_TAG "MtpDeviceJNI" +#include "utils/Log.h" + +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" +#include "private/android_filesystem_config.h" + +#include "MtpTypes.h" +#include "MtpDevice.h" +#include "MtpDeviceInfo.h" +#include "MtpStorageInfo.h" +#include "MtpObjectInfo.h" + +using namespace android; + +// ---------------------------------------------------------------------------- + +static jfieldID field_context; + +jclass clazz_deviceInfo; +jclass clazz_storageInfo; +jclass clazz_objectInfo; + +jmethodID constructor_deviceInfo; +jmethodID constructor_storageInfo; +jmethodID constructor_objectInfo; + +// MtpDeviceInfo fields +static jfieldID field_deviceInfo_manufacturer; +static jfieldID field_deviceInfo_model; +static jfieldID field_deviceInfo_version; +static jfieldID field_deviceInfo_serialNumber; + +// MtpStorageInfo fields +static jfieldID field_storageInfo_storageId; +static jfieldID field_storageInfo_maxCapacity; +static jfieldID field_storageInfo_freeSpace; +static jfieldID field_storageInfo_description; +static jfieldID field_storageInfo_volumeIdentifier; + +// MtpObjectInfo fields +static jfieldID field_objectInfo_handle; +static jfieldID field_objectInfo_storageId; +static jfieldID field_objectInfo_format; +static jfieldID field_objectInfo_protectionStatus; +static jfieldID field_objectInfo_compressedSize; +static jfieldID field_objectInfo_thumbFormat; +static jfieldID field_objectInfo_thumbCompressedSize; +static jfieldID field_objectInfo_thumbPixWidth; +static jfieldID field_objectInfo_thumbPixHeight; +static jfieldID field_objectInfo_imagePixWidth; +static jfieldID field_objectInfo_imagePixHeight; +static jfieldID field_objectInfo_imagePixDepth; +static jfieldID field_objectInfo_parent; +static jfieldID field_objectInfo_associationType; +static jfieldID field_objectInfo_associationDesc; +static jfieldID field_objectInfo_sequenceNumber; +static jfieldID field_objectInfo_name; +static jfieldID field_objectInfo_dateCreated; +static jfieldID field_objectInfo_dateModified; +static jfieldID field_objectInfo_keywords; + +#ifdef HAVE_ANDROID_OS + +MtpDevice* get_device_from_object(JNIEnv* env, jobject javaDevice) +{ + return (MtpDevice*)env->GetIntField(javaDevice, field_context); +} + +static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { + if (env->ExceptionCheck()) { + LOGE("An exception was thrown by callback '%s'.", methodName); + LOGE_EX(env); + env->ExceptionClear(); + } +} + +#endif // HAVE_ANDROID_OS + +// ---------------------------------------------------------------------------- + +static jboolean +android_mtp_MtpDevice_open(JNIEnv *env, jobject thiz, jstring deviceName, jint fd) +{ +#ifdef HAVE_ANDROID_OS + LOGD("open\n"); + const char *deviceNameStr = env->GetStringUTFChars(deviceName, NULL); + MtpDevice* device = MtpDevice::open(deviceNameStr, fd); + env->ReleaseStringUTFChars(deviceName, deviceNameStr); + + if (device) + env->SetIntField(thiz, field_context, (int)device); + return (device != NULL); +#endif +} + +static void +android_mtp_MtpDevice_close(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + LOGD("close\n"); + MtpDevice* device = get_device_from_object(env, thiz); + if (device) { + device->close(); + delete device; + env->SetIntField(thiz, field_context, 0); + } +#endif +} + +static jobject +android_mtp_MtpDevice_get_device_info(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + MtpDevice* device = get_device_from_object(env, thiz); + if (!device) { + LOGD("android_mtp_MtpDevice_get_device_info device is null"); + return NULL; + } + MtpDeviceInfo* deviceInfo = device->getDeviceInfo(); + if (!deviceInfo) { + LOGD("android_mtp_MtpDevice_get_device_info deviceInfo is null"); + return NULL; + } + jobject info = env->NewObject(clazz_deviceInfo, constructor_deviceInfo); + if (info == NULL) { + LOGE("Could not create a MtpDeviceInfo object"); + delete deviceInfo; + return NULL; + } + + if (deviceInfo->mManufacturer) + env->SetObjectField(info, field_deviceInfo_manufacturer, + env->NewStringUTF(deviceInfo->mManufacturer)); + if (deviceInfo->mModel) + env->SetObjectField(info, field_deviceInfo_model, + env->NewStringUTF(deviceInfo->mModel)); + if (deviceInfo->mVersion) + env->SetObjectField(info, field_deviceInfo_version, + env->NewStringUTF(deviceInfo->mVersion)); + if (deviceInfo->mSerial) + env->SetObjectField(info, field_deviceInfo_serialNumber, + env->NewStringUTF(deviceInfo->mSerial)); + + delete deviceInfo; + return info; +#else + return NULL; +#endif +} + +static jintArray +android_mtp_MtpDevice_get_storage_ids(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + MtpDevice* device = get_device_from_object(env, thiz); + if (!device) + return NULL; + MtpStorageIDList* storageIDs = device->getStorageIDs(); + if (!storageIDs) + return NULL; + + int length = storageIDs->size(); + jintArray array = env->NewIntArray(length); + // FIXME is this cast safe? + env->SetIntArrayRegion(array, 0, length, (const jint *)storageIDs->array()); + + delete storageIDs; + return array; +#else + return NULL; +#endif +} + +static jobject +android_mtp_MtpDevice_get_storage_info(JNIEnv *env, jobject thiz, jint storageID) +{ +#ifdef HAVE_ANDROID_OS + MtpDevice* device = get_device_from_object(env, thiz); + if (!device) + return NULL; + MtpStorageInfo* storageInfo = device->getStorageInfo(storageID); + if (!storageInfo) + return NULL; + + jobject info = env->NewObject(clazz_storageInfo, constructor_storageInfo); + if (info == NULL) { + LOGE("Could not create a MtpStorageInfo object"); + delete storageInfo; + return NULL; + } + + if (storageInfo->mStorageID) + env->SetIntField(info, field_storageInfo_storageId, storageInfo->mStorageID); + if (storageInfo->mMaxCapacity) + env->SetLongField(info, field_storageInfo_maxCapacity, storageInfo->mMaxCapacity); + if (storageInfo->mFreeSpaceBytes) + env->SetLongField(info, field_storageInfo_freeSpace, storageInfo->mFreeSpaceBytes); + if (storageInfo->mStorageDescription) + env->SetObjectField(info, field_storageInfo_description, + env->NewStringUTF(storageInfo->mStorageDescription)); + if (storageInfo->mVolumeIdentifier) + env->SetObjectField(info, field_storageInfo_volumeIdentifier, + env->NewStringUTF(storageInfo->mVolumeIdentifier)); + + delete storageInfo; + return info; +#else + return NULL; +#endif +} + +static jintArray +android_mtp_MtpDevice_get_object_handles(JNIEnv *env, jobject thiz, + jint storageID, jint format, jint objectID) +{ +#ifdef HAVE_ANDROID_OS + MtpDevice* device = get_device_from_object(env, thiz); + if (!device) + return NULL; + MtpObjectHandleList* handles = device->getObjectHandles(storageID, format, objectID); + if (!handles) + return NULL; + + int length = handles->size(); + jintArray array = env->NewIntArray(length); + // FIXME is this cast safe? + env->SetIntArrayRegion(array, 0, length, (const jint *)handles->array()); + + delete handles; + return array; +#else + return NULL; +#endif +} + +static jobject +android_mtp_MtpDevice_get_object_info(JNIEnv *env, jobject thiz, jint objectID) +{ +#ifdef HAVE_ANDROID_OS + MtpDevice* device = get_device_from_object(env, thiz); + if (!device) + return NULL; + MtpObjectInfo* objectInfo = device->getObjectInfo(objectID); + if (!objectInfo) + return NULL; + jobject info = env->NewObject(clazz_objectInfo, constructor_objectInfo); + if (info == NULL) { + LOGE("Could not create a MtpObjectInfo object"); + delete objectInfo; + return NULL; + } + + if (objectInfo->mHandle) + env->SetIntField(info, field_objectInfo_handle, objectInfo->mHandle); + if (objectInfo->mStorageID) + env->SetIntField(info, field_objectInfo_storageId, objectInfo->mStorageID); + if (objectInfo->mFormat) + env->SetIntField(info, field_objectInfo_format, objectInfo->mFormat); + if (objectInfo->mProtectionStatus) + env->SetIntField(info, field_objectInfo_protectionStatus, objectInfo->mProtectionStatus); + if (objectInfo->mCompressedSize) + env->SetIntField(info, field_objectInfo_compressedSize, objectInfo->mCompressedSize); + if (objectInfo->mThumbFormat) + env->SetIntField(info, field_objectInfo_thumbFormat, objectInfo->mThumbFormat); + if (objectInfo->mThumbCompressedSize) + env->SetIntField(info, field_objectInfo_thumbCompressedSize, objectInfo->mThumbCompressedSize); + if (objectInfo->mThumbPixWidth) + env->SetIntField(info, field_objectInfo_thumbPixWidth, objectInfo->mThumbPixWidth); + if (objectInfo->mThumbPixHeight) + env->SetIntField(info, field_objectInfo_thumbPixHeight, objectInfo->mThumbPixHeight); + if (objectInfo->mImagePixWidth) + env->SetIntField(info, field_objectInfo_imagePixWidth, objectInfo->mImagePixWidth); + if (objectInfo->mImagePixHeight) + env->SetIntField(info, field_objectInfo_imagePixHeight, objectInfo->mImagePixHeight); + if (objectInfo->mImagePixDepth) + env->SetIntField(info, field_objectInfo_imagePixDepth, objectInfo->mImagePixDepth); + if (objectInfo->mParent) + env->SetIntField(info, field_objectInfo_parent, objectInfo->mParent); + if (objectInfo->mAssociationType) + env->SetIntField(info, field_objectInfo_associationType, objectInfo->mAssociationType); + if (objectInfo->mAssociationDesc) + env->SetIntField(info, field_objectInfo_associationDesc, objectInfo->mAssociationDesc); + if (objectInfo->mSequenceNumber) + env->SetIntField(info, field_objectInfo_sequenceNumber, objectInfo->mSequenceNumber); + if (objectInfo->mName) + env->SetObjectField(info, field_objectInfo_name, env->NewStringUTF(objectInfo->mName)); + if (objectInfo->mDateCreated) + env->SetLongField(info, field_objectInfo_dateCreated, objectInfo->mDateCreated); + if (objectInfo->mDateModified) + env->SetLongField(info, field_objectInfo_dateModified, objectInfo->mDateModified); + if (objectInfo->mKeywords) + env->SetObjectField(info, field_objectInfo_keywords, + env->NewStringUTF(objectInfo->mKeywords)); + + delete objectInfo; + return info; +#else + return NULL; +#endif +} + +struct get_object_callback_data { + JNIEnv *env; + jbyteArray array; +}; + +static bool get_object_callback(void* data, int offset, int length, void* clientData) +{ + get_object_callback_data* cbData = (get_object_callback_data *)clientData; + cbData->env->SetByteArrayRegion(cbData->array, offset, length, (jbyte *)data); + return true; +} + +static jbyteArray +android_mtp_MtpDevice_get_object(JNIEnv *env, jobject thiz, jint objectID, jint objectSize) +{ +#ifdef HAVE_ANDROID_OS + MtpDevice* device = get_device_from_object(env, thiz); + if (!device) + return NULL; + + jbyteArray array = env->NewByteArray(objectSize); + if (!array) { + jniThrowException(env, "java/lang/OutOfMemoryError", NULL); + return NULL; + } + + get_object_callback_data data; + data.env = env; + data.array = array; + + if (device->readObject(objectID, get_object_callback, objectSize, &data)) + return array; +#endif + return NULL; +} + +static jbyteArray +android_mtp_MtpDevice_get_thumbnail(JNIEnv *env, jobject thiz, jint objectID) +{ +#ifdef HAVE_ANDROID_OS + MtpDevice* device = get_device_from_object(env, thiz); + if (!device) + return NULL; + + int length; + void* thumbnail = device->getThumbnail(objectID, length); + if (! thumbnail) + return NULL; + jbyteArray array = env->NewByteArray(length); + env->SetByteArrayRegion(array, 0, length, (const jbyte *)thumbnail); + + free(thumbnail); + return array; +#else + return NULL; +#endif +} + +static jboolean +android_mtp_MtpDevice_delete_object(JNIEnv *env, jobject thiz, jint object_id) +{ +#ifdef HAVE_ANDROID_OS + MtpDevice* device = get_device_from_object(env, thiz); + if (device) + return device->deleteObject(object_id); + else + #endif + return NULL; +} + +static jlong +android_mtp_MtpDevice_get_parent(JNIEnv *env, jobject thiz, jint object_id) +{ +#ifdef HAVE_ANDROID_OS + MtpDevice* device = get_device_from_object(env, thiz); + if (device) + return device->getParent(object_id); + else +#endif + return -1; +} + +static jlong +android_mtp_MtpDevice_get_storage_id(JNIEnv *env, jobject thiz, jint object_id) +{ + #ifdef HAVE_ANDROID_OS + MtpDevice* device = get_device_from_object(env, thiz); + if (device) + return device->getStorageID(object_id); + else +#endif + return -1; +} + +static jboolean +android_mtp_MtpDevice_import_file(JNIEnv *env, jobject thiz, jint object_id, jstring dest_path) +{ +#ifdef HAVE_ANDROID_OS + MtpDevice* device = get_device_from_object(env, thiz); + if (device) { + const char *destPathStr = env->GetStringUTFChars(dest_path, NULL); + bool result = device->readObject(object_id, destPathStr, AID_SDCARD_RW, 0664); + env->ReleaseStringUTFChars(dest_path, destPathStr); + return result; + } +#endif + return NULL; +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"native_open", "(Ljava/lang/String;I)Z", + (void *)android_mtp_MtpDevice_open}, + {"native_close", "()V", (void *)android_mtp_MtpDevice_close}, + {"native_get_device_info", "()Landroid/mtp/MtpDeviceInfo;", + (void *)android_mtp_MtpDevice_get_device_info}, + {"native_get_storage_ids", "()[I", (void *)android_mtp_MtpDevice_get_storage_ids}, + {"native_get_storage_info", "(I)Landroid/mtp/MtpStorageInfo;", + (void *)android_mtp_MtpDevice_get_storage_info}, + {"native_get_object_handles","(III)[I", + (void *)android_mtp_MtpDevice_get_object_handles}, + {"native_get_object_info", "(I)Landroid/mtp/MtpObjectInfo;", + (void *)android_mtp_MtpDevice_get_object_info}, + {"native_get_object", "(II)[B",(void *)android_mtp_MtpDevice_get_object}, + {"native_get_thumbnail", "(I)[B",(void *)android_mtp_MtpDevice_get_thumbnail}, + {"native_delete_object", "(I)Z", (void *)android_mtp_MtpDevice_delete_object}, + {"native_get_parent", "(I)J", (void *)android_mtp_MtpDevice_get_parent}, + {"native_get_storage_id", "(I)J", (void *)android_mtp_MtpDevice_get_storage_id}, + {"native_import_file", "(ILjava/lang/String;)Z", + (void *)android_mtp_MtpDevice_import_file}, +}; + +static const char* const kClassPathName = "android/mtp/MtpDevice"; + +int register_android_mtp_MtpDevice(JNIEnv *env) +{ + jclass clazz; + + LOGD("register_android_mtp_MtpDevice\n"); + + clazz = env->FindClass("android/mtp/MtpDeviceInfo"); + if (clazz == NULL) { + LOGE("Can't find android/mtp/MtpDeviceInfo"); + return -1; + } + constructor_deviceInfo = env->GetMethodID(clazz, "<init>", "()V"); + if (constructor_deviceInfo == NULL) { + LOGE("Can't find android/mtp/MtpDeviceInfo constructor"); + return -1; + } + field_deviceInfo_manufacturer = env->GetFieldID(clazz, "mManufacturer", "Ljava/lang/String;"); + if (field_deviceInfo_manufacturer == NULL) { + LOGE("Can't find MtpDeviceInfo.mManufacturer"); + return -1; + } + field_deviceInfo_model = env->GetFieldID(clazz, "mModel", "Ljava/lang/String;"); + if (field_deviceInfo_model == NULL) { + LOGE("Can't find MtpDeviceInfo.mModel"); + return -1; + } + field_deviceInfo_version = env->GetFieldID(clazz, "mVersion", "Ljava/lang/String;"); + if (field_deviceInfo_version == NULL) { + LOGE("Can't find MtpDeviceInfo.mVersion"); + return -1; + } + field_deviceInfo_serialNumber = env->GetFieldID(clazz, "mSerialNumber", "Ljava/lang/String;"); + if (field_deviceInfo_serialNumber == NULL) { + LOGE("Can't find MtpDeviceInfo.mSerialNumber"); + return -1; + } + clazz_deviceInfo = clazz; + + clazz = env->FindClass("android/mtp/MtpStorageInfo"); + if (clazz == NULL) { + LOGE("Can't find android/mtp/MtpStorageInfo"); + return -1; + } + constructor_storageInfo = env->GetMethodID(clazz, "<init>", "()V"); + if (constructor_storageInfo == NULL) { + LOGE("Can't find android/mtp/MtpStorageInfo constructor"); + return -1; + } + field_storageInfo_storageId = env->GetFieldID(clazz, "mStorageId", "I"); + if (field_storageInfo_storageId == NULL) { + LOGE("Can't find MtpStorageInfo.mStorageId"); + return -1; + } + field_storageInfo_maxCapacity = env->GetFieldID(clazz, "mMaxCapacity", "J"); + if (field_storageInfo_maxCapacity == NULL) { + LOGE("Can't find MtpStorageInfo.mMaxCapacity"); + return -1; + } + field_storageInfo_freeSpace = env->GetFieldID(clazz, "mFreeSpace", "J"); + if (field_storageInfo_freeSpace == NULL) { + LOGE("Can't find MtpStorageInfo.mFreeSpace"); + return -1; + } + field_storageInfo_description = env->GetFieldID(clazz, "mDescription", "Ljava/lang/String;"); + if (field_storageInfo_description == NULL) { + LOGE("Can't find MtpStorageInfo.mDescription"); + return -1; + } + field_storageInfo_volumeIdentifier = env->GetFieldID(clazz, "mVolumeIdentifier", "Ljava/lang/String;"); + if (field_storageInfo_volumeIdentifier == NULL) { + LOGE("Can't find MtpStorageInfo.mVolumeIdentifier"); + return -1; + } + clazz_storageInfo = clazz; + + clazz = env->FindClass("android/mtp/MtpObjectInfo"); + if (clazz == NULL) { + LOGE("Can't find android/mtp/MtpObjectInfo"); + return -1; + } + constructor_objectInfo = env->GetMethodID(clazz, "<init>", "()V"); + if (constructor_objectInfo == NULL) { + LOGE("Can't find android/mtp/MtpObjectInfo constructor"); + return -1; + } + field_objectInfo_handle = env->GetFieldID(clazz, "mHandle", "I"); + if (field_objectInfo_handle == NULL) { + LOGE("Can't find MtpObjectInfo.mHandle"); + return -1; + } + field_objectInfo_storageId = env->GetFieldID(clazz, "mStorageId", "I"); + if (field_objectInfo_storageId == NULL) { + LOGE("Can't find MtpObjectInfo.mStorageId"); + return -1; + } + field_objectInfo_format = env->GetFieldID(clazz, "mFormat", "I"); + if (field_objectInfo_format == NULL) { + LOGE("Can't find MtpObjectInfo.mFormat"); + return -1; + } + field_objectInfo_protectionStatus = env->GetFieldID(clazz, "mProtectionStatus", "I"); + if (field_objectInfo_protectionStatus == NULL) { + LOGE("Can't find MtpObjectInfo.mProtectionStatus"); + return -1; + } + field_objectInfo_compressedSize = env->GetFieldID(clazz, "mCompressedSize", "I"); + if (field_objectInfo_compressedSize == NULL) { + LOGE("Can't find MtpObjectInfo.mCompressedSize"); + return -1; + } + field_objectInfo_thumbFormat = env->GetFieldID(clazz, "mThumbFormat", "I"); + if (field_objectInfo_thumbFormat == NULL) { + LOGE("Can't find MtpObjectInfo.mThumbFormat"); + return -1; + } + field_objectInfo_thumbCompressedSize = env->GetFieldID(clazz, "mThumbCompressedSize", "I"); + if (field_objectInfo_thumbCompressedSize == NULL) { + LOGE("Can't find MtpObjectInfo.mThumbCompressedSize"); + return -1; + } + field_objectInfo_thumbPixWidth = env->GetFieldID(clazz, "mThumbPixWidth", "I"); + if (field_objectInfo_thumbPixWidth == NULL) { + LOGE("Can't find MtpObjectInfo.mThumbPixWidth"); + return -1; + } + field_objectInfo_thumbPixHeight = env->GetFieldID(clazz, "mThumbPixHeight", "I"); + if (field_objectInfo_thumbPixHeight == NULL) { + LOGE("Can't find MtpObjectInfo.mThumbPixHeight"); + return -1; + } + field_objectInfo_imagePixWidth = env->GetFieldID(clazz, "mImagePixWidth", "I"); + if (field_objectInfo_imagePixWidth == NULL) { + LOGE("Can't find MtpObjectInfo.mImagePixWidth"); + return -1; + } + field_objectInfo_imagePixHeight = env->GetFieldID(clazz, "mImagePixHeight", "I"); + if (field_objectInfo_imagePixHeight == NULL) { + LOGE("Can't find MtpObjectInfo.mImagePixHeight"); + return -1; + } + field_objectInfo_imagePixDepth = env->GetFieldID(clazz, "mImagePixDepth", "I"); + if (field_objectInfo_imagePixDepth == NULL) { + LOGE("Can't find MtpObjectInfo.mImagePixDepth"); + return -1; + } + field_objectInfo_parent = env->GetFieldID(clazz, "mParent", "I"); + if (field_objectInfo_parent == NULL) { + LOGE("Can't find MtpObjectInfo.mParent"); + return -1; + } + field_objectInfo_associationType = env->GetFieldID(clazz, "mAssociationType", "I"); + if (field_objectInfo_associationType == NULL) { + LOGE("Can't find MtpObjectInfo.mAssociationType"); + return -1; + } + field_objectInfo_associationDesc = env->GetFieldID(clazz, "mAssociationDesc", "I"); + if (field_objectInfo_associationDesc == NULL) { + LOGE("Can't find MtpObjectInfo.mAssociationDesc"); + return -1; + } + field_objectInfo_sequenceNumber = env->GetFieldID(clazz, "mSequenceNumber", "I"); + if (field_objectInfo_sequenceNumber == NULL) { + LOGE("Can't find MtpObjectInfo.mSequenceNumber"); + return -1; + } + field_objectInfo_name = env->GetFieldID(clazz, "mName", "Ljava/lang/String;"); + if (field_objectInfo_name == NULL) { + LOGE("Can't find MtpObjectInfo.mName"); + return -1; + } + field_objectInfo_dateCreated = env->GetFieldID(clazz, "mDateCreated", "J"); + if (field_objectInfo_dateCreated == NULL) { + LOGE("Can't find MtpObjectInfo.mDateCreated"); + return -1; + } + field_objectInfo_dateModified = env->GetFieldID(clazz, "mDateModified", "J"); + if (field_objectInfo_dateModified == NULL) { + LOGE("Can't find MtpObjectInfo.mDateModified"); + return -1; + } + field_objectInfo_keywords = env->GetFieldID(clazz, "mKeywords", "Ljava/lang/String;"); + if (field_objectInfo_keywords == NULL) { + LOGE("Can't find MtpObjectInfo.mKeywords"); + return -1; + } + clazz_objectInfo = clazz; + + clazz = env->FindClass("android/mtp/MtpDevice"); + if (clazz == NULL) { + LOGE("Can't find android/mtp/MtpDevice"); + return -1; + } + field_context = env->GetFieldID(clazz, "mNativeContext", "I"); + if (field_context == NULL) { + LOGE("Can't find MtpDevice.mNativeContext"); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, + "android/mtp/MtpDevice", gMethods, NELEM(gMethods)); +} diff --git a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp index 26c5aca..9097e20 100755 --- a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp +++ b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp @@ -166,7 +166,7 @@ enum { REVERB_VOLUME_RAMP, }; -#define REVERB_DEFAULT_PRESET REVERB_PRESET_MEDIUMROOM +#define REVERB_DEFAULT_PRESET REVERB_PRESET_NONE #define REVERB_SEND_LEVEL (0x0C00) // 0.75 in 4.12 format diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp index dfb4e00..505d9d4 100644 --- a/media/libstagefright/ACodec.cpp +++ b/media/libstagefright/ACodec.cpp @@ -197,6 +197,9 @@ struct ACodec::ExecutingState : public ACodec::BaseState { // to fill with data. void resume(); + // Returns true iff input and output buffers are in play. + bool active() const { return mActive; } + protected: virtual PortMode getPortMode(OMX_U32 portIndex); virtual bool onMessageReceived(const sp<AMessage> &msg); @@ -205,6 +208,8 @@ protected: virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); private: + bool mActive; + DISALLOW_EVIL_CONSTRUCTORS(ExecutingState); }; @@ -564,13 +569,17 @@ status_t ACodec::freeBuffersOnPort(OMX_U32 portIndex) { return OK; } -status_t ACodec::freeOutputBuffersOwnedByNativeWindow() { +status_t ACodec::freeOutputBuffersNotOwnedByComponent() { for (size_t i = mBuffers[kPortIndexOutput].size(); i-- > 0;) { BufferInfo *info = &mBuffers[kPortIndexOutput].editItemAt(i); - if (info->mStatus == - BufferInfo::OWNED_BY_NATIVE_WINDOW) { + if (info->mStatus != + BufferInfo::OWNED_BY_COMPONENT) { + // We shouldn't have sent out any buffers to the client at this + // point. + CHECK_NE((int)info->mStatus, (int)BufferInfo::OWNED_BY_DOWNSTREAM); + CHECK_EQ((status_t)OK, freeBuffer(kPortIndexOutput, i)); } } @@ -1195,6 +1204,9 @@ bool ACodec::BaseState::onOMXEvent( } bool ACodec::BaseState::onOMXEmptyBufferDone(IOMX::buffer_id bufferID) { + LOGV("[%s] onOMXEmptyBufferDone %p", + mCodec->mComponentName.c_str(), bufferID); + BufferInfo *info = mCodec->findBufferByID(kPortIndexInput, bufferID); @@ -1295,7 +1307,7 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) { } if (buffer != info->mData) { - if (!(flags & OMX_BUFFERFLAG_CODECCONFIG)) { + if (0 && !(flags & OMX_BUFFERFLAG_CODECCONFIG)) { LOGV("[%s] Needs to copy input data.", mCodec->mComponentName.c_str()); } @@ -1304,6 +1316,9 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) { memcpy(info->mData->data(), buffer->data(), buffer->size()); } + LOGV("[%s] calling emptyBuffer %p", + mCodec->mComponentName.c_str(), bufferID); + CHECK_EQ(mCodec->mOMX->emptyBuffer( mCodec->mNode, bufferID, @@ -1320,6 +1335,9 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) { LOGV("[%s] Signalling EOS on the input port", mCodec->mComponentName.c_str()); + LOGV("[%s] calling emptyBuffer %p", + mCodec->mComponentName.c_str(), bufferID); + CHECK_EQ(mCodec->mOMX->emptyBuffer( mCodec->mNode, bufferID, @@ -1378,6 +1396,9 @@ bool ACodec::BaseState::onOMXFillBufferDone( int64_t timeUs, void *platformPrivate, void *dataPtr) { + LOGV("[%s] onOMXFillBufferDone %p", + mCodec->mComponentName.c_str(), bufferID); + ssize_t index; BufferInfo *info = mCodec->findBufferByID(kPortIndexOutput, bufferID, &index); @@ -1396,6 +1417,9 @@ bool ACodec::BaseState::onOMXFillBufferDone( { if (rangeLength == 0) { if (!(flags & OMX_BUFFERFLAG_EOS)) { + LOGV("[%s] calling fillBuffer %p", + mCodec->mComponentName.c_str(), info->mBufferID); + CHECK_EQ(mCodec->mOMX->fillBuffer( mCodec->mNode, info->mBufferID), (status_t)OK); @@ -1503,6 +1527,9 @@ void ACodec::BaseState::onOutputBufferDrained(const sp<AMessage> &msg) { info = mCodec->dequeueBufferFromNativeWindow(); } + LOGV("[%s] calling fillBuffer %p", + mCodec->mComponentName.c_str(), info->mBufferID); + CHECK_EQ(mCodec->mOMX->fillBuffer(mCodec->mNode, info->mBufferID), (status_t)OK); @@ -1600,6 +1627,9 @@ void ACodec::UninitializedState::onSetup( mCodec->mOMX = omx; mCodec->mNode = node; + mCodec->mPortEOS[kPortIndexInput] = + mCodec->mPortEOS[kPortIndexOutput] = false; + mCodec->configureCodec(mime.c_str(), msg); sp<RefBase> obj; @@ -1717,7 +1747,8 @@ bool ACodec::IdleToExecutingState::onOMXEvent( //////////////////////////////////////////////////////////////////////////////// ACodec::ExecutingState::ExecutingState(ACodec *codec) - : BaseState(codec) { + : BaseState(codec), + mActive(false) { } ACodec::BaseState::PortMode ACodec::ExecutingState::getPortMode( @@ -1745,6 +1776,9 @@ void ACodec::ExecutingState::submitOutputBuffers() { CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US); } + LOGV("[%s] calling fillBuffer %p", + mCodec->mComponentName.c_str(), info->mBufferID); + CHECK_EQ(mCodec->mOMX->fillBuffer(mCodec->mNode, info->mBufferID), (status_t)OK); @@ -1753,6 +1787,13 @@ void ACodec::ExecutingState::submitOutputBuffers() { } void ACodec::ExecutingState::resume() { + if (mActive) { + LOGV("[%s] We're already active, no need to resume.", + mCodec->mComponentName.c_str()); + + return; + } + submitOutputBuffers(); // Post the first input buffer. @@ -1760,6 +1801,8 @@ void ACodec::ExecutingState::resume() { BufferInfo *info = &mCodec->mBuffers[kPortIndexInput].editItemAt(0); postFillThisBuffer(info); + + mActive = true; } void ACodec::ExecutingState::stateEntered() { @@ -1774,6 +1817,8 @@ bool ACodec::ExecutingState::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { case kWhatShutdown: { + mActive = false; + CHECK_EQ(mCodec->mOMX->sendCommand( mCodec->mNode, OMX_CommandStateSet, OMX_StateIdle), (status_t)OK); @@ -1786,6 +1831,8 @@ bool ACodec::ExecutingState::onMessageReceived(const sp<AMessage> &msg) { case kWhatFlush: { + mActive = false; + CHECK_EQ(mCodec->mOMX->sendCommand( mCodec->mNode, OMX_CommandFlush, OMX_ALL), (status_t)OK); @@ -1825,10 +1872,7 @@ bool ACodec::ExecutingState::onOMXEvent( OMX_CommandPortDisable, kPortIndexOutput), (status_t)OK); - if (mCodec->mNativeWindow != NULL) { - CHECK_EQ((status_t)OK, - mCodec->freeOutputBuffersOwnedByNativeWindow()); - } + mCodec->freeOutputBuffersNotOwnedByComponent(); mCodec->changeState(mCodec->mOutputPortSettingsChangedState); } else if (data2 == OMX_IndexConfigCommonOutputCrop) { @@ -1876,7 +1920,12 @@ bool ACodec::OutputPortSettingsChangedState::onMessageReceived( switch (msg->what()) { case kWhatFlush: case kWhatShutdown: + case kWhatResume: { + if (msg->what() == kWhatResume) { + LOGV("[%s] Deferring resume", mCodec->mComponentName.c_str()); + } + mCodec->deferMessage(msg); handled = true; break; @@ -1925,7 +1974,10 @@ bool ACodec::OutputPortSettingsChangedState::onOMXEvent( LOGV("[%s] Output port now reenabled.", mCodec->mComponentName.c_str()); - mCodec->mExecutingState->submitOutputBuffers(); + if (mCodec->mExecutingState->active()) { + mCodec->mExecutingState->submitOutputBuffers(); + } + mCodec->changeState(mCodec->mExecutingState); return true; @@ -1992,6 +2044,13 @@ bool ACodec::ExecutingToIdleState::onOMXEvent( return true; } + case OMX_EventPortSettingsChanged: + case OMX_EventBufferFlag: + { + // We're shutting down and don't care about this anymore. + return true; + } + default: return BaseState::onOMXEvent(event, data1, data2); } @@ -2170,6 +2229,23 @@ bool ACodec::FlushingState::onOMXEvent( return true; } + case OMX_EventPortSettingsChanged: + { + sp<AMessage> msg = new AMessage(kWhatOMXMessage, mCodec->id()); + msg->setInt32("type", omx_message::EVENT); + msg->setPointer("node", mCodec->mNode); + msg->setInt32("event", event); + msg->setInt32("data1", data1); + msg->setInt32("data2", data2); + + LOGV("[%s] Deferring OMX_EventPortSettingsChanged", + mCodec->mComponentName.c_str()); + + mCodec->deferMessage(msg); + + return true; + } + default: return BaseState::onOMXEvent(event, data1, data2); } diff --git a/media/libstagefright/AMRExtractor.cpp b/media/libstagefright/AMRExtractor.cpp index ac87c29..7eca5e4 100644 --- a/media/libstagefright/AMRExtractor.cpp +++ b/media/libstagefright/AMRExtractor.cpp @@ -35,8 +35,9 @@ class AMRSource : public MediaSource { public: AMRSource(const sp<DataSource> &source, const sp<MetaData> &meta, - size_t frameSize, - bool isWide); + bool isWide, + const off64_t *offset_table, + size_t offset_table_length); virtual status_t start(MetaData *params = NULL); virtual status_t stop(); @@ -52,7 +53,6 @@ protected: private: sp<DataSource> mDataSource; sp<MetaData> mMeta; - size_t mFrameSize; bool mIsWide; off64_t mOffset; @@ -60,6 +60,9 @@ private: bool mStarted; MediaBufferGroup *mGroup; + off64_t mOffsetTable[OFFSET_TABLE_LEN]; + size_t mOffsetTableLength; + AMRSource(const AMRSource &); AMRSource &operator=(const AMRSource &); }; @@ -67,13 +70,25 @@ private: //////////////////////////////////////////////////////////////////////////////// static size_t getFrameSize(bool isWide, unsigned FT) { - static const size_t kFrameSizeNB[8] = { - 95, 103, 118, 134, 148, 159, 204, 244 + static const size_t kFrameSizeNB[16] = { + 95, 103, 118, 134, 148, 159, 204, 244, + 39, 43, 38, 37, // SID + 0, 0, 0, // future use + 0 // no data }; - static const size_t kFrameSizeWB[9] = { - 132, 177, 253, 285, 317, 365, 397, 461, 477 + static const size_t kFrameSizeWB[16] = { + 132, 177, 253, 285, 317, 365, 397, 461, 477, + 40, // SID + 0, 0, 0, 0, // future use + 0, // speech lost + 0 // no data }; + if (FT > 15 || (isWide && FT > 9 && FT < 14) || (!isWide && FT > 11 && FT < 15)) { + LOGE("illegal AMR frame type %d", FT); + return 0; + } + size_t frameSize = isWide ? kFrameSizeWB[FT] : kFrameSizeNB[FT]; // Round up bits to bytes and add 1 for the header byte. @@ -82,9 +97,26 @@ static size_t getFrameSize(bool isWide, unsigned FT) { return frameSize; } +static status_t getFrameSizeByOffset(const sp<DataSource> &source, + off64_t offset, bool isWide, size_t *frameSize) { + uint8_t header; + if (source->readAt(offset, &header, 1) < 1) { + return ERROR_IO; + } + + unsigned FT = (header >> 3) & 0x0f; + + *frameSize = getFrameSize(isWide, FT); + if (*frameSize == 0) { + return ERROR_MALFORMED; + } + return OK; +} + AMRExtractor::AMRExtractor(const sp<DataSource> &source) : mDataSource(source), - mInitCheck(NO_INIT) { + mInitCheck(NO_INIT), + mOffsetTableLength(0) { String8 mimeType; float confidence; if (!SniffAMR(mDataSource, &mimeType, &confidence, NULL)) { @@ -101,25 +133,29 @@ AMRExtractor::AMRExtractor(const sp<DataSource> &source) mMeta->setInt32(kKeyChannelCount, 1); mMeta->setInt32(kKeySampleRate, mIsWide ? 16000 : 8000); - size_t offset = mIsWide ? 9 : 6; - uint8_t header; - if (mDataSource->readAt(offset, &header, 1) != 1) { - return; - } - - unsigned FT = (header >> 3) & 0x0f; - - if (FT > 8 || (!mIsWide && FT > 7)) { - return; - } - - mFrameSize = getFrameSize(mIsWide, FT); - + off64_t offset = mIsWide ? 9 : 6; off64_t streamSize; - if (mDataSource->getSize(&streamSize) == OK) { - off64_t numFrames = streamSize / mFrameSize; + size_t frameSize, numFrames = 0; + int64_t duration = 0; - mMeta->setInt64(kKeyDuration, 20000ll * numFrames); + if (mDataSource->getSize(&streamSize) == OK) { + while (offset < streamSize) { + if (getFrameSizeByOffset(source, offset, mIsWide, &frameSize) != OK) { + return; + } + + if ((numFrames % 50 == 0) && (numFrames / 50 < OFFSET_TABLE_LEN)) { + CHECK_EQ(mOffsetTableLength, numFrames / 50); + mOffsetTable[mOffsetTableLength] = offset - (mIsWide ? 9: 6); + mOffsetTableLength ++; + } + + offset += frameSize; + duration += 20000; // Each frame is 20ms + numFrames ++; + } + + mMeta->setInt64(kKeyDuration, duration); } mInitCheck = OK; @@ -149,7 +185,8 @@ sp<MediaSource> AMRExtractor::getTrack(size_t index) { return NULL; } - return new AMRSource(mDataSource, mMeta, mFrameSize, mIsWide); + return new AMRSource(mDataSource, mMeta, mIsWide, + mOffsetTable, mOffsetTableLength); } sp<MetaData> AMRExtractor::getTrackMetaData(size_t index, uint32_t flags) { @@ -164,15 +201,18 @@ sp<MetaData> AMRExtractor::getTrackMetaData(size_t index, uint32_t flags) { AMRSource::AMRSource( const sp<DataSource> &source, const sp<MetaData> &meta, - size_t frameSize, bool isWide) + bool isWide, const off64_t *offset_table, size_t offset_table_length) : mDataSource(source), mMeta(meta), - mFrameSize(frameSize), mIsWide(isWide), mOffset(mIsWide ? 9 : 6), mCurrentTimeUs(0), mStarted(false), - mGroup(NULL) { + mGroup(NULL), + mOffsetTableLength(offset_table_length) { + if (mOffsetTableLength > 0 && mOffsetTableLength <= OFFSET_TABLE_LEN) { + memcpy ((char*)mOffsetTable, (char*)offset_table, sizeof(off64_t) * mOffsetTableLength); + } } AMRSource::~AMRSource() { @@ -214,9 +254,25 @@ status_t AMRSource::read( int64_t seekTimeUs; ReadOptions::SeekMode mode; if (options && options->getSeekTo(&seekTimeUs, &mode)) { + size_t size; int64_t seekFrame = seekTimeUs / 20000ll; // 20ms per frame. mCurrentTimeUs = seekFrame * 20000ll; - mOffset = seekFrame * mFrameSize + (mIsWide ? 9 : 6); + + int index = seekFrame / 50; + if (index >= mOffsetTableLength) { + index = mOffsetTableLength - 1; + } + + mOffset = mOffsetTable[index] + (mIsWide ? 9 : 6); + + for (int i = 0; i< seekFrame - index * 50; i++) { + status_t err; + if ((err = getFrameSizeByOffset(mDataSource, mOffset, + mIsWide, &size)) != OK) { + return err; + } + mOffset += size; + } } uint8_t header; @@ -236,16 +292,11 @@ status_t AMRSource::read( unsigned FT = (header >> 3) & 0x0f; - if (FT > 8 || (!mIsWide && FT > 7)) { - - LOGE("illegal AMR frame type %d", FT); - + size_t frameSize = getFrameSize(mIsWide, FT); + if (frameSize == 0) { return ERROR_MALFORMED; } - size_t frameSize = getFrameSize(mIsWide, FT); - CHECK_EQ(frameSize, mFrameSize); - MediaBuffer *buffer; status_t err = mGroup->acquire_buffer(&buffer); if (err != OK) { diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 2d486e3..029b238 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -17,6 +17,7 @@ LOCAL_SRC_FILES:= \ DRMExtractor.cpp \ ESDS.cpp \ FileSource.cpp \ + FLACExtractor.cpp \ HTTPStream.cpp \ JPEGSource.cpp \ MP3Extractor.cpp \ @@ -54,6 +55,7 @@ LOCAL_SRC_FILES:= \ LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ $(TOP)/frameworks/base/include/media/stagefright/openmax \ + $(TOP)/external/flac/include \ $(TOP)/external/tremolo \ $(TOP)/frameworks/base/media/libstagefright/rtsp @@ -93,6 +95,7 @@ LOCAL_STATIC_LIBRARIES := \ libstagefright_rtsp \ libstagefright_id3 \ libstagefright_g711dec \ + libFLAC \ LOCAL_SHARED_LIBRARIES += \ libstagefright_amrnb_common \ diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 89b3dab..11ac56c 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -378,11 +378,14 @@ status_t AwesomePlayer::setDataSource_l(const sp<MediaExtractor> &extractor) { } void AwesomePlayer::reset() { + LOGI("reset"); + Mutex::Autolock autoLock(mLock); reset_l(); } void AwesomePlayer::reset_l() { + LOGI("reset_l"); mDisplayWidth = 0; mDisplayHeight = 0; @@ -408,6 +411,10 @@ void AwesomePlayer::reset_l() { } } + if (mFlags & PREPARING) { + LOGI("waiting until preparation is completes."); + } + while (mFlags & PREPARING) { mPreparedCondition.wait(mLock); } @@ -431,6 +438,8 @@ void AwesomePlayer::reset_l() { } mAudioSource.clear(); + LOGI("audio source cleared"); + mTimeSource = NULL; delete mAudioPlayer; @@ -471,6 +480,8 @@ void AwesomePlayer::reset_l() { IPCThreadState::self()->flushCommands(); } + LOGI("video source cleared"); + mDurationUs = -1; mFlags = 0; mExtractorFlags = 0; @@ -487,6 +498,8 @@ void AwesomePlayer::reset_l() { mFileSource.clear(); mBitrate = -1; + + LOGI("reset_l completed"); } void AwesomePlayer::notifyListener_l(int msg, int ext1, int ext2) { diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp index ee0d792..e06fa81 100644 --- a/media/libstagefright/DataSource.cpp +++ b/media/libstagefright/DataSource.cpp @@ -23,6 +23,7 @@ #include "include/NuCachedSource2.h" #include "include/NuHTTPDataSource.h" #include "include/DRMExtractor.h" +#include "include/FLACExtractor.h" #include "matroska/MatroskaExtractor.h" @@ -104,6 +105,7 @@ void DataSource::RegisterDefaultSniffers() { RegisterSniffer(SniffMatroska); RegisterSniffer(SniffOgg); RegisterSniffer(SniffWAV); + RegisterSniffer(SniffFLAC); RegisterSniffer(SniffAMR); RegisterSniffer(SniffMPEG2TS); RegisterSniffer(SniffMP3); diff --git a/media/libstagefright/FLACExtractor.cpp b/media/libstagefright/FLACExtractor.cpp new file mode 100644 index 0000000..8ba5a2d --- /dev/null +++ b/media/libstagefright/FLACExtractor.cpp @@ -0,0 +1,813 @@ +/* + * Copyright (C) 2011 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "FLACExtractor" +#include <utils/Log.h> + +#include "include/FLACExtractor.h" +// Vorbis comments +#include "include/OggExtractor.h" +// libFLAC parser +#include "FLAC/stream_decoder.h" + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MediaBuffer.h> + +namespace android { + +class FLACParser; + +class FLACSource : public MediaSource { + +public: + FLACSource( + const sp<DataSource> &dataSource, + const sp<MetaData> &trackMetadata); + + virtual status_t start(MetaData *params); + virtual status_t stop(); + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +protected: + virtual ~FLACSource(); + +private: + sp<DataSource> mDataSource; + sp<MetaData> mTrackMetadata; + sp<FLACParser> mParser; + bool mInitCheck; + bool mStarted; + + status_t init(); + + // no copy constructor or assignment + FLACSource(const FLACSource &); + FLACSource &operator=(const FLACSource &); + +}; + +// FLACParser wraps a C libFLAC parser aka stream decoder + +class FLACParser : public RefBase { + +public: + FLACParser( + const sp<DataSource> &dataSource, + // If metadata pointers aren't provided, we don't fill them + const sp<MetaData> &fileMetadata = 0, + const sp<MetaData> &trackMetadata = 0); + + status_t initCheck() const { + return mInitCheck; + } + + // stream properties + unsigned getMaxBlockSize() const { + return mStreamInfo.max_blocksize; + } + unsigned getSampleRate() const { + return mStreamInfo.sample_rate; + } + unsigned getChannels() const { + return mStreamInfo.channels; + } + unsigned getBitsPerSample() const { + return mStreamInfo.bits_per_sample; + } + FLAC__uint64 getTotalSamples() const { + return mStreamInfo.total_samples; + } + + // media buffers + void allocateBuffers(); + void releaseBuffers(); + MediaBuffer *readBuffer() { + return readBuffer(false, 0LL); + } + MediaBuffer *readBuffer(FLAC__uint64 sample) { + return readBuffer(true, sample); + } + +protected: + virtual ~FLACParser(); + +private: + sp<DataSource> mDataSource; + sp<MetaData> mFileMetadata; + sp<MetaData> mTrackMetadata; + bool mInitCheck; + + // media buffers + size_t mMaxBufferSize; + MediaBufferGroup *mGroup; + void (*mCopy)(short *dst, const int *const *src, unsigned nSamples); + + // handle to underlying libFLAC parser + FLAC__StreamDecoder *mDecoder; + + // current position within the data source + off64_t mCurrentPos; + bool mEOF; + + // cached when the STREAMINFO metadata is parsed by libFLAC + FLAC__StreamMetadata_StreamInfo mStreamInfo; + bool mStreamInfoValid; + + // cached when a decoded PCM block is "written" by libFLAC parser + bool mWriteRequested; + bool mWriteCompleted; + FLAC__FrameHeader mWriteHeader; + const FLAC__int32 * const *mWriteBuffer; + + // most recent error reported by libFLAC parser + FLAC__StreamDecoderErrorStatus mErrorStatus; + + status_t init(); + MediaBuffer *readBuffer(bool doSeek, FLAC__uint64 sample); + + // no copy constructor or assignment + FLACParser(const FLACParser &); + FLACParser &operator=(const FLACParser &); + + // FLAC parser callbacks as C++ instance methods + FLAC__StreamDecoderReadStatus readCallback( + FLAC__byte buffer[], size_t *bytes); + FLAC__StreamDecoderSeekStatus seekCallback( + FLAC__uint64 absolute_byte_offset); + FLAC__StreamDecoderTellStatus tellCallback( + FLAC__uint64 *absolute_byte_offset); + FLAC__StreamDecoderLengthStatus lengthCallback( + FLAC__uint64 *stream_length); + FLAC__bool eofCallback(); + FLAC__StreamDecoderWriteStatus writeCallback( + const FLAC__Frame *frame, const FLAC__int32 * const buffer[]); + void metadataCallback(const FLAC__StreamMetadata *metadata); + void errorCallback(FLAC__StreamDecoderErrorStatus status); + + // FLAC parser callbacks as C-callable functions + static FLAC__StreamDecoderReadStatus read_callback( + const FLAC__StreamDecoder *decoder, + FLAC__byte buffer[], size_t *bytes, + void *client_data); + static FLAC__StreamDecoderSeekStatus seek_callback( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 absolute_byte_offset, + void *client_data); + static FLAC__StreamDecoderTellStatus tell_callback( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 *absolute_byte_offset, + void *client_data); + static FLAC__StreamDecoderLengthStatus length_callback( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 *stream_length, + void *client_data); + static FLAC__bool eof_callback( + const FLAC__StreamDecoder *decoder, + void *client_data); + static FLAC__StreamDecoderWriteStatus write_callback( + const FLAC__StreamDecoder *decoder, + const FLAC__Frame *frame, const FLAC__int32 * const buffer[], + void *client_data); + static void metadata_callback( + const FLAC__StreamDecoder *decoder, + const FLAC__StreamMetadata *metadata, + void *client_data); + static void error_callback( + const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, + void *client_data); + +}; + +// The FLAC parser calls our C++ static callbacks using C calling conventions, +// inside FLAC__stream_decoder_process_until_end_of_metadata +// and FLAC__stream_decoder_process_single. +// We immediately then call our corresponding C++ instance methods +// with the same parameter list, but discard redundant information. + +FLAC__StreamDecoderReadStatus FLACParser::read_callback( + const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], + size_t *bytes, void *client_data) +{ + return ((FLACParser *) client_data)->readCallback(buffer, bytes); +} + +FLAC__StreamDecoderSeekStatus FLACParser::seek_callback( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 absolute_byte_offset, void *client_data) +{ + return ((FLACParser *) client_data)->seekCallback(absolute_byte_offset); +} + +FLAC__StreamDecoderTellStatus FLACParser::tell_callback( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + return ((FLACParser *) client_data)->tellCallback(absolute_byte_offset); +} + +FLAC__StreamDecoderLengthStatus FLACParser::length_callback( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 *stream_length, void *client_data) +{ + return ((FLACParser *) client_data)->lengthCallback(stream_length); +} + +FLAC__bool FLACParser::eof_callback( + const FLAC__StreamDecoder *decoder, void *client_data) +{ + return ((FLACParser *) client_data)->eofCallback(); +} + +FLAC__StreamDecoderWriteStatus FLACParser::write_callback( + const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, + const FLAC__int32 * const buffer[], void *client_data) +{ + return ((FLACParser *) client_data)->writeCallback(frame, buffer); +} + +void FLACParser::metadata_callback( + const FLAC__StreamDecoder *decoder, + const FLAC__StreamMetadata *metadata, void *client_data) +{ + ((FLACParser *) client_data)->metadataCallback(metadata); +} + +void FLACParser::error_callback( + const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + ((FLACParser *) client_data)->errorCallback(status); +} + +// These are the corresponding callbacks with C++ calling conventions + +FLAC__StreamDecoderReadStatus FLACParser::readCallback( + FLAC__byte buffer[], size_t *bytes) +{ + size_t requested = *bytes; + ssize_t actual = mDataSource->readAt(mCurrentPos, buffer, requested); + if (0 > actual) { + *bytes = 0; + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } else if (0 == actual) { + *bytes = 0; + mEOF = true; + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + } else { + assert(actual <= requested); + *bytes = actual; + mCurrentPos += actual; + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } +} + +FLAC__StreamDecoderSeekStatus FLACParser::seekCallback( + FLAC__uint64 absolute_byte_offset) +{ + mCurrentPos = absolute_byte_offset; + mEOF = false; + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +FLAC__StreamDecoderTellStatus FLACParser::tellCallback( + FLAC__uint64 *absolute_byte_offset) +{ + *absolute_byte_offset = mCurrentPos; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +FLAC__StreamDecoderLengthStatus FLACParser::lengthCallback( + FLAC__uint64 *stream_length) +{ + off64_t size; + if (OK == mDataSource->getSize(&size)) { + *stream_length = size; + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; + } else { + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; + } +} + +FLAC__bool FLACParser::eofCallback() +{ + return mEOF; +} + +FLAC__StreamDecoderWriteStatus FLACParser::writeCallback( + const FLAC__Frame *frame, const FLAC__int32 * const buffer[]) +{ + if (mWriteRequested) { + mWriteRequested = false; + // FLAC parser doesn't free or realloc buffer until next frame or finish + mWriteHeader = frame->header; + mWriteBuffer = buffer; + mWriteCompleted = true; + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } else { + LOGE("FLACParser::writeCallback unexpected"); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } +} + +void FLACParser::metadataCallback(const FLAC__StreamMetadata *metadata) +{ + switch (metadata->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + if (!mStreamInfoValid) { + mStreamInfo = metadata->data.stream_info; + mStreamInfoValid = true; + } else { + LOGE("FLACParser::metadataCallback unexpected STREAMINFO"); + } + break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + { + const FLAC__StreamMetadata_VorbisComment *vc; + vc = &metadata->data.vorbis_comment; + for (FLAC__uint32 i = 0; i < vc->num_comments; ++i) { + FLAC__StreamMetadata_VorbisComment_Entry *vce; + vce = &vc->comments[i]; + if (mFileMetadata != 0) { + parseVorbisComment(mFileMetadata, (const char *) vce->entry, + vce->length); + } + } + } + break; + case FLAC__METADATA_TYPE_PICTURE: + if (mFileMetadata != 0) { + const FLAC__StreamMetadata_Picture *p = &metadata->data.picture; + mFileMetadata->setData(kKeyAlbumArt, + MetaData::TYPE_NONE, p->data, p->data_length); + mFileMetadata->setCString(kKeyAlbumArtMIME, p->mime_type); + } + break; + default: + LOGW("FLACParser::metadataCallback unexpected type %u", metadata->type); + break; + } +} + +void FLACParser::errorCallback(FLAC__StreamDecoderErrorStatus status) +{ + LOGE("FLACParser::errorCallback status=%d", status); + mErrorStatus = status; +} + +// Copy samples from FLAC native 32-bit non-interleaved to 16-bit interleaved. +// These are candidates for optimization if needed. + +static void copyMono8(short *dst, const int *const *src, unsigned nSamples) +{ + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i] << 8; + } +} + +static void copyStereo8(short *dst, const int *const *src, unsigned nSamples) +{ + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i] << 8; + *dst++ = src[1][i] << 8; + } +} + +static void copyMono16(short *dst, const int *const *src, unsigned nSamples) +{ + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i]; + } +} + +static void copyStereo16(short *dst, const int *const *src, unsigned nSamples) +{ + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i]; + *dst++ = src[1][i]; + } +} + +// 24-bit versions should do dithering or noise-shaping, here or in AudioFlinger + +static void copyMono24(short *dst, const int *const *src, unsigned nSamples) +{ + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i] >> 8; + } +} + +static void copyStereo24(short *dst, const int *const *src, unsigned nSamples) +{ + for (unsigned i = 0; i < nSamples; ++i) { + *dst++ = src[0][i] >> 8; + *dst++ = src[1][i] >> 8; + } +} + +static void copyTrespass(short *dst, const int *const *src, unsigned nSamples) +{ + TRESPASS(); +} + +// FLACParser + +FLACParser::FLACParser( + const sp<DataSource> &dataSource, + const sp<MetaData> &fileMetadata, + const sp<MetaData> &trackMetadata) + : mDataSource(dataSource), + mFileMetadata(fileMetadata), + mTrackMetadata(trackMetadata), + mInitCheck(false), + mMaxBufferSize(0), + mGroup(NULL), + mCopy(copyTrespass), + mDecoder(NULL), + mCurrentPos(0LL), + mEOF(false), + mStreamInfoValid(false), + mWriteRequested(false), + mWriteCompleted(false), + mWriteBuffer(NULL), + mErrorStatus((FLAC__StreamDecoderErrorStatus) -1) +{ + LOGV("FLACParser::FLACParser"); + memset(&mStreamInfo, 0, sizeof(mStreamInfo)); + memset(&mWriteHeader, 0, sizeof(mWriteHeader)); + mInitCheck = init(); +} + +FLACParser::~FLACParser() +{ + LOGV("FLACParser::~FLACParser"); + if (mDecoder != NULL) { + FLAC__stream_decoder_delete(mDecoder); + mDecoder = NULL; + } +} + +status_t FLACParser::init() +{ + // setup libFLAC parser + mDecoder = FLAC__stream_decoder_new(); + if (mDecoder == NULL) { + // The new should succeed, since probably all it does is a malloc + // that always succeeds in Android. But to avoid dependence on the + // libFLAC internals, we check and log here. + LOGE("new failed"); + return NO_INIT; + } + FLAC__stream_decoder_set_md5_checking(mDecoder, false); + FLAC__stream_decoder_set_metadata_ignore_all(mDecoder); + FLAC__stream_decoder_set_metadata_respond( + mDecoder, FLAC__METADATA_TYPE_STREAMINFO); + FLAC__stream_decoder_set_metadata_respond( + mDecoder, FLAC__METADATA_TYPE_PICTURE); + FLAC__stream_decoder_set_metadata_respond( + mDecoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); + FLAC__StreamDecoderInitStatus initStatus; + initStatus = FLAC__stream_decoder_init_stream( + mDecoder, + read_callback, seek_callback, tell_callback, + length_callback, eof_callback, write_callback, + metadata_callback, error_callback, (void *) this); + if (initStatus != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + // A failure here probably indicates a programming error and so is + // unlikely to happen. But we check and log here similarly to above. + LOGE("init_stream failed %d", initStatus); + return NO_INIT; + } + // parse all metadata + if (!FLAC__stream_decoder_process_until_end_of_metadata(mDecoder)) { + LOGE("end_of_metadata failed"); + return NO_INIT; + } + if (mStreamInfoValid) { + // check channel count + switch (getChannels()) { + case 1: + case 2: + break; + default: + LOGE("unsupported channel count %u", getChannels()); + return NO_INIT; + } + // check bit depth + switch (getBitsPerSample()) { + case 8: + case 16: + case 24: + break; + default: + LOGE("unsupported bits per sample %u", getBitsPerSample()); + return NO_INIT; + } + // check sample rate + switch (getSampleRate()) { + case 8000: + case 11025: + case 12000: + case 16000: + case 22050: + case 24000: + case 32000: + case 44100: + case 48000: + break; + default: + // 96000 would require a proper downsampler in AudioFlinger + LOGE("unsupported sample rate %u", getSampleRate()); + return NO_INIT; + } + // configure the appropriate copy function, defaulting to trespass + static const struct { + unsigned mChannels; + unsigned mBitsPerSample; + void (*mCopy)(short *dst, const int *const *src, unsigned nSamples); + } table[] = { + { 1, 8, copyMono8 }, + { 2, 8, copyStereo8 }, + { 1, 16, copyMono16 }, + { 2, 16, copyStereo16 }, + { 1, 24, copyMono24 }, + { 2, 24, copyStereo24 }, + }; + for (unsigned i = 0; i < sizeof(table)/sizeof(table[0]); ++i) { + if (table[i].mChannels == getChannels() && + table[i].mBitsPerSample == getBitsPerSample()) { + mCopy = table[i].mCopy; + break; + } + } + // populate track metadata + if (mTrackMetadata != 0) { + mTrackMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); + mTrackMetadata->setInt32(kKeyChannelCount, getChannels()); + mTrackMetadata->setInt32(kKeySampleRate, getSampleRate()); + // sample rate is non-zero, so division by zero not possible + mTrackMetadata->setInt64(kKeyDuration, + (getTotalSamples() * 1000000LL) / getSampleRate()); + } + } else { + LOGE("missing STREAMINFO"); + return NO_INIT; + } + if (mFileMetadata != 0) { + mFileMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_FLAC); + } + return OK; +} + +void FLACParser::allocateBuffers() +{ + CHECK(mGroup == NULL); + mGroup = new MediaBufferGroup; + mMaxBufferSize = getMaxBlockSize() * getChannels() * sizeof(short); + mGroup->add_buffer(new MediaBuffer(mMaxBufferSize)); +} + +void FLACParser::releaseBuffers() +{ + CHECK(mGroup != NULL); + delete mGroup; + mGroup = NULL; +} + +MediaBuffer *FLACParser::readBuffer(bool doSeek, FLAC__uint64 sample) +{ + mWriteRequested = true; + mWriteCompleted = false; + if (doSeek) { + // We implement the seek callback, so this works without explicit flush + if (!FLAC__stream_decoder_seek_absolute(mDecoder, sample)) { + LOGE("FLACParser::readBuffer seek to sample %llu failed", sample); + return NULL; + } + LOGV("FLACParser::readBuffer seek to sample %llu succeeded", sample); + } else { + if (!FLAC__stream_decoder_process_single(mDecoder)) { + LOGE("FLACParser::readBuffer process_single failed"); + return NULL; + } + } + if (!mWriteCompleted) { + LOGV("FLACParser::readBuffer write did not complete"); + return NULL; + } + // verify that block header keeps the promises made by STREAMINFO + unsigned blocksize = mWriteHeader.blocksize; + if (blocksize == 0 || blocksize > getMaxBlockSize()) { + LOGE("FLACParser::readBuffer write invalid blocksize %u", blocksize); + return NULL; + } + if (mWriteHeader.sample_rate != getSampleRate() || + mWriteHeader.channels != getChannels() || + mWriteHeader.bits_per_sample != getBitsPerSample()) { + LOGE("FLACParser::readBuffer write changed parameters mid-stream"); + } + // acquire a media buffer + CHECK(mGroup != NULL); + MediaBuffer *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return NULL; + } + size_t bufferSize = blocksize * getChannels() * sizeof(short); + CHECK(bufferSize <= mMaxBufferSize); + short *data = (short *) buffer->data(); + buffer->set_range(0, bufferSize); + // copy PCM from FLAC write buffer to our media buffer, with interleaving + (*mCopy)(data, mWriteBuffer, blocksize); + // fill in buffer metadata + CHECK(mWriteHeader.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER); + FLAC__uint64 sampleNumber = mWriteHeader.number.sample_number; + int64_t timeUs = (1000000LL * sampleNumber) / getSampleRate(); + buffer->meta_data()->setInt64(kKeyTime, timeUs); + buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1); + return buffer; +} + +// FLACsource + +FLACSource::FLACSource( + const sp<DataSource> &dataSource, + const sp<MetaData> &trackMetadata) + : mDataSource(dataSource), + mTrackMetadata(trackMetadata), + mParser(0), + mInitCheck(false), + mStarted(false) +{ + LOGV("FLACSource::FLACSource"); + mInitCheck = init(); +} + +FLACSource::~FLACSource() +{ + LOGV("~FLACSource::FLACSource"); + if (mStarted) { + stop(); + } +} + +status_t FLACSource::start(MetaData *params) +{ + LOGV("FLACSource::start"); + + CHECK(!mStarted); + mParser->allocateBuffers(); + mStarted = true; + + return OK; +} + +status_t FLACSource::stop() +{ + LOGV("FLACSource::stop"); + + CHECK(mStarted); + mParser->releaseBuffers(); + mStarted = false; + + return OK; +} + +sp<MetaData> FLACSource::getFormat() +{ + return mTrackMetadata; +} + +status_t FLACSource::read( + MediaBuffer **outBuffer, const ReadOptions *options) +{ + MediaBuffer *buffer; + // process an optional seek request + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if ((NULL != options) && options->getSeekTo(&seekTimeUs, &mode)) { + FLAC__uint64 sample; + if (seekTimeUs <= 0LL) { + sample = 0LL; + } else { + // sample and total samples are both zero-based, and seek to EOF ok + sample = (seekTimeUs * mParser->getSampleRate()) / 1000000LL; + if (sample >= mParser->getTotalSamples()) { + sample = mParser->getTotalSamples(); + } + } + buffer = mParser->readBuffer(sample); + // otherwise read sequentially + } else { + buffer = mParser->readBuffer(); + } + *outBuffer = buffer; + return buffer != NULL ? (status_t) OK : (status_t) ERROR_END_OF_STREAM; +} + +status_t FLACSource::init() +{ + LOGV("FLACSource::init"); + // re-use the same track metadata passed into constructor from FLACExtractor + mParser = new FLACParser(mDataSource); + return mParser->initCheck(); +} + +// FLACExtractor + +FLACExtractor::FLACExtractor( + const sp<DataSource> &dataSource) + : mDataSource(dataSource), + mInitCheck(false) +{ + LOGV("FLACExtractor::FLACExtractor"); + mInitCheck = init(); +} + +FLACExtractor::~FLACExtractor() +{ + LOGV("~FLACExtractor::FLACExtractor"); +} + +size_t FLACExtractor::countTracks() +{ + return mInitCheck == OK ? 1 : 0; +} + +sp<MediaSource> FLACExtractor::getTrack(size_t index) +{ + if (mInitCheck != OK || index > 0) { + return NULL; + } + return new FLACSource(mDataSource, mTrackMetadata); +} + +sp<MetaData> FLACExtractor::getTrackMetaData( + size_t index, uint32_t flags) +{ + if (mInitCheck != OK || index > 0) { + return NULL; + } + return mTrackMetadata; +} + +status_t FLACExtractor::init() +{ + mFileMetadata = new MetaData; + mTrackMetadata = new MetaData; + // FLACParser will fill in the metadata for us + mParser = new FLACParser(mDataSource, mFileMetadata, mTrackMetadata); + return mParser->initCheck(); +} + +sp<MetaData> FLACExtractor::getMetaData() +{ + return mFileMetadata; +} + +// Sniffer + +bool SniffFLAC( + const sp<DataSource> &source, String8 *mimeType, float *confidence, + sp<AMessage> *) +{ + // first 4 is the signature word + // second 4 is the sizeof STREAMINFO + // 042 is the mandatory STREAMINFO + // no need to read rest of the header, as a premature EOF will be caught later + uint8_t header[4+4]; + if (source->readAt(0, header, sizeof(header)) != sizeof(header) + || memcmp("fLaC\0\0\0\042", header, 4+4)) + { + return false; + } + + *mimeType = MEDIA_MIMETYPE_AUDIO_FLAC; + *confidence = 0.5; + + return true; +} + +} // namespace android diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index e6e98aa..108a1d1 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -1074,6 +1074,20 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { break; } + case FOURCC('c', 't', 't', 's'): + { + status_t err = + mLastTrack->sampleTable->setCompositionTimeToSampleParams( + data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + *offset += chunk_size; + break; + } + case FOURCC('s', 't', 's', 's'): { status_t err = diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp index 4599fca..b50af89 100644 --- a/media/libstagefright/MediaDefs.cpp +++ b/media/libstagefright/MediaDefs.cpp @@ -35,6 +35,7 @@ const char *MEDIA_MIMETYPE_AUDIO_VORBIS = "audio/vorbis"; const char *MEDIA_MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw"; const char *MEDIA_MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw"; const char *MEDIA_MIMETYPE_AUDIO_RAW = "audio/raw"; +const char *MEDIA_MIMETYPE_AUDIO_FLAC = "audio/flac"; const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mpeg4"; const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/wav"; diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp index d12ac64..08ed206 100644 --- a/media/libstagefright/MediaExtractor.cpp +++ b/media/libstagefright/MediaExtractor.cpp @@ -26,6 +26,7 @@ #include "include/MPEG2TSExtractor.h" #include "include/DRMExtractor.h" #include "include/WVMExtractor.h" +#include "include/FLACExtractor.h" #include "matroska/MatroskaExtractor.h" @@ -85,6 +86,8 @@ sp<MediaExtractor> MediaExtractor::Create( } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB) || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) { return new AMRExtractor(source); + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) { + return new FLACExtractor(source); } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) { return new WAVExtractor(source); } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) { diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 2a19b25..247ace7 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -3269,7 +3269,7 @@ status_t OMXCodec::start(MetaData *meta) { } status_t OMXCodec::stop() { - CODEC_LOGV("stop mState=%d", mState); + CODEC_LOGI("stop mState=%d", mState); Mutex::Autolock autoLock(mLock); diff --git a/media/libstagefright/OggExtractor.cpp b/media/libstagefright/OggExtractor.cpp index cf622af..0e51caf 100644 --- a/media/libstagefright/OggExtractor.cpp +++ b/media/libstagefright/OggExtractor.cpp @@ -114,7 +114,6 @@ private: MediaBuffer *buffer, uint8_t type); void parseFileMetaData(); - void extractAlbumArt(const void *data, size_t size); uint64_t findPrevGranulePosition(off64_t pageOffset); @@ -122,6 +121,9 @@ private: MyVorbisExtractor &operator=(const MyVorbisExtractor &); }; +static void extractAlbumArt( + const sp<MetaData> &fileMeta, const void *data, size_t size); + //////////////////////////////////////////////////////////////////////////////// OggSource::OggSource(const sp<OggExtractor> &extractor) @@ -654,6 +656,17 @@ void MyVorbisExtractor::parseFileMetaData() { mFileMeta = new MetaData; mFileMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG); + for (int i = 0; i < mVc.comments; ++i) { + const char *comment = mVc.user_comments[i]; + size_t commentLength = mVc.comment_lengths[i]; + parseVorbisComment(mFileMeta, comment, commentLength); + //LOGI("comment #%d: '%s'", i + 1, mVc.user_comments[i]); + } +} + +void parseVorbisComment( + const sp<MetaData> &fileMeta, const char *comment, size_t commentLength) +{ struct { const char *const mTag; uint32_t mKey; @@ -675,33 +688,25 @@ void MyVorbisExtractor::parseFileMetaData() { { "ANDROID_LOOP", kKeyAutoLoop }, }; - for (int i = 0; i < mVc.comments; ++i) { - const char *comment = mVc.user_comments[i]; - for (size_t j = 0; j < sizeof(kMap) / sizeof(kMap[0]); ++j) { size_t tagLen = strlen(kMap[j].mTag); if (!strncasecmp(kMap[j].mTag, comment, tagLen) && comment[tagLen] == '=') { if (kMap[j].mKey == kKeyAlbumArt) { extractAlbumArt( + fileMeta, &comment[tagLen + 1], - mVc.comment_lengths[i] - tagLen - 1); + commentLength - tagLen - 1); } else if (kMap[j].mKey == kKeyAutoLoop) { if (!strcasecmp(&comment[tagLen + 1], "true")) { - mFileMeta->setInt32(kKeyAutoLoop, true); + fileMeta->setInt32(kKeyAutoLoop, true); } } else { - mFileMeta->setCString(kMap[j].mKey, &comment[tagLen + 1]); + fileMeta->setCString(kMap[j].mKey, &comment[tagLen + 1]); } } } - } -#if 0 - for (int i = 0; i < mVc.comments; ++i) { - LOGI("comment #%d: '%s'", i + 1, mVc.user_comments[i]); - } -#endif } // The returned buffer should be free()d. @@ -769,7 +774,8 @@ static uint8_t *DecodeBase64(const char *s, size_t size, size_t *outSize) { return (uint8_t *)buffer; } -void MyVorbisExtractor::extractAlbumArt(const void *data, size_t size) { +static void extractAlbumArt( + const sp<MetaData> &fileMeta, const void *data, size_t size) { LOGV("extractAlbumArt from '%s'", (const char *)data); size_t flacSize; @@ -833,10 +839,10 @@ void MyVorbisExtractor::extractAlbumArt(const void *data, size_t size) { LOGV("got image data, %d trailing bytes", flacSize - 32 - typeLen - descLen - dataLen); - mFileMeta->setData( + fileMeta->setData( kKeyAlbumArt, 0, &flac[8 + typeLen + 4 + descLen + 20], dataLen); - mFileMeta->setCString(kKeyAlbumArtMIME, type); + fileMeta->setCString(kKeyAlbumArtMIME, type); exit: free(flac); diff --git a/media/libstagefright/SampleIterator.cpp b/media/libstagefright/SampleIterator.cpp index 062ab9b..c7b00b1 100644 --- a/media/libstagefright/SampleIterator.cpp +++ b/media/libstagefright/SampleIterator.cpp @@ -307,6 +307,8 @@ status_t SampleIterator::findSampleTime( *time = mTTSSampleTime + mTTSDuration * (sampleIndex - mTTSSampleIndex); + *time += mTable->getCompositionTimeOffset(sampleIndex); + return OK; } diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp index a9163fc..423df70 100644 --- a/media/libstagefright/SampleTable.cpp +++ b/media/libstagefright/SampleTable.cpp @@ -53,6 +53,8 @@ SampleTable::SampleTable(const sp<DataSource> &source) mNumSampleSizes(0), mTimeToSampleCount(0), mTimeToSample(NULL), + mCompositionTimeDeltaEntries(NULL), + mNumCompositionTimeDeltaEntries(0), mSyncSampleOffset(-1), mNumSyncSamples(0), mSyncSamples(NULL), @@ -68,6 +70,9 @@ SampleTable::~SampleTable() { delete[] mSyncSamples; mSyncSamples = NULL; + delete[] mCompositionTimeDeltaEntries; + mCompositionTimeDeltaEntries = NULL; + delete[] mTimeToSample; mTimeToSample = NULL; @@ -260,6 +265,51 @@ status_t SampleTable::setTimeToSampleParams( return OK; } +status_t SampleTable::setCompositionTimeToSampleParams( + off64_t data_offset, size_t data_size) { + LOGI("There are reordered frames present."); + + if (mCompositionTimeDeltaEntries != NULL || data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->readAt( + data_offset, header, sizeof(header)) + < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + size_t numEntries = U32_AT(&header[4]); + + if (data_size != (numEntries + 1) * 8) { + return ERROR_MALFORMED; + } + + mNumCompositionTimeDeltaEntries = numEntries; + mCompositionTimeDeltaEntries = new uint32_t[2 * numEntries]; + + if (mDataSource->readAt( + data_offset + 8, mCompositionTimeDeltaEntries, numEntries * 8) + < (ssize_t)numEntries * 8) { + delete[] mCompositionTimeDeltaEntries; + mCompositionTimeDeltaEntries = NULL; + + return ERROR_IO; + } + + for (size_t i = 0; i < 2 * numEntries; ++i) { + mCompositionTimeDeltaEntries[i] = ntohl(mCompositionTimeDeltaEntries[i]); + } + + return OK; +} + status_t SampleTable::setSyncSampleParams(off64_t data_offset, size_t data_size) { if (mSyncSampleOffset >= 0 || data_size < 8) { return ERROR_MALFORMED; @@ -333,6 +383,8 @@ uint32_t abs_difference(uint32_t time1, uint32_t time2) { status_t SampleTable::findSampleAtTime( uint32_t req_time, uint32_t *sample_index, uint32_t flags) { + // XXX this currently uses decoding time, instead of composition time. + *sample_index = 0; Mutex::Autolock autoLock(mLock); @@ -607,5 +659,26 @@ status_t SampleTable::getMetaDataForSample( return OK; } +uint32_t SampleTable::getCompositionTimeOffset(uint32_t sampleIndex) const { + if (mCompositionTimeDeltaEntries == NULL) { + return 0; + } + + uint32_t curSample = 0; + for (size_t i = 0; i < mNumCompositionTimeDeltaEntries; ++i) { + uint32_t sampleCount = mCompositionTimeDeltaEntries[2 * i]; + + if (sampleIndex < curSample + sampleCount) { + uint32_t sampleDelta = mCompositionTimeDeltaEntries[2 * i + 1]; + + return sampleDelta; + } + + curSample += sampleCount; + } + + return 0; +} + } // namespace android diff --git a/media/libstagefright/StagefrightMediaScanner.cpp b/media/libstagefright/StagefrightMediaScanner.cpp index be3df7c..84f65ff 100644 --- a/media/libstagefright/StagefrightMediaScanner.cpp +++ b/media/libstagefright/StagefrightMediaScanner.cpp @@ -37,7 +37,7 @@ static bool FileHasAcceptableExtension(const char *extension) { ".mp3", ".mp4", ".m4a", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".mpeg", ".ogg", ".mid", ".smf", ".imy", ".wma", ".aac", ".wav", ".amr", ".midi", ".xmf", ".rtttl", ".rtx", ".ota", - ".mkv", ".mka", ".webm", ".ts", ".fl" + ".mkv", ".mka", ".webm", ".ts", ".fl", ".flac" }; static const size_t kNumValidExtensions = sizeof(kValidExtensions) / sizeof(kValidExtensions[0]); diff --git a/media/libstagefright/include/AMRExtractor.h b/media/libstagefright/include/AMRExtractor.h index 1cdf36d..589d837 100644 --- a/media/libstagefright/include/AMRExtractor.h +++ b/media/libstagefright/include/AMRExtractor.h @@ -24,6 +24,7 @@ namespace android { struct AMessage; class String8; +#define OFFSET_TABLE_LEN 300 class AMRExtractor : public MediaExtractor { public: @@ -42,9 +43,11 @@ private: sp<DataSource> mDataSource; sp<MetaData> mMeta; status_t mInitCheck; - size_t mFrameSize; bool mIsWide; + off64_t mOffsetTable[OFFSET_TABLE_LEN]; //5 min + size_t mOffsetTableLength; + AMRExtractor(const AMRExtractor &); AMRExtractor &operator=(const AMRExtractor &); }; diff --git a/media/libstagefright/include/FLACExtractor.h b/media/libstagefright/include/FLACExtractor.h new file mode 100644 index 0000000..ded91c2 --- /dev/null +++ b/media/libstagefright/include/FLACExtractor.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef FLAC_EXTRACTOR_H_ +#define FLAC_EXTRACTOR_H_ + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaExtractor.h> +#include <utils/String8.h> + +namespace android { + +class FLACParser; + +class FLACExtractor : public MediaExtractor { + +public: + // Extractor assumes ownership of source + FLACExtractor(const sp<DataSource> &source); + + virtual size_t countTracks(); + virtual sp<MediaSource> getTrack(size_t index); + virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags); + + virtual sp<MetaData> getMetaData(); + +protected: + virtual ~FLACExtractor(); + +private: + sp<DataSource> mDataSource; + sp<FLACParser> mParser; + status_t mInitCheck; + sp<MetaData> mFileMetadata; + + // There is only one track + sp<MetaData> mTrackMetadata; + + status_t init(); + + FLACExtractor(const FLACExtractor &); + FLACExtractor &operator=(const FLACExtractor &); + +}; + +bool SniffFLAC(const sp<DataSource> &source, String8 *mimeType, + float *confidence, sp<AMessage> *); + +} // namespace android + +#endif // FLAC_EXTRACTOR_H_ diff --git a/media/libstagefright/include/OggExtractor.h b/media/libstagefright/include/OggExtractor.h index 1eda025..a41f681 100644 --- a/media/libstagefright/include/OggExtractor.h +++ b/media/libstagefright/include/OggExtractor.h @@ -57,6 +57,9 @@ bool SniffOgg( const sp<DataSource> &source, String8 *mimeType, float *confidence, sp<AMessage> *); +void parseVorbisComment( + const sp<MetaData> &fileMeta, const char *comment, size_t commentLength); + } // namespace android #endif // OGG_EXTRACTOR_H_ diff --git a/media/libstagefright/include/SampleTable.h b/media/libstagefright/include/SampleTable.h index c5e8136..2f95de9 100644 --- a/media/libstagefright/include/SampleTable.h +++ b/media/libstagefright/include/SampleTable.h @@ -46,6 +46,9 @@ public: status_t setTimeToSampleParams(off64_t data_offset, size_t data_size); + status_t setCompositionTimeToSampleParams( + off64_t data_offset, size_t data_size); + status_t setSyncSampleParams(off64_t data_offset, size_t data_size); //////////////////////////////////////////////////////////////////////////// @@ -104,6 +107,9 @@ private: uint32_t mTimeToSampleCount; uint32_t *mTimeToSample; + uint32_t *mCompositionTimeDeltaEntries; + size_t mNumCompositionTimeDeltaEntries; + off64_t mSyncSampleOffset; uint32_t mNumSyncSamples; uint32_t *mSyncSamples; @@ -122,6 +128,8 @@ private: status_t getSampleSize_l(uint32_t sample_index, size_t *sample_size); + uint32_t getCompositionTimeOffset(uint32_t sampleIndex) const; + SampleTable(const SampleTable &); SampleTable &operator=(const SampleTable &); }; diff --git a/media/mtp/Android.mk b/media/mtp/Android.mk index 70dc340..c25285e 100644 --- a/media/mtp/Android.mk +++ b/media/mtp/Android.mk @@ -21,7 +21,6 @@ ifneq ($(TARGET_SIMULATOR),true) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ - MtpClient.cpp \ MtpDataPacket.cpp \ MtpDebug.cpp \ MtpDevice.cpp \ @@ -53,7 +52,6 @@ ifeq ($(HOST_OS),linux) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ - MtpClient.cpp \ MtpDataPacket.cpp \ MtpDebug.cpp \ MtpDevice.cpp \ diff --git a/media/mtp/MtpClient.cpp b/media/mtp/MtpClient.cpp deleted file mode 100644 index c830540..0000000 --- a/media/mtp/MtpClient.cpp +++ /dev/null @@ -1,251 +0,0 @@ -/* - * 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. - */ - -#define LOG_TAG "MtpClient" - -#include "MtpDebug.h" -#include "MtpClient.h" -#include "MtpDevice.h" - -#include <stdio.h> -#include <stdlib.h> -#include <sys/types.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <errno.h> - -#include <usbhost/usbhost.h> - -struct usb_device; - -namespace android { - -static bool isMtpDevice(uint16_t vendor, uint16_t product) { - // Sandisk Sansa Fuze - if (vendor == 0x0781 && product == 0x74c2) - return true; - // Samsung YP-Z5 - if (vendor == 0x04e8 && product == 0x503c) - return true; - return false; -} - -class MtpClientThread : public Thread { -private: - MtpClient* mClient; - -public: - MtpClientThread(MtpClient* client) - : mClient(client) - { - } - - virtual bool threadLoop() { - return mClient->threadLoop(); - } -}; - - -MtpClient::MtpClient() - : mThread(NULL), - mUsbHostContext(NULL), - mDone(false) -{ -} - -MtpClient::~MtpClient() { - usb_host_cleanup(mUsbHostContext); -} - -bool MtpClient::start() { - Mutex::Autolock autoLock(mMutex); - - if (mThread) - return true; - - mUsbHostContext = usb_host_init(); - if (!mUsbHostContext) - return false; - - mThread = new MtpClientThread(this); - mThread->run("MtpClientThread"); - // wait for the thread to do initial device discovery before returning - mThreadStartCondition.wait(mMutex); - - return true; -} - -void MtpClient::stop() { - mDone = true; -} - -MtpDevice* MtpClient::getDevice(int id) { - for (int i = 0; i < mDeviceList.size(); i++) { - MtpDevice* device = mDeviceList[i]; - if (device->getID() == id) - return device; - } - return NULL; -} - -bool MtpClient::usbDeviceAdded(const char *devname) { - struct usb_descriptor_header* desc; - struct usb_descriptor_iter iter; - - struct usb_device *device = usb_device_open(devname); - if (!device) { - LOGE("usb_device_open failed\n"); - return mDone; - } - - usb_descriptor_iter_init(device, &iter); - - while ((desc = usb_descriptor_iter_next(&iter)) != NULL) { - if (desc->bDescriptorType == USB_DT_INTERFACE) { - struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc; - - if (interface->bInterfaceClass == USB_CLASS_STILL_IMAGE && - interface->bInterfaceSubClass == 1 && // Still Image Capture - interface->bInterfaceProtocol == 1) // Picture Transfer Protocol (PIMA 15470) - { - LOGD("Found camera: \"%s\" \"%s\"\n", usb_device_get_manufacturer_name(device), - usb_device_get_product_name(device)); - } else if (interface->bInterfaceClass == 0xFF && - interface->bInterfaceSubClass == 0xFF && - interface->bInterfaceProtocol == 0) { - char* interfaceName = usb_device_get_string(device, interface->iInterface); - if (!interfaceName || strcmp(interfaceName, "MTP")) - continue; - // Looks like an android style MTP device - LOGD("Found MTP device: \"%s\" \"%s\"\n", usb_device_get_manufacturer_name(device), - usb_device_get_product_name(device)); - } else { - // look for special cased devices based on vendor/product ID - // we are doing this mainly for testing purposes - uint16_t vendor = usb_device_get_vendor_id(device); - uint16_t product = usb_device_get_product_id(device); - if (!isMtpDevice(vendor, product)) { - // not an MTP or PTP device - continue; - } - // request MTP OS string and descriptor - // some music players need to see this before entering MTP mode. - char buffer[256]; - memset(buffer, 0, sizeof(buffer)); - int ret = usb_device_send_control(device, - USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_STANDARD, - USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | 0xEE, - 0, sizeof(buffer), buffer); - printf("usb_device_send_control returned %d errno: %d\n", ret, errno); - if (ret > 0) { - printf("got MTP string %s\n", buffer); - ret = usb_device_send_control(device, - USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_VENDOR, 1, - 0, 4, sizeof(buffer), buffer); - printf("OS descriptor got %d\n", ret); - } else { - printf("no MTP string\n"); - } - } - - // if we got here, then we have a likely MTP or PTP device - - // interface should be followed by three endpoints - struct usb_endpoint_descriptor *ep; - struct usb_endpoint_descriptor *ep_in_desc = NULL; - struct usb_endpoint_descriptor *ep_out_desc = NULL; - struct usb_endpoint_descriptor *ep_intr_desc = NULL; - for (int i = 0; i < 3; i++) { - ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter); - if (!ep || ep->bDescriptorType != USB_DT_ENDPOINT) { - LOGE("endpoints not found\n"); - return mDone; - } - if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK) { - if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) - ep_in_desc = ep; - else - ep_out_desc = ep; - } else if (ep->bmAttributes == USB_ENDPOINT_XFER_INT && - ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) { - ep_intr_desc = ep; - } - } - if (!ep_in_desc || !ep_out_desc || !ep_intr_desc) { - LOGE("endpoints not found\n"); - return mDone; - } - - if (usb_device_claim_interface(device, interface->bInterfaceNumber)) { - LOGE("usb_device_claim_interface failed errno: %d\n", errno); - return mDone; - } - - MtpDevice* mtpDevice = new MtpDevice(device, interface->bInterfaceNumber, - ep_in_desc, ep_out_desc, ep_intr_desc); - mDeviceList.add(mtpDevice); - mtpDevice->initialize(); - deviceAdded(mtpDevice); - return mDone; - } - } - - usb_device_close(device); - return mDone; -} - -bool MtpClient::usbDeviceRemoved(const char *devname) { - for (int i = 0; i < mDeviceList.size(); i++) { - MtpDevice* device = mDeviceList[i]; - if (!strcmp(devname, device->getDeviceName())) { - deviceRemoved(device); - mDeviceList.removeAt(i); - delete device; - LOGD("Camera removed!\n"); - break; - } - } - return mDone; -} - -bool MtpClient::usbDiscoveryDone() { - Mutex::Autolock autoLock(mMutex); - mThreadStartCondition.signal(); - return mDone; -} - -bool MtpClient::threadLoop() { - usb_host_run(mUsbHostContext, usb_device_added, usb_device_removed, usb_discovery_done, this); - return false; -} - -int MtpClient::usb_device_added(const char *devname, void* client_data) { - LOGD("usb_device_added %s\n", devname); - return ((MtpClient *)client_data)->usbDeviceAdded(devname); -} - -int MtpClient::usb_device_removed(const char *devname, void* client_data) { - LOGD("usb_device_removed %s\n", devname); - return ((MtpClient *)client_data)->usbDeviceRemoved(devname); -} - -int MtpClient::usb_discovery_done(void* client_data) { - LOGD("usb_discovery_done\n"); - return ((MtpClient *)client_data)->usbDiscoveryDone(); -} - -} // namespace android diff --git a/media/mtp/MtpClient.h b/media/mtp/MtpClient.h deleted file mode 100644 index fa5c527..0000000 --- a/media/mtp/MtpClient.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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. - */ - -#ifndef _MTP_CLIENT_H -#define _MTP_CLIENT_H - -#include "MtpTypes.h" - -#include <utils/threads.h> - -struct usb_host_context; - -namespace android { - -class MtpClientThread; - -class MtpClient { -private: - MtpDeviceList mDeviceList; - MtpClientThread* mThread; - Condition mThreadStartCondition; - Mutex mMutex; - struct usb_host_context* mUsbHostContext; - bool mDone; - -public: - MtpClient(); - virtual ~MtpClient(); - - bool start(); - void stop(); - - inline MtpDeviceList& getDeviceList() { return mDeviceList; } - MtpDevice* getDevice(int id); - - - virtual void deviceAdded(MtpDevice *device) = 0; - virtual void deviceRemoved(MtpDevice *device) = 0; - -private: - // these return true if we should stop monitoring USB and clean up - bool usbDeviceAdded(const char *devname); - bool usbDeviceRemoved(const char *devname); - bool usbDiscoveryDone(); - - friend class MtpClientThread; - bool threadLoop(); - static int usb_device_added(const char *devname, void* client_data); - static int usb_device_removed(const char *devname, void* client_data); - static int usb_discovery_done(void* client_data); -}; - -}; // namespace android - -#endif // _MTP_CLIENT_H diff --git a/media/mtp/MtpDevice.cpp b/media/mtp/MtpDevice.cpp index d22c72f..fb1b073 100644 --- a/media/mtp/MtpDevice.cpp +++ b/media/mtp/MtpDevice.cpp @@ -38,6 +38,136 @@ namespace android { +static bool isMtpDevice(uint16_t vendor, uint16_t product) { + // Sandisk Sansa Fuze + if (vendor == 0x0781 && product == 0x74c2) + return true; + // Samsung YP-Z5 + if (vendor == 0x04e8 && product == 0x503c) + return true; + return false; +} + +MtpDevice* MtpDevice::open(const char* deviceName, int fd) { + struct usb_device *device = usb_device_new(deviceName, fd); + if (!device) { + LOGE("usb_device_new failed for %s", deviceName); + return NULL; + } + + struct usb_descriptor_header* desc; + struct usb_descriptor_iter iter; + + usb_descriptor_iter_init(device, &iter); + + while ((desc = usb_descriptor_iter_next(&iter)) != NULL) { + if (desc->bDescriptorType == USB_DT_INTERFACE) { + struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc; + + if (interface->bInterfaceClass == USB_CLASS_STILL_IMAGE && + interface->bInterfaceSubClass == 1 && // Still Image Capture + interface->bInterfaceProtocol == 1) // Picture Transfer Protocol (PIMA 15470) + { + char* manufacturerName = usb_device_get_manufacturer_name(device); + char* productName = usb_device_get_product_name(device); + LOGD("Found camera: \"%s\" \"%s\"\n", manufacturerName, productName); + free(manufacturerName); + free(productName); + } else if (interface->bInterfaceClass == 0xFF && + interface->bInterfaceSubClass == 0xFF && + interface->bInterfaceProtocol == 0) { + char* interfaceName = usb_device_get_string(device, interface->iInterface); + if (!interfaceName) { + continue; + } else if (strcmp(interfaceName, "MTP")) { + free(interfaceName); + continue; + } + free(interfaceName); + + // Looks like an android style MTP device + char* manufacturerName = usb_device_get_manufacturer_name(device); + char* productName = usb_device_get_product_name(device); + LOGD("Found MTP device: \"%s\" \"%s\"\n", manufacturerName, productName); + free(manufacturerName); + free(productName); + } else { + // look for special cased devices based on vendor/product ID + // we are doing this mainly for testing purposes + uint16_t vendor = usb_device_get_vendor_id(device); + uint16_t product = usb_device_get_product_id(device); + if (!isMtpDevice(vendor, product)) { + // not an MTP or PTP device + continue; + } + // request MTP OS string and descriptor + // some music players need to see this before entering MTP mode. + char buffer[256]; + memset(buffer, 0, sizeof(buffer)); + int ret = usb_device_control_transfer(device, + USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_STANDARD, + USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | 0xEE, + 0, buffer, sizeof(buffer), 0); + printf("usb_device_control_transfer returned %d errno: %d\n", ret, errno); + if (ret > 0) { + printf("got MTP string %s\n", buffer); + ret = usb_device_control_transfer(device, + USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_VENDOR, 1, + 0, 4, buffer, sizeof(buffer), 0); + printf("OS descriptor got %d\n", ret); + } else { + printf("no MTP string\n"); + } + } + + // if we got here, then we have a likely MTP or PTP device + + // interface should be followed by three endpoints + struct usb_endpoint_descriptor *ep; + struct usb_endpoint_descriptor *ep_in_desc = NULL; + struct usb_endpoint_descriptor *ep_out_desc = NULL; + struct usb_endpoint_descriptor *ep_intr_desc = NULL; + for (int i = 0; i < 3; i++) { + ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter); + if (!ep || ep->bDescriptorType != USB_DT_ENDPOINT) { + LOGE("endpoints not found\n"); + usb_device_close(device); + return NULL; + } + if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK) { + if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + ep_in_desc = ep; + else + ep_out_desc = ep; + } else if (ep->bmAttributes == USB_ENDPOINT_XFER_INT && + ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) { + ep_intr_desc = ep; + } + } + if (!ep_in_desc || !ep_out_desc || !ep_intr_desc) { + LOGE("endpoints not found\n"); + usb_device_close(device); + return NULL; + } + + if (usb_device_claim_interface(device, interface->bInterfaceNumber)) { + LOGE("usb_device_claim_interface failed errno: %d\n", errno); + usb_device_close(device); + return NULL; + } + + MtpDevice* mtpDevice = new MtpDevice(device, interface->bInterfaceNumber, + ep_in_desc, ep_out_desc, ep_intr_desc); + mtpDevice->initialize(); + return mtpDevice; + } + } + + usb_device_close(device); + LOGE("device not found"); + return NULL; +} + MtpDevice::MtpDevice(struct usb_device* device, int interface, const struct usb_endpoint_descriptor *ep_in, const struct usb_endpoint_descriptor *ep_out, @@ -49,7 +179,6 @@ MtpDevice::MtpDevice(struct usb_device* device, int interface, mRequestOut(NULL), mRequestIntr(NULL), mDeviceInfo(NULL), - mID(usb_device_get_unique_id(device)), mSessionID(0), mTransactionID(0), mReceivedResponse(false) @@ -106,6 +235,7 @@ void MtpDevice::print() { MtpProperty* property = getDevicePropDesc(propCode); if (property) { property->print(); + delete property; } } } @@ -122,11 +252,13 @@ void MtpDevice::print() { for (int j = 0; j < props->size(); j++) { MtpObjectProperty prop = (*props)[j]; MtpProperty* property = getObjectPropDesc(prop, format); - if (property) + if (property) { property->print(); - else + delete property; + } else { LOGE("could not fetch property: %s", MtpDebug::getObjectPropCodeName(prop)); + } } } } @@ -362,18 +494,24 @@ bool MtpDevice::deleteObject(MtpObjectHandle handle) { MtpObjectHandle MtpDevice::getParent(MtpObjectHandle handle) { MtpObjectInfo* info = getObjectInfo(handle); - if (info) - return info->mParent; - else + if (info) { + MtpObjectHandle parent = info->mParent; + delete info; + return parent; + } else { return -1; + } } MtpObjectHandle MtpDevice::getStorageID(MtpObjectHandle handle) { MtpObjectInfo* info = getObjectInfo(handle); - if (info) - return info->mStorageID; - else + if (info) { + MtpObjectHandle storageId = info->mStorageID; + delete info; + return storageId; + } else { return -1; + } } MtpObjectPropertyList* MtpDevice::getObjectPropsSupported(MtpObjectFormat format) { @@ -430,6 +568,98 @@ MtpProperty* MtpDevice::getObjectPropDesc(MtpObjectProperty code, MtpObjectForma return NULL; } +bool MtpDevice::readObject(MtpObjectHandle handle, + bool (* callback)(void* data, int offset, int length, void* clientData), + int objectSize, void* clientData) { + Mutex::Autolock autoLock(mMutex); + bool result = false; + + mRequest.reset(); + mRequest.setParameter(1, handle); + if (sendRequest(MTP_OPERATION_GET_OBJECT) + && mData.readDataHeader(mRequestIn1)) { + uint32_t length = mData.getContainerLength(); + if (length - MTP_CONTAINER_HEADER_SIZE != objectSize) { + LOGE("readObject error objectSize: %d, length: %d", + objectSize, length); + goto fail; + } + length -= MTP_CONTAINER_HEADER_SIZE; + uint32_t remaining = length; + int offset = 0; + + int initialDataLength = 0; + void* initialData = mData.getData(initialDataLength); + if (initialData) { + if (initialDataLength > 0) { + if (!callback(initialData, 0, initialDataLength, clientData)) + goto fail; + remaining -= initialDataLength; + offset += initialDataLength; + } + free(initialData); + } + + // USB reads greater than 16K don't work + char buffer1[16384], buffer2[16384]; + mRequestIn1->buffer = buffer1; + mRequestIn2->buffer = buffer2; + struct usb_request* req = mRequestIn1; + void* writeBuffer = NULL; + int writeLength = 0; + + while (remaining > 0 || writeBuffer) { + if (remaining > 0) { + // queue up a read request + req->buffer_length = (remaining > sizeof(buffer1) ? sizeof(buffer1) : remaining); + if (mData.readDataAsync(req)) { + LOGE("readDataAsync failed"); + goto fail; + } + } else { + req = NULL; + } + + if (writeBuffer) { + // write previous buffer + if (!callback(writeBuffer, offset, writeLength, clientData)) { + LOGE("write failed"); + // wait for pending read before failing + if (req) + mData.readDataWait(mDevice); + goto fail; + } + offset += writeLength; + writeBuffer = NULL; + } + + // wait for read to complete + if (req) { + int read = mData.readDataWait(mDevice); + if (read < 0) + goto fail; + + if (read > 0) { + writeBuffer = req->buffer; + writeLength = read; + remaining -= read; + req = (req == mRequestIn1 ? mRequestIn2 : mRequestIn1); + } else { + writeBuffer = NULL; + } + } + } + + MtpResponseCode response = readResponse(); + if (response == MTP_RESPONSE_OK) + result = true; + } + +fail: + return result; +} + + // reads the object's data and writes it to the specified file path bool MtpDevice::readObject(MtpObjectHandle handle, const char* destPath, int group, int perm) { LOGD("readObject: %s", destPath); @@ -462,8 +692,10 @@ bool MtpDevice::readObject(MtpObjectHandle handle, const char* destPath, int gro void* initialData = mData.getData(initialDataLength); if (initialData) { if (initialDataLength > 0) { - if (write(fd, initialData, initialDataLength) != initialDataLength) + if (write(fd, initialData, initialDataLength) != initialDataLength) { + free(initialData); goto fail; + } remaining -= initialDataLength; } free(initialData); @@ -507,10 +739,14 @@ bool MtpDevice::readObject(MtpObjectHandle handle, const char* destPath, int gro if (read < 0) goto fail; - writeBuffer = req->buffer; - writeLength = read; - remaining -= read; - req = (req == mRequestIn1 ? mRequestIn2 : mRequestIn1); + if (read > 0) { + writeBuffer = req->buffer; + writeLength = read; + remaining -= read; + req = (req == mRequestIn1 ? mRequestIn2 : mRequestIn1); + } else { + writeBuffer = NULL; + } } } diff --git a/media/mtp/MtpDevice.h b/media/mtp/MtpDevice.h index d0a0fb3..b69203e 100644 --- a/media/mtp/MtpDevice.h +++ b/media/mtp/MtpDevice.h @@ -45,9 +45,6 @@ private: MtpDeviceInfo* mDeviceInfo; MtpPropertyList mDeviceProperties; - // a unique ID for the device - int mID; - // current session ID MtpSessionID mSessionID; // current transaction ID @@ -67,9 +64,10 @@ public: const struct usb_endpoint_descriptor *ep_in, const struct usb_endpoint_descriptor *ep_out, const struct usb_endpoint_descriptor *ep_intr); - virtual ~MtpDevice(); - inline int getID() const { return mID; } + static MtpDevice* open(const char* deviceName, int fd); + + virtual ~MtpDevice(); void initialize(); void close(); @@ -97,7 +95,11 @@ public: MtpProperty* getDevicePropDesc(MtpDeviceProperty code); MtpProperty* getObjectPropDesc(MtpObjectProperty code, MtpObjectFormat format); - bool readObject(MtpObjectHandle handle, const char* destPath, int group, + bool readObject(MtpObjectHandle handle, + bool (* callback)(void* data, int offset, + int length, void* clientData), + int objectSize, void* clientData); + bool readObject(MtpObjectHandle handle, const char* destPath, int group, int perm); private: diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp index be004d2..853a5af 100644 --- a/media/mtp/MtpServer.cpp +++ b/media/mtp/MtpServer.cpp @@ -343,8 +343,9 @@ MtpResponseCode MtpServer::doGetDeviceInfo() { mData.putAUInt16(deviceProperties); // Device Properties Supported mData.putAUInt16(captureFormats); // Capture Formats mData.putAUInt16(playbackFormats); // Playback Formats - // FIXME - string.set("Google, Inc."); + + property_get("ro.product.manufacturer", prop_value, "unknown manufacturer"); + string.set(prop_value); mData.putString(string); // Manufacturer property_get("ro.product.model", prop_value, "MTP Device"); diff --git a/media/tests/CameraBrowser/Android.mk b/media/tests/CameraBrowser/Android.mk new file mode 100644 index 0000000..1d81129 --- /dev/null +++ b/media/tests/CameraBrowser/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := CameraBrowser + +include $(BUILD_PACKAGE) diff --git a/media/tests/CameraBrowser/AndroidManifest.xml b/media/tests/CameraBrowser/AndroidManifest.xml new file mode 100644 index 0000000..f167f4b --- /dev/null +++ b/media/tests/CameraBrowser/AndroidManifest.xml @@ -0,0 +1,31 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.camerabrowser"> + + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.ACCESS_USB" /> + + <application android:label="@string/app_label" + android:name="com.android.camerabrowser.CameraBrowserApplication"> + + <activity android:name="CameraBrowser" android:label="Camera Browser"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <activity android:name="StorageBrowser" /> + <activity android:name="ObjectBrowser" /> + <activity android:name="ObjectViewer" /> + +<!-- + <receiver android:name="UsbReceiver"> + <intent-filter> + <action android:name="android.hardware.action.USB_DEVICE_ATTACHED" /> + </intent-filter> + </receiver> +--> + </application> + + +</manifest> diff --git a/media/tests/CameraBrowser/res/layout/object_info.xml b/media/tests/CameraBrowser/res/layout/object_info.xml new file mode 100644 index 0000000..a0499f2 --- /dev/null +++ b/media/tests/CameraBrowser/res/layout/object_info.xml @@ -0,0 +1,169 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2008, 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. +*/ +--> +<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/object_info" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <TableRow> + <TextView android:id="@+id/name_label" + android:text="@string/name_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/name" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/size_label" + android:text="@string/size_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/size" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/format_label" + android:text="@string/format_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/format" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/thumb_width_label" + android:text="@string/thumb_width_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/thumb_width" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/thumb_height_label" + android:text="@string/thumb_height_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/thumb_height" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/thumb_size_label" + android:text="@string/thumb_size_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/thumb_size" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/width_label" + android:text="@string/width_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/width" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/height_label" + android:text="@string/height_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/height" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/depth_label" + android:text="@string/depth_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/depth" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/sequence_label" + android:text="@string/sequence_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/sequence" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/created_label" + android:text="@string/created_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/created" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/modified_label" + android:text="@string/modified_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/modified" + style="@style/info_value" /> + </TableRow> + <TableRow> + <TextView android:id="@+id/keywords_label" + android:text="@string/keywords_label" + android:layout_gravity="right" + android:layout_marginRight="8dip" + style="@style/info_label" /> + + <TextView android:id="@+id/keywords" + style="@style/info_value" /> + </TableRow> + <TableRow> + <ImageView android:id="@+id/thumbnail" /> + </TableRow> + <TableRow> + <Button android:id="@+id/import_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/import_label"> + </Button> + <Button android:id="@+id/delete_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/delete_label"> + </Button> + </TableRow> +</TableLayout> + diff --git a/media/tests/CameraBrowser/res/layout/object_list.xml b/media/tests/CameraBrowser/res/layout/object_list.xml new file mode 100644 index 0000000..30c18bb --- /dev/null +++ b/media/tests/CameraBrowser/res/layout/object_list.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <ImageView android:id="@+id/thumbnail" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView android:id="@+id/name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:gravity="center_vertical" + android:paddingLeft="6dip" + android:minHeight="?android:attr/listPreferredItemHeight" /> +</LinearLayout> diff --git a/media/tests/CameraBrowser/res/values/strings.xml b/media/tests/CameraBrowser/res/values/strings.xml new file mode 100644 index 0000000..932aaec --- /dev/null +++ b/media/tests/CameraBrowser/res/values/strings.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="app_label">Camera Browser</string> + + <!-- for object info --> + <string name="name_label">Name: </string> + <string name="size_label">Size: </string> + <string name="format_label">Format: </string> + <string name="thumb_width_label">Thumb Width: </string> + <string name="thumb_height_label">Thumb Height: </string> + <string name="thumb_size_label">Thumb Size: </string> + <string name="width_label">Width: </string> + <string name="height_label">Height: </string> + <string name="depth_label">Depth: </string> + <string name="sequence_label">Sequence: </string> + <string name="created_label">Created: </string> + <string name="modified_label">Modified: </string> + <string name="keywords_label">Keywords: </string> + + <!-- button labels --> + <string name="import_label">Import</string> + <string name="delete_label">Delete</string> + + <!-- toasts --> + <string name="object_saved_message">Object saved</string> + <string name="save_failed_message">Could not save object</string> + <string name="object_deleted_message">Object deleted</string> + <string name="delete_failed_message">Could not delete object</string> + <string name="start_activity_failed_message">Import succeeded, but could not display object</string> + +</resources> diff --git a/media/tests/CameraBrowser/res/values/styles.xml b/media/tests/CameraBrowser/res/values/styles.xml new file mode 100644 index 0000000..c869985 --- /dev/null +++ b/media/tests/CameraBrowser/res/values/styles.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 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. +--> + +<resources> + <style name="info_label"> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_width">wrap_content</item> + <item name="android:textSize">14sp</item> + <item name="android:textStyle">bold</item> + <item name="android:paddingRight">4dip</item> + </style> + + <style name="info_value"> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_width">wrap_content</item> + <item name="android:textSize">14sp</item> + <item name="android:textStyle">normal</item> + </style> + +</resources> + diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java new file mode 100644 index 0000000..f642d93 --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java @@ -0,0 +1,139 @@ +/* + * 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.camerabrowser; + +import android.app.ListActivity; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.mtp.MtpClient; +import android.mtp.MtpDevice; +import android.mtp.MtpDeviceInfo; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.TwoLineListItem; + +import java.util.List; + + /** + * A list view displaying all connected cameras. + */ +public class CameraBrowser extends ListActivity implements MtpClient.Listener { + + private static final String TAG = "CameraBrowser"; + + private MtpClient mClient; + private List<MtpDevice> mDeviceList; + + private static final int MODEL_COLUMN = 0; + private static final int MANUFACTURER_COLUMN = 1; + private static final int COLUMN_COUNT = 2; + + private class CameraAdapter extends BaseAdapter { + private final Context mContext; + private final LayoutInflater mInflater; + + public CameraAdapter(Context c) { + mContext = c; + mInflater = (LayoutInflater)c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + public int getCount() { + return mDeviceList.size(); + } + + public Object getItem(int position) { + return mDeviceList.get(position); + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + TwoLineListItem view; + if (convertView == null) { + view = (TwoLineListItem)mInflater.inflate( + android.R.layout.simple_list_item_2, parent, false); + } else { + view = (TwoLineListItem)convertView; + } + + TextView textView1 = (TextView)view.findViewById(com.android.internal.R.id.text1); + TextView textView2 = (TextView)view.findViewById(com.android.internal.R.id.text2); + MtpDevice device = mDeviceList.get(position); + MtpDeviceInfo info = device.getDeviceInfo(); + if (info != null) { + textView1.setText(info.getManufacturer()); + textView2.setText(info.getModel()); + } else { + textView1.setText("???"); + textView2.setText("???"); + } + return view; + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mClient = ((CameraBrowserApplication)getApplication()).getMtpClient(); + mClient.addListener(this); + mDeviceList = mClient.getDeviceList(); + } + + @Override + protected void onResume() { + super.onResume(); + reload(); + } + + @Override + protected void onDestroy() { + mClient.removeListener(this); + super.onDestroy(); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Intent intent = new Intent(this, StorageBrowser.class); + intent.putExtra("device", mDeviceList.get(position).getDeviceName()); + startActivity(intent); + } + + private void reload() { + setListAdapter(new CameraAdapter(this)); + } + + public void deviceAdded(MtpDevice device) { + Log.d(TAG, "deviceAdded: " + device.getDeviceName()); + mDeviceList = mClient.getDeviceList(); + reload(); + } + + public void deviceRemoved(MtpDevice device) { + Log.d(TAG, "deviceRemoved: " + device.getDeviceName()); + mDeviceList = mClient.getDeviceList(); + reload(); + } +} diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowserApplication.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowserApplication.java new file mode 100644 index 0000000..6f1edfea --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowserApplication.java @@ -0,0 +1,41 @@ +/* + * 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.camerabrowser; + +import android.app.Application; +import android.mtp.MtpClient; + + +public class CameraBrowserApplication extends Application { + + private MtpClient mClient; + + @Override + public void onCreate() { + mClient = new MtpClient(this); + } + + @Override + public void onTerminate() { + mClient.close(); + mClient = null; + } + + public MtpClient getMtpClient() { + return mClient; + } +} diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/DeviceDisconnectedReceiver.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/DeviceDisconnectedReceiver.java new file mode 100644 index 0000000..736af1f --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/DeviceDisconnectedReceiver.java @@ -0,0 +1,52 @@ +/* + * 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.camerabrowser; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.UsbManager; +import android.util.Log; + +public class DeviceDisconnectedReceiver extends BroadcastReceiver { + + private static final String TAG = "DeviceDisconnectedReceiver"; + + private final Activity mActivity; + private final String mDeviceName; + + public DeviceDisconnectedReceiver(Activity activity, String deviceName) { + mActivity = activity; + mDeviceName = deviceName; + + IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED); + activity.registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + String deviceName = intent.getStringExtra(UsbManager.EXTRA_DEVICE_NAME); + Log.d(TAG, "ACTION_USB_DEVICE_DETACHED " + deviceName); + + // close our activity if the device it is displaying is disconnected + if (deviceName.equals(mDeviceName)) { + mActivity.finish(); + } + } +}
\ No newline at end of file diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java new file mode 100644 index 0000000..82251d9 --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java @@ -0,0 +1,147 @@ +/* + * 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.camerabrowser; + +import android.app.ListActivity; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.mtp.MtpClient; +import android.mtp.MtpConstants; +import android.mtp.MtpDevice; +import android.mtp.MtpObjectInfo; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.List; + + /** + * A list view displaying all objects within a container (folder or storage unit). + */ +public class ObjectBrowser extends ListActivity { + + private static final String TAG = "ObjectBrowser"; + + private MtpClient mClient; + private List<MtpObjectInfo> mObjectList; + private String mDeviceName; + private int mStorageID; + private int mObjectID; + private DeviceDisconnectedReceiver mDisconnectedReceiver; + + private class ObjectAdapter extends BaseAdapter { + private final Context mContext; + private final LayoutInflater mInflater; + + public ObjectAdapter(Context c) { + mContext = c; + mInflater = (LayoutInflater)c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + public int getCount() { + if (mObjectList == null) { + return 0; + } else { + return mObjectList.size(); + } + } + + public Object getItem(int position) { + return mObjectList.get(position); + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = mInflater.inflate(R.layout.object_list, parent, false); + } else { + view = convertView; + } + + TextView nameView = (TextView)view.findViewById(R.id.name); + MtpObjectInfo info = mObjectList.get(position); + nameView.setText(info.getName()); + + int thumbFormat = info.getThumbFormat(); + if (thumbFormat == MtpConstants.FORMAT_EXIF_JPEG + || thumbFormat == MtpConstants.FORMAT_JFIF) { + byte[] thumbnail = mClient.getThumbnail(mDeviceName, info.getObjectHandle()); + if (thumbnail != null) { + Bitmap bitmap = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length); + if (bitmap != null) { + ImageView thumbView = (ImageView)view.findViewById(R.id.thumbnail); + thumbView.setImageBitmap(bitmap); + } + } + } + return view; + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mClient = ((CameraBrowserApplication)getApplication()).getMtpClient(); + mDeviceName = getIntent().getStringExtra("device"); + mStorageID = getIntent().getIntExtra("storage", 0); + mObjectID = getIntent().getIntExtra("object", 0); + mDisconnectedReceiver = new DeviceDisconnectedReceiver(this, mDeviceName); + } + + @Override + protected void onResume() { + super.onResume(); + + mObjectList = mClient.getObjectList(mDeviceName, mStorageID, mObjectID); + setListAdapter(new ObjectAdapter(this)); + } + + @Override + protected void onDestroy() { + unregisterReceiver(mDisconnectedReceiver); + super.onDestroy(); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + MtpObjectInfo info = mObjectList.get(position); + Intent intent; + if (info.getFormat() == MtpConstants.FORMAT_ASSOCIATION) { + intent = new Intent(this, ObjectBrowser.class); + } else { + intent = new Intent(this, ObjectViewer.class); + } + intent.putExtra("device", mDeviceName); + intent.putExtra("storage", mStorageID); + intent.putExtra("object", info.getObjectHandle()); + startActivity(intent); + } +} diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java new file mode 100644 index 0000000..e9ea9f3 --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java @@ -0,0 +1,203 @@ +/* + * 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.camerabrowser; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.MediaScannerConnection; +import android.media.MediaScannerConnection.MediaScannerConnectionClient; +import android.mtp.MtpClient; +import android.mtp.MtpConstants; +import android.mtp.MtpObjectInfo; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.File; +import java.util.Date; + +/** + * A view to display the properties of an object. + */ +public class ObjectViewer extends Activity implements View.OnClickListener { + + private static final String TAG = "ObjectViewer"; + + private MtpClient mClient; + private String mDeviceName; + private int mStorageID; + private int mObjectID; + private String mFileName; + private Button mImportButton; + private Button mDeleteButton; + private DeviceDisconnectedReceiver mDisconnectedReceiver; + + private final class ScannerClient implements MediaScannerConnectionClient { + private final Context mContext; + private String mPath; + + public ScannerClient(Context context) { + mContext = context; + } + + public void setScanPath(String path) { + mPath = path; + } + + @Override + public void onMediaScannerConnected() { + mScannerConnection.scanFile(mPath, null); + } + + @Override + public void onScanCompleted(String path, Uri uri) { + mScannerConnection.disconnect(); + + // try to start an activity to view the file + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + try { + mContext.startActivity(intent); + } catch (ActivityNotFoundException e) { + Toast.makeText(mContext, R.string.start_activity_failed_message, + Toast.LENGTH_SHORT).show(); + } + } + } + + private MediaScannerConnection mScannerConnection; + private ScannerClient mScannerClient; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mClient = ((CameraBrowserApplication)getApplication()).getMtpClient(); + + setContentView(R.layout.object_info); + + mImportButton = (Button)findViewById(R.id.import_button); + mImportButton.setOnClickListener(this); + mDeleteButton = (Button)findViewById(R.id.delete_button); + mDeleteButton.setOnClickListener(this); + + mDeviceName = getIntent().getStringExtra("device"); + mStorageID = getIntent().getIntExtra("storage", 0); + mObjectID = getIntent().getIntExtra("object", 0); + mDisconnectedReceiver = new DeviceDisconnectedReceiver(this, mDeviceName); + mScannerClient = new ScannerClient(this); + mScannerConnection = new MediaScannerConnection(this, mScannerClient); + } + + @Override + protected void onResume() { + super.onResume(); + + MtpObjectInfo info = mClient.getObjectInfo(mDeviceName, mObjectID); + if (info != null) { + TextView view = (TextView)findViewById(R.id.name); + mFileName = info.getName(); + view.setText(mFileName); + view = (TextView)findViewById(R.id.format); + view.setText(Integer.toHexString(info.getFormat()).toUpperCase()); + view = (TextView)findViewById(R.id.size); + view.setText(Long.toString(info.getCompressedSize())); + view = (TextView)findViewById(R.id.thumb_width); + view.setText(Long.toString(info.getThumbPixWidth())); + view = (TextView)findViewById(R.id.thumb_height); + view.setText(Long.toString(info.getThumbPixHeight())); + view = (TextView)findViewById(R.id.thumb_size); + view.setText(Long.toString(info.getThumbCompressedSize())); + view = (TextView)findViewById(R.id.width); + view.setText(Long.toString(info.getImagePixWidth())); + view = (TextView)findViewById(R.id.height); + view.setText(Long.toString(info.getImagePixHeight())); + view = (TextView)findViewById(R.id.depth); + view.setText(Long.toString(info.getImagePixDepth())); + view = (TextView)findViewById(R.id.sequence); + view.setText(Long.toString(info.getSequenceNumber())); + view = (TextView)findViewById(R.id.created); + Date date = new Date(info.getDateCreated() * 1000); + view.setText(date.toString()); + view = (TextView)findViewById(R.id.modified); + date = new Date(info.getDateModified() * 1000); + view.setText(date.toString()); + view = (TextView)findViewById(R.id.keywords); + view.setText(info.getKeywords()); + int thumbFormat = info.getThumbFormat(); + if (thumbFormat == MtpConstants.FORMAT_EXIF_JPEG + || thumbFormat == MtpConstants.FORMAT_JFIF) { + byte[] thumbnail = mClient.getThumbnail(mDeviceName, info.getObjectHandle()); + if (thumbnail != null) { + Bitmap bitmap = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length); + if (bitmap != null) { + ImageView thumbView = (ImageView)findViewById(R.id.thumbnail); + thumbView.setImageBitmap(bitmap); + } + } + } + } + } + + @Override + protected void onDestroy() { + unregisterReceiver(mDisconnectedReceiver); + super.onDestroy(); + } + + private void importObject() { + // copy file to /mnt/sdcard/imported/<filename> + File dest = Environment.getExternalStorageDirectory(); + dest = new File(dest, "imported"); + dest.mkdirs(); + dest = new File(dest, mFileName); + + if (mClient.importFile(mDeviceName, mObjectID, dest.getAbsolutePath())) { + Toast.makeText(this, R.string.object_saved_message, Toast.LENGTH_SHORT).show(); + + mScannerClient.setScanPath(dest.getAbsolutePath()); + mScannerConnection.connect(); + } else { + Toast.makeText(this, R.string.save_failed_message, Toast.LENGTH_SHORT).show(); + } + } + + private void deleteObject() { + if (mClient.deleteObject(mDeviceName, mObjectID)) { + Toast.makeText(this, R.string.object_deleted_message, Toast.LENGTH_SHORT).show(); + finish(); + } else { + Toast.makeText(this, R.string.delete_failed_message, Toast.LENGTH_SHORT).show(); + } + } + + public void onClick(View v) { + if (v == mImportButton) { + importObject(); + } else if (v == mDeleteButton) { + deleteObject(); + } + } +} diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java new file mode 100644 index 0000000..7d5a5da --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java @@ -0,0 +1,121 @@ +/* + * 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.camerabrowser; + +import android.app.ListActivity; +import android.content.Context; +import android.content.Intent; +import android.mtp.MtpClient; +import android.mtp.MtpDevice; +import android.mtp.MtpStorageInfo; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.List; + +/** + * A list view displaying all storage units on a device. + */ +public class StorageBrowser extends ListActivity { + + private static final String TAG = "StorageBrowser"; + + private MtpClient mClient; + private String mDeviceName; + private List<MtpStorageInfo> mStorageList; + private DeviceDisconnectedReceiver mDisconnectedReceiver; + + private class StorageAdapter extends BaseAdapter { + private final Context mContext; + private final LayoutInflater mInflater; + + public StorageAdapter(Context c) { + mContext = c; + mInflater = (LayoutInflater)c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + public int getCount() { + if (mStorageList == null) { + return 0; + } else { + return mStorageList.size(); + } + } + + public Object getItem(int position) { + return mStorageList.get(position); + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + TextView view; + if (convertView == null) { + view = (TextView)mInflater.inflate( + android.R.layout.simple_list_item_1, parent, false); + } else { + view = (TextView)convertView; + } + + MtpStorageInfo info = mStorageList.get(position); + if (info != null) { + view.setText(info.getDescription()); + } else { + view.setText("???"); + } + return view; + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mClient = ((CameraBrowserApplication)getApplication()).getMtpClient(); + mDeviceName = getIntent().getStringExtra("device"); + mDisconnectedReceiver = new DeviceDisconnectedReceiver(this, mDeviceName); + } + + @Override + protected void onResume() { + super.onResume(); + mStorageList = mClient.getStorageList(mDeviceName); + setListAdapter(new StorageAdapter(this)); + } + + @Override + protected void onDestroy() { + unregisterReceiver(mDisconnectedReceiver); + super.onDestroy(); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Intent intent = new Intent(this, ObjectBrowser.class); + intent.putExtra("device", mDeviceName); + intent.putExtra("storage", mStorageList.get(position).getStorageId()); + startActivity(intent); + } +} diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.java new file mode 100644 index 0000000..22d9443 --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.java @@ -0,0 +1,48 @@ +/* + * 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.camerabrowser; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.hardware.UsbDevice; +import android.hardware.UsbManager; +import android.mtp.MtpClient; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.Log; + +public class UsbReceiver extends BroadcastReceiver +{ + private static final String TAG = "UsbReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "onReceive " + intent); + if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { + UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (MtpClient.isCamera(device)) { + String deviceName = device.getDeviceName(); + Log.d(TAG, "Got camera: " + deviceName); + intent = new Intent(context, StorageBrowser.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("device", deviceName); + context.startActivity(intent); + } + } + } +} |