diff options
Diffstat (limited to 'media')
106 files changed, 11100 insertions, 164 deletions
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index f1fa1e8..31e4631 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -31,9 +31,9 @@ public class AudioFormat { public static final int ENCODING_INVALID = 0; /** Default audio data format */ public static final int ENCODING_DEFAULT = 1; - /** Audio data format: PCM 16 bit per sample */ + /** Audio data format: PCM 16 bit per sample. Guaranteed to be supported by devices. */ public static final int ENCODING_PCM_16BIT = 2; // accessed by native code - /** Audio data format: PCM 8 bit per sample */ + /** Audio data format: PCM 8 bit per sample. Not guaranteed to be supported by devices. */ public static final int ENCODING_PCM_8BIT = 3; // accessed by native code /** Invalid audio channel configuration */ diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index bbbba74..b23dcde 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1525,4 +1525,22 @@ public class AudioManager { * {@hide} */ private IBinder mICallBack = new Binder(); + + /** + * Checks whether the phone is in silent mode, with or without vibrate. + * + * @return true if phone is in silent mode, with or without vibrate. + * + * @see #getRingerMode() + * + * @hide pending API Council approval + */ + public boolean isSilentMode() { + int ringerMode = getRingerMode(); + boolean silentMode = + (ringerMode == RINGER_MODE_SILENT) || + (ringerMode == RINGER_MODE_VIBRATE); + return silentMode; + } + } diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index c48eaad..c567a6e 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -194,11 +194,13 @@ public class AudioRecord * Class constructor. * @param audioSource the recording source. See {@link MediaRecorder.AudioSource} for * recording source definitions. - * @param sampleRateInHz the sample rate expressed in Hertz. Examples of rates are (but - * not limited to) 44100, 22050 and 11025. + * @param sampleRateInHz the sample rate expressed in Hertz. 44100Hz is currently the only + * rate that is guaranteed to work on all devices, but other rates such as 22050, + * 16000, and 11025 may work on some devices. * @param channelConfig describes the configuration of the audio channels. * See {@link AudioFormat#CHANNEL_IN_MONO} and - * {@link AudioFormat#CHANNEL_IN_STEREO} + * {@link AudioFormat#CHANNEL_IN_STEREO}. {@link AudioFormat#CHANNEL_IN_MONO} is guaranteed + * to work on all devices. * @param audioFormat the format in which the audio data is represented. * See {@link AudioFormat#ENCODING_PCM_16BIT} and * {@link AudioFormat#ENCODING_PCM_8BIT} @@ -444,6 +446,8 @@ public class AudioRecord * or {@link #ERROR} if the implementation was unable to query the hardware for its * output properties, * or the minimum buffer size expressed in bytes. + * @see #AudioRecord(int, int, int, int, int) for more information on valid + * configuration values. */ static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) { int channelCount = 0; diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java index 6e527d9..a346ae4 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -20,6 +20,7 @@ import android.content.ContentValues; import android.provider.MediaStore.Audio; import android.provider.MediaStore.Images; import android.provider.MediaStore.Video; +import android.provider.Mtp; import android.media.DecoderCapabilities; import android.media.DecoderCapabilities.VideoDecoder; import android.media.DecoderCapabilities.AudioDecoder; @@ -96,15 +97,32 @@ public class MediaFile { } } - private static HashMap<String, MediaFileType> sFileTypeMap + private static HashMap<String, MediaFileType> sFileTypeMap = new HashMap<String, MediaFileType>(); - private static HashMap<String, Integer> sMimeTypeMap - = new HashMap<String, Integer>(); + private static HashMap<String, Integer> sMimeTypeMap + = new HashMap<String, Integer>(); + // maps file extension to MTP format code + private static HashMap<String, Integer> sFileTypeToFormatMap + = new HashMap<String, Integer>(); + // maps mime type to MTP format code + private static HashMap<String, Integer> sMimeTypeToFormatMap + = new HashMap<String, Integer>(); + // maps MTP format code to mime type + private static HashMap<Integer, String> sFormatToMimeTypeMap + = new HashMap<Integer, String>(); + static void addFileType(String extension, int fileType, String mimeType) { sFileTypeMap.put(extension, new MediaFileType(fileType, mimeType)); sMimeTypeMap.put(mimeType, Integer.valueOf(fileType)); } + static void addFileType(String extension, int fileType, String mimeType, int mtpFormatCode) { + addFileType(extension, fileType, mimeType); + sFileTypeToFormatMap.put(extension, Integer.valueOf(mtpFormatCode)); + sMimeTypeToFormatMap.put(mimeType, Integer.valueOf(mtpFormatCode)); + sFormatToMimeTypeMap.put(mtpFormatCode, mimeType); + } + private static boolean isWMAEnabled() { List<AudioDecoder> decoders = DecoderCapabilities.getAudioDecoders(); for (AudioDecoder decoder: decoders) { @@ -126,17 +144,17 @@ public class MediaFile { } static { - addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg"); - addFileType("M4A", FILE_TYPE_M4A, "audio/mp4"); - addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav"); + addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg", Mtp.Object.FORMAT_MP3); + addFileType("M4A", FILE_TYPE_M4A, "audio/mp4", Mtp.Object.FORMAT_MPEG); + addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav", Mtp.Object.FORMAT_WAV); addFileType("AMR", FILE_TYPE_AMR, "audio/amr"); addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb"); if (isWMAEnabled()) { - addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma"); + addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma", Mtp.Object.FORMAT_WMA); } - addFileType("OGG", FILE_TYPE_OGG, "application/ogg"); - addFileType("OGA", FILE_TYPE_OGG, "application/ogg"); - addFileType("AAC", FILE_TYPE_AAC, "audio/aac"); + addFileType("OGG", FILE_TYPE_OGG, "application/ogg", Mtp.Object.FORMAT_OGG); + addFileType("OGA", FILE_TYPE_OGG, "application/ogg", Mtp.Object.FORMAT_OGG); + addFileType("AAC", FILE_TYPE_AAC, "audio/aac", Mtp.Object.FORMAT_AAC); addFileType("MKA", FILE_TYPE_MKA, "audio/x-matroska"); addFileType("MID", FILE_TYPE_MID, "audio/midi"); @@ -148,32 +166,32 @@ public class MediaFile { addFileType("RTX", FILE_TYPE_MID, "audio/midi"); addFileType("OTA", FILE_TYPE_MID, "audio/midi"); - addFileType("MPEG", FILE_TYPE_MP4, "video/mpeg"); - addFileType("MP4", FILE_TYPE_MP4, "video/mp4"); - addFileType("M4V", FILE_TYPE_M4V, "video/mp4"); - addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp"); - addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp"); - addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2"); - addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2"); + addFileType("MPEG", FILE_TYPE_MP4, "video/mpeg", Mtp.Object.FORMAT_MPEG); + addFileType("MP4", FILE_TYPE_MP4, "video/mp4", Mtp.Object.FORMAT_MPEG); + addFileType("M4V", FILE_TYPE_M4V, "video/mp4", Mtp.Object.FORMAT_MPEG); + addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp", Mtp.Object.FORMAT_3GP_CONTAINER); + addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp", Mtp.Object.FORMAT_3GP_CONTAINER); + addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2", Mtp.Object.FORMAT_3GP_CONTAINER); + addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2", Mtp.Object.FORMAT_3GP_CONTAINER); addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska"); addFileType("WEBM", FILE_TYPE_MKV, "video/x-matroska"); addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts"); if (isWMVEnabled()) { - addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv"); + addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv", Mtp.Object.FORMAT_WMV); addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf"); } - addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg"); - addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg"); - addFileType("GIF", FILE_TYPE_GIF, "image/gif"); - addFileType("PNG", FILE_TYPE_PNG, "image/png"); - addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp"); + addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg", Mtp.Object.FORMAT_EXIF_JPEG); + addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg", Mtp.Object.FORMAT_EXIF_JPEG); + addFileType("GIF", FILE_TYPE_GIF, "image/gif", Mtp.Object.FORMAT_GIF); + addFileType("PNG", FILE_TYPE_PNG, "image/png", Mtp.Object.FORMAT_PNG); + addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp", Mtp.Object.FORMAT_BMP); addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp"); - addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl"); - addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls"); - addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl"); + addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl", Mtp.Object.FORMAT_M3U_PLAYLIST); + addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls", Mtp.Object.FORMAT_PLS_PLAYLIST); + addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl", Mtp.Object.FORMAT_WPL_PLAYLIST); // compute file extensions list for native Media Scanner StringBuilder builder = new StringBuilder(); @@ -222,4 +240,25 @@ public class MediaFile { return (value == null ? 0 : value.intValue()); } + public static int getFormatCode(String fileName, String mimeType) { + if (mimeType != null) { + Integer value = sMimeTypeToFormatMap.get(mimeType); + if (value != null) { + return value.intValue(); + } + } + int lastDot = fileName.lastIndexOf('.'); + if (lastDot > 0) { + String extension = fileName.substring(lastDot + 1); + Integer value = sFileTypeToFormatMap.get(extension); + if (value != null) { + return value.intValue(); + } + } + return Mtp.Object.FORMAT_UNDEFINED; + } + + public static String getMimeTypeForFormatCode(int formatCode) { + return sFormatToMimeTypeMap.get(formatCode); + } } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 34a86ec..94f5c7a 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -282,6 +282,28 @@ public class MediaRecorder } /** + * Enables/Disables time lapse capture and sets its parameters. This method should + * be called after setProfile(). + * + * @param enableTimeLapse Pass true to enable time lapse capture, false to disable it. + * @param useStillCameraForTimeLapse Pass true to use still camera for capturing time lapse + * frames, false to use the video camera. + * @param timeBetweenTimeLapseFrameCaptureMs time between two captures of time lapse frames. + * @param encoderLevel the video encoder level. + */ + public void setTimeLapseParameters(boolean enableTimeLapse, + boolean useStillCameraForTimeLapse, + int timeBetweenTimeLapseFrameCaptureMs, int encoderLevel) { + setParameter(String.format("time-lapse-enable=%d", + (enableTimeLapse) ? 1 : 0)); + setParameter(String.format("use-still-camera-for-time-lapse=%d", + (useStillCameraForTimeLapse) ? 1 : 0)); + setParameter(String.format("time-between-time-lapse-frame-capture=%d", + timeBetweenTimeLapseFrameCaptureMs)); + setVideoEncoderLevel(encoderLevel); + } + + /** * Sets the format of the output file produced during recording. Call this * after setAudioSource()/setVideoSource() but before prepare(). * @@ -448,6 +470,16 @@ public class MediaRecorder } /** + * Sets the level of the encoder. Call this before prepare(). + * + * @param encoderLevel the video encoder level. + * @hide + */ + public void setVideoEncoderLevel(int encoderLevel) { + setParameter(String.format("video-param-encoder-level=%d", encoderLevel)); + } + + /** * Pass in the file descriptor of the file to be written. Call this after * setOutputFormat() but before prepare(). * diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 3333268..7d8ef58 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -305,6 +305,7 @@ public class MediaScanner private Uri mGenresUri; private Uri mPlaylistsUri; private boolean mProcessPlaylists, mProcessGenres; + private int mMtpObjectHandle; // used when scanning the image database so we know whether we have to prune // old thumbnail files @@ -555,7 +556,8 @@ public class MediaScanner mTitle = value; } else if (name.equalsIgnoreCase("artist") || name.startsWith("artist;")) { mArtist = value.trim(); - } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;")) { + } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;") + || name.equalsIgnoreCase("band") || name.startsWith("band;")) { mAlbumArtist = value.trim(); } else if (name.equalsIgnoreCase("album") || name.startsWith("album;")) { mAlbum = value.trim(); @@ -625,6 +627,9 @@ public class MediaScanner map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified); map.put(MediaStore.MediaColumns.SIZE, mFileSize); map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType); + if (mMtpObjectHandle != 0) { + map.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle); + } if (MediaFile.isVideoFileType(mFileType)) { map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaStore.UNKNOWN_STRING)); @@ -1227,6 +1232,14 @@ public class MediaScanner } } + public Uri scanMtpFile(String path, String volumeName, int objectHandle, int format) { + String mimeType = MediaFile.getMimeTypeForFormatCode(format); + mMtpObjectHandle = objectHandle; + Uri result = scanSingleFile(path, volumeName, mimeType); + mMtpObjectHandle = 0; + return result; + } + // returns the number of matching file/directory names, starting from the right private int matchPaths(String path1, String path2) { int result = 0; diff --git a/media/java/android/media/MtpClient.java b/media/java/android/media/MtpClient.java new file mode 100644 index 0000000..1aebcb8 --- /dev/null +++ b/media/java/android/media/MtpClient.java @@ -0,0 +1,104 @@ +/* + * 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.media; + +import android.os.ParcelFileDescriptor; +import android.util.Log; + +/** + * {@hide} + */ +public class MtpClient { + + private static final String TAG = "MtpClient"; + + private final Listener mListener; + + static { + System.loadLibrary("media_jni"); + } + + public MtpClient(Listener listener) { + native_setup(); + if (listener == null) { + throw new NullPointerException("MtpClient: listener is null"); + } + mListener = listener; + } + + @Override + protected void finalize() { + native_finalize(); + } + + public boolean start() { + return native_start(); + } + + public void stop() { + native_stop(); + } + + public boolean deleteObject(int deviceID, int objectID) { + return native_delete_object(deviceID, objectID); + } + + public int getParent(int deviceID, int objectID) { + return native_get_parent(deviceID, objectID); + } + + public int getStorageID(int deviceID, int objectID) { + return native_get_storage_id(deviceID, objectID); + } + + // create a file descriptor for reading the contents of an object over MTP + public ParcelFileDescriptor openFile(int deviceID, int objectID) { + return native_open_file(deviceID, objectID); + } + + public interface Listener { + // called when a new MTP device has been discovered + void deviceAdded(int id); + + // called when an MTP device has been removed + void deviceRemoved(int id); + } + + // called from native code + private void deviceAdded(int id) { + Log.d(TAG, "deviceAdded " + id); + mListener.deviceAdded(id); + } + + // called from native code + private void deviceRemoved(int id) { + Log.d(TAG, "deviceRemoved " + id); + mListener.deviceRemoved(id); + } + + // used by the JNI code + private int mNativeContext; + + private native final void native_setup(); + private native final void native_finalize(); + private native boolean native_start(); + private native void native_stop(); + private native boolean native_delete_object(int deviceID, int objectID); + private native int native_get_parent(int deviceID, int objectID); + private native int native_get_storage_id(int deviceID, int objectID); + private native ParcelFileDescriptor native_open_file(int deviceID, int objectID); +} diff --git a/media/java/android/media/MtpCursor.java b/media/java/android/media/MtpCursor.java new file mode 100644 index 0000000..6ecfd0d --- /dev/null +++ b/media/java/android/media/MtpCursor.java @@ -0,0 +1,223 @@ +/* + * 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.media; + +import android.database.AbstractWindowedCursor; +import android.database.CursorWindow; +import android.provider.Mtp; +import android.util.Log; + +import java.util.HashMap; + +/** + * Cursor class for MTP content provider + * @hide + */ +public final class MtpCursor extends AbstractWindowedCursor { + static final String TAG = "MtpCursor"; + static final int NO_COUNT = -1; + + /* constants for mQueryType */ + public static final int DEVICE = 1; + public static final int DEVICE_ID = 2; + public static final int STORAGE = 3; + public static final int STORAGE_ID = 4; + public static final int OBJECT = 5; + public static final int OBJECT_ID = 6; + public static final int STORAGE_CHILDREN = 7; + public static final int OBJECT_CHILDREN = 8; + + private int mQueryType; + private int mDeviceID; + private int mStorageID; + private int mQbjectID; + + /** The names of the columns in the projection */ + private String[] mColumns; + + /** The number of rows in the cursor */ + private int mCount = NO_COUNT; + + private final MtpClient mClient; + + public MtpCursor(MtpClient client, int queryType, int deviceID, int storageID, int objectID, + String[] projection) { + + mClient = client; + mQueryType = queryType; + mDeviceID = deviceID; + mStorageID = storageID; + mQbjectID = objectID; + mColumns = projection; + + HashMap<String, Integer> map; + switch (queryType) { + case DEVICE: + case DEVICE_ID: + map = sDeviceProjectionMap; + break; + case STORAGE: + case STORAGE_ID: + map = sStorageProjectionMap; + break; + case OBJECT: + case OBJECT_ID: + case STORAGE_CHILDREN: + case OBJECT_CHILDREN: + map = sObjectProjectionMap; + break; + default: + throw new IllegalArgumentException("unknown query type " + queryType); + } + + int[] columns = new int[projection.length]; + for (int i = 0; i < projection.length; i++) { + Integer id = map.get(projection[i]); + if (id == null) { + throw new IllegalArgumentException("unknown column " + projection[i]); + } + columns[i] = id.intValue(); + } + native_setup(client, queryType, deviceID, storageID, objectID, columns); + } + + @Override + protected void finalize() { + native_finalize(); + } + + @Override + public int getCount() { + if (mCount == NO_COUNT) { + fillWindow(0); + } + return mCount; + } + + @Override + public boolean requery() { + Log.d(TAG, "requery"); + mCount = NO_COUNT; + if (mWindow != null) { + mWindow.clear(); + } + return super.requery(); + } + + private void fillWindow(int startPos) { + if (mWindow == null) { + // If there isn't a window set already it will only be accessed locally + mWindow = new CursorWindow(true /* the window is local only */); + } else { + mWindow.clear(); + } + mWindow.setStartPosition(startPos); + mCount = native_fill_window(mWindow, startPos); + } + + @Override + public String[] getColumnNames() { + Log.d(TAG, "getColumnNames returning " + mColumns); + return mColumns; + } + + /* Device Column IDs */ + /* These must match the values in MtpCursor.cpp */ + private static final int DEVICE_ROW_ID = 1; + private static final int DEVICE_MANUFACTURER = 2; + private static final int DEVICE_MODEL = 3; + + /* Storage Column IDs */ + /* These must match the values in MtpCursor.cpp */ + private static final int STORAGE_ROW_ID = 101; + private static final int STORAGE_IDENTIFIER = 102; + private static final int STORAGE_DESCRIPTION = 103; + + /* Object Column IDs */ + /* These must match the values in MtpCursor.cpp */ + private static final int OBJECT_ROW_ID = 201; + private static final int OBJECT_STORAGE_ID = 202; + private static final int OBJECT_FORMAT = 203; + private static final int OBJECT_PROTECTION_STATUS = 204; + private static final int OBJECT_SIZE = 205; + private static final int OBJECT_THUMB_FORMAT = 206; + private static final int OBJECT_THUMB_SIZE = 207; + private static final int OBJECT_THUMB_WIDTH = 208; + private static final int OBJECT_THUMB_HEIGHT = 209; + private static final int OBJECT_IMAGE_WIDTH = 210; + private static final int OBJECT_IMAGE_HEIGHT = 211; + private static final int OBJECT_IMAGE_DEPTH = 212; + private static final int OBJECT_PARENT = 213; + private static final int OBJECT_ASSOCIATION_TYPE = 214; + private static final int OBJECT_ASSOCIATION_DESC = 215; + private static final int OBJECT_SEQUENCE_NUMBER = 216; + private static final int OBJECT_NAME = 217; + private static final int OBJECT_DATE_CREATED = 218; + private static final int OBJECT_DATE_MODIFIED = 219; + private static final int OBJECT_KEYWORDS = 220; + private static final int OBJECT_THUMB = 221; + + private static HashMap<String, Integer> sDeviceProjectionMap; + private static HashMap<String, Integer> sStorageProjectionMap; + private static HashMap<String, Integer> sObjectProjectionMap; + + static { + sDeviceProjectionMap = new HashMap<String, Integer>(); + sDeviceProjectionMap.put(Mtp.Device._ID, new Integer(DEVICE_ROW_ID)); + sDeviceProjectionMap.put(Mtp.Device.MANUFACTURER, new Integer(DEVICE_MANUFACTURER)); + sDeviceProjectionMap.put(Mtp.Device.MODEL, new Integer(DEVICE_MODEL)); + + sStorageProjectionMap = new HashMap<String, Integer>(); + sStorageProjectionMap.put(Mtp.Storage._ID, new Integer(STORAGE_ROW_ID)); + sStorageProjectionMap.put(Mtp.Storage.IDENTIFIER, new Integer(STORAGE_IDENTIFIER)); + sStorageProjectionMap.put(Mtp.Storage.DESCRIPTION, new Integer(STORAGE_DESCRIPTION)); + + sObjectProjectionMap = new HashMap<String, Integer>(); + sObjectProjectionMap.put(Mtp.Object._ID, new Integer(OBJECT_ROW_ID)); + sObjectProjectionMap.put(Mtp.Object.STORAGE_ID, new Integer(OBJECT_STORAGE_ID)); + sObjectProjectionMap.put(Mtp.Object.FORMAT, new Integer(OBJECT_FORMAT)); + sObjectProjectionMap.put(Mtp.Object.PROTECTION_STATUS, new Integer(OBJECT_PROTECTION_STATUS)); + sObjectProjectionMap.put(Mtp.Object.SIZE, new Integer(OBJECT_SIZE)); + sObjectProjectionMap.put(Mtp.Object.THUMB_FORMAT, new Integer(OBJECT_THUMB_FORMAT)); + sObjectProjectionMap.put(Mtp.Object.THUMB_SIZE, new Integer(OBJECT_THUMB_SIZE)); + sObjectProjectionMap.put(Mtp.Object.THUMB_WIDTH, new Integer(OBJECT_THUMB_WIDTH)); + sObjectProjectionMap.put(Mtp.Object.THUMB_HEIGHT, new Integer(OBJECT_THUMB_HEIGHT)); + sObjectProjectionMap.put(Mtp.Object.IMAGE_WIDTH, new Integer(OBJECT_IMAGE_WIDTH)); + sObjectProjectionMap.put(Mtp.Object.IMAGE_HEIGHT, new Integer(OBJECT_IMAGE_HEIGHT)); + sObjectProjectionMap.put(Mtp.Object.IMAGE_DEPTH, new Integer(OBJECT_IMAGE_DEPTH)); + sObjectProjectionMap.put(Mtp.Object.PARENT, new Integer(OBJECT_PARENT)); + sObjectProjectionMap.put(Mtp.Object.ASSOCIATION_TYPE, new Integer(OBJECT_ASSOCIATION_TYPE)); + sObjectProjectionMap.put(Mtp.Object.ASSOCIATION_DESC, new Integer(OBJECT_ASSOCIATION_DESC)); + sObjectProjectionMap.put(Mtp.Object.SEQUENCE_NUMBER, new Integer(OBJECT_SEQUENCE_NUMBER)); + sObjectProjectionMap.put(Mtp.Object.NAME, new Integer(OBJECT_NAME)); + sObjectProjectionMap.put(Mtp.Object.DATE_CREATED, new Integer(OBJECT_DATE_CREATED)); + sObjectProjectionMap.put(Mtp.Object.DATE_MODIFIED, new Integer(OBJECT_DATE_MODIFIED)); + sObjectProjectionMap.put(Mtp.Object.KEYWORDS, new Integer(OBJECT_KEYWORDS)); + sObjectProjectionMap.put(Mtp.Object.THUMB, new Integer(OBJECT_THUMB)); + + sObjectProjectionMap.put(Mtp.Object.NAME, new Integer(OBJECT_NAME)); + } + + // used by the JNI code + private int mNativeContext; + + private native final void native_setup(MtpClient client, int queryType, + int deviceID, int storageID, int objectID, int[] columns); + private native final void native_finalize(); + private native void native_wait_for_event(); + private native int native_fill_window(CursorWindow window, int startPos); +} diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java new file mode 100644 index 0000000..88cce46 --- /dev/null +++ b/media/java/android/media/MtpDatabase.java @@ -0,0 +1,466 @@ +/* + * 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.media; + +import android.content.Context; +import android.content.ContentValues; +import android.content.IContentProvider; +import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; +import android.provider.MediaStore.Audio; +import android.provider.MediaStore.MediaColumns; +import android.provider.MediaStore.MtpObjects; +import android.provider.Mtp; +import android.util.Log; + +/** + * {@hide} + */ +public class MtpDatabase { + + private static final String TAG = "MtpDatabase"; + + private final IContentProvider mMediaProvider; + private final String mVolumeName; + private final Uri mObjectsUri; + + // FIXME - this should be passed in via the constructor + private final int mStorageID = 0x00010001; + + private static final String[] ID_PROJECTION = new String[] { + MtpObjects.ObjectColumns._ID, // 0 + }; + private static final String[] PATH_SIZE_PROJECTION = new String[] { + MtpObjects.ObjectColumns._ID, // 0 + MtpObjects.ObjectColumns.DATA, // 1 + MtpObjects.ObjectColumns.SIZE, // 2 + }; + private static final String[] OBJECT_INFO_PROJECTION = new String[] { + MtpObjects.ObjectColumns._ID, // 0 + MtpObjects.ObjectColumns.DATA, // 1 + MtpObjects.ObjectColumns.FORMAT, // 2 + MtpObjects.ObjectColumns.PARENT, // 3 + MtpObjects.ObjectColumns.SIZE, // 4 + MtpObjects.ObjectColumns.DATE_MODIFIED, // 5 + }; + private static final String ID_WHERE = MtpObjects.ObjectColumns._ID + "=?"; + private static final String PATH_WHERE = MtpObjects.ObjectColumns.DATA + "=?"; + private static final String PARENT_WHERE = MtpObjects.ObjectColumns.PARENT + "=?"; + private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " + + MtpObjects.ObjectColumns.FORMAT + "=?"; + + private final MediaScanner mMediaScanner; + + // MTP property codes + private static final int MTP_PROPERTY_STORAGE_ID = 0xDC01; + private static final int MTP_PROPERTY_OBJECT_FORMAT = 0xDC02; + private static final int MTP_PROPERTY_OBJECT_SIZE = 0xDC04; + private static final int MTP_PROPERTY_OBJECT_FILE_NAME = 0xDC07; + private static final int MTP_PROPERTY_DATE_MODIFIED = 0xDC09; + private static final int MTP_PROPERTY_PARENT_OBJECT = 0xDC0B; + + // MTP response codes + private static final int MTP_RESPONSE_OK = 0x2001; + private static final int MTP_RESPONSE_GENERAL_ERROR = 0x2002; + private static final int MTP_RESPONSE_INVALID_OBJECT_HANDLE = 0x2009; + private static final int MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED = 0xA80A; + + static { + System.loadLibrary("media_jni"); + } + + public MtpDatabase(Context context, String volumeName) { + native_setup(); + + mMediaProvider = context.getContentResolver().acquireProvider("media"); + mVolumeName = volumeName; + mObjectsUri = MtpObjects.getContentUri(volumeName); + mMediaScanner = new MediaScanner(context); + } + + @Override + protected void finalize() { + native_finalize(); + } + + private int beginSendObject(String path, int format, int parent, + int storage, long size, long modified) { + ContentValues values = new ContentValues(); + values.put(MtpObjects.ObjectColumns.DATA, path); + values.put(MtpObjects.ObjectColumns.FORMAT, format); + values.put(MtpObjects.ObjectColumns.PARENT, parent); + // storage is ignored for now + values.put(MtpObjects.ObjectColumns.SIZE, size); + values.put(MtpObjects.ObjectColumns.DATE_MODIFIED, modified); + + try { + Uri uri = mMediaProvider.insert(mObjectsUri, values); + if (uri != null) { + return Integer.parseInt(uri.getPathSegments().get(2)); + } else { + return -1; + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in beginSendObject", e); + return -1; + } + } + + private void endSendObject(String path, int handle, int format, boolean succeeded) { + if (succeeded) { + // handle abstract playlists separately + // they do not exist in the file system so don't use the media scanner here + if (format == Mtp.Object.FORMAT_ABSTRACT_AV_PLAYLIST) { + // Strip Windows Media Player file extension + if (path.endsWith(".pla")) { + path = path.substring(0, path.length() - 4); + } + + // extract name from path + String name = path; + int lastSlash = name.lastIndexOf('/'); + if (lastSlash >= 0) { + name = name.substring(lastSlash + 1); + } + + ContentValues values = new ContentValues(1); + values.put(Audio.Playlists.DATA, path); + values.put(Audio.Playlists.NAME, name); + values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle); + try { + Uri uri = mMediaProvider.insert(Audio.Playlists.EXTERNAL_CONTENT_URI, values); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in endSendObject", e); + } + } else { + Uri uri = mMediaScanner.scanMtpFile(path, mVolumeName, handle, format); + } + } else { + deleteFile(handle); + } + } + + private int[] getObjectList(int storageID, int format, int parent) { + // we can ignore storageID until we support multiple storages + Log.d(TAG, "getObjectList parent: " + parent); + Cursor c = null; + try { + if (format != 0) { + c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, + PARENT_FORMAT_WHERE, + new String[] { Integer.toString(parent), Integer.toString(format) }, + null); + } else { + c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, + PARENT_WHERE, new String[] { Integer.toString(parent) }, null); + } + if (c == null) { + Log.d(TAG, "null cursor"); + return null; + } + int count = c.getCount(); + if (count > 0) { + int[] result = new int[count]; + for (int i = 0; i < count; i++) { + c.moveToNext(); + result[i] = c.getInt(0); + } + Log.d(TAG, "returning " + result); + return result; + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getObjectList", e); + } finally { + if (c != null) { + c.close(); + } + } + return null; + } + + private int getNumObjects(int storageID, int format, int parent) { + // we can ignore storageID until we support multiple storages + Log.d(TAG, "getObjectList parent: " + parent); + Cursor c = null; + try { + if (format != 0) { + c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, + PARENT_FORMAT_WHERE, + new String[] { Integer.toString(parent), Integer.toString(format) }, + null); + } else { + c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, + PARENT_WHERE, new String[] { Integer.toString(parent) }, null); + } + if (c != null) { + return c.getCount(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getNumObjects", e); + } finally { + if (c != null) { + c.close(); + } + } + return -1; + } + + private int[] getSupportedPlaybackFormats() { + return new int[] { + Mtp.Object.FORMAT_ASSOCIATION, + Mtp.Object.FORMAT_MP3, + Mtp.Object.FORMAT_MPEG, + Mtp.Object.FORMAT_EXIF_JPEG, + Mtp.Object.FORMAT_TIFF_EP, + Mtp.Object.FORMAT_GIF, + Mtp.Object.FORMAT_JFIF, + Mtp.Object.FORMAT_PNG, + Mtp.Object.FORMAT_TIFF, + Mtp.Object.FORMAT_WMA, + Mtp.Object.FORMAT_OGG, + Mtp.Object.FORMAT_AAC, + Mtp.Object.FORMAT_MP4_CONTAINER, + Mtp.Object.FORMAT_MP2, + Mtp.Object.FORMAT_3GP_CONTAINER, + Mtp.Object.FORMAT_ABSTRACT_AV_PLAYLIST, + Mtp.Object.FORMAT_WPL_PLAYLIST, + Mtp.Object.FORMAT_M3U_PLAYLIST, + Mtp.Object.FORMAT_PLS_PLAYLIST, + }; + } + + private int[] getSupportedCaptureFormats() { + // no capture formats yet + return null; + } + + private int[] getSupportedObjectProperties(int handle) { + return new int[] { + Mtp.Object.PROPERTY_STORAGE_ID, + Mtp.Object.PROPERTY_OBJECT_FORMAT, + Mtp.Object.PROPERTY_OBJECT_SIZE, + Mtp.Object.PROPERTY_OBJECT_FILE_NAME, + Mtp.Object.PROPERTY_PARENT_OBJECT, + }; + } + + private int[] getSupportedDeviceProperties() { + // no device properties yet + return null; + } + + private int getObjectProperty(int handle, int property, + long[] outIntValue, char[] outStringValue) { + Log.d(TAG, "getObjectProperty: " + property); + String column = null; + boolean isString = false; + + switch (property) { + case MTP_PROPERTY_STORAGE_ID: + outIntValue[0] = mStorageID; + return MTP_RESPONSE_OK; + case MTP_PROPERTY_OBJECT_FORMAT: + column = MtpObjects.ObjectColumns.FORMAT; + break; + case MTP_PROPERTY_OBJECT_SIZE: + column = MtpObjects.ObjectColumns.SIZE; + break; + case MTP_PROPERTY_OBJECT_FILE_NAME: + column = MtpObjects.ObjectColumns.DATA; + isString = true; + break; + case MTP_PROPERTY_DATE_MODIFIED: + column = MtpObjects.ObjectColumns.DATE_MODIFIED; + break; + case MTP_PROPERTY_PARENT_OBJECT: + column = MtpObjects.ObjectColumns.PARENT; + break; + default: + return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED; + } + + Cursor c = null; + try { + // for now we are only reading properties from the "objects" table + c = mMediaProvider.query(mObjectsUri, + new String [] { MtpObjects.ObjectColumns._ID, column }, + ID_WHERE, new String[] { Integer.toString(handle) }, null); + if (c != null && c.moveToNext()) { + if (isString) { + String value = c.getString(1); + int start = 0; + + if (property == MTP_PROPERTY_OBJECT_FILE_NAME) { + // extract name from full path + int lastSlash = value.lastIndexOf('/'); + if (lastSlash >= 0) { + start = lastSlash + 1; + } + } + int end = value.length(); + if (end - start > 255) { + end = start + 255; + } + value.getChars(start, end, outStringValue, 0); + outStringValue[end - start] = 0; + } else { + outIntValue[0] = c.getLong(1); + } + return MTP_RESPONSE_OK; + } + } catch (Exception e) { + return MTP_RESPONSE_GENERAL_ERROR; + } finally { + if (c != null) { + c.close(); + } + } + // query failed if we get here + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + + private boolean getObjectInfo(int handle, int[] outStorageFormatParent, + char[] outName, long[] outSizeModified) { + Log.d(TAG, "getObjectInfo: " + handle); + Cursor c = null; + try { + c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION, + ID_WHERE, new String[] { Integer.toString(handle) }, null); + if (c != null && c.moveToNext()) { + outStorageFormatParent[0] = mStorageID; + outStorageFormatParent[1] = c.getInt(2); + outStorageFormatParent[2] = c.getInt(3); + + // extract name from path + String path = c.getString(1); + int lastSlash = path.lastIndexOf('/'); + int start = (lastSlash >= 0 ? lastSlash + 1 : 0); + int end = path.length(); + if (end - start > 255) { + end = start + 255; + } + path.getChars(start, end, outName, 0); + outName[end - start] = 0; + + outSizeModified[0] = c.getLong(4); + outSizeModified[1] = c.getLong(5); + return true; + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getObjectProperty", e); + } finally { + if (c != null) { + c.close(); + } + } + return false; + } + + private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLength) { + Log.d(TAG, "getObjectFilePath: " + handle); + Cursor c = null; + try { + c = mMediaProvider.query(mObjectsUri, PATH_SIZE_PROJECTION, + ID_WHERE, new String[] { Integer.toString(handle) }, null); + if (c != null && c.moveToNext()) { + String path = c.getString(1); + path.getChars(0, path.length(), outFilePath, 0); + outFilePath[path.length()] = 0; + outFileLength[0] = c.getLong(2); + return MTP_RESPONSE_OK; + } else { + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getObjectFilePath", e); + return MTP_RESPONSE_GENERAL_ERROR; + } finally { + if (c != null) { + c.close(); + } + } + } + + private int deleteFile(int handle) { + Log.d(TAG, "deleteFile: " + handle); + Uri uri = MtpObjects.getContentUri(mVolumeName, handle); + try { + if (mMediaProvider.delete(uri, null, null) == 1) { + return MTP_RESPONSE_OK; + } else { + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in deleteFile", e); + return MTP_RESPONSE_GENERAL_ERROR; + } + } + + private int[] getObjectReferences(int handle) { + Log.d(TAG, "getObjectReferences for: " + handle); + Uri uri = MtpObjects.getReferencesUri(mVolumeName, handle); + Cursor c = null; + try { + c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null); + if (c == null) { + return null; + } + int count = c.getCount(); + if (count > 0) { + int[] result = new int[count]; + for (int i = 0; i < count; i++) { + c.moveToNext(); + result[i] = c.getInt(0); + } + return result; + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getObjectList", e); + } finally { + if (c != null) { + c.close(); + } + } + return null; + } + + private int setObjectReferences(int handle, int[] references) { + Uri uri = MtpObjects.getReferencesUri(mVolumeName, handle); + int count = references.length; + ContentValues[] valuesList = new ContentValues[count]; + for (int i = 0; i < count; i++) { + ContentValues values = new ContentValues(); + values.put(MtpObjects.ObjectColumns._ID, references[i]); + valuesList[i] = values; + } + try { + if (count == mMediaProvider.bulkInsert(uri, valuesList)) { + return MTP_RESPONSE_OK; + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in setObjectReferences", e); + } + return MTP_RESPONSE_GENERAL_ERROR; + } + + // used by the JNI code + private int mNativeContext; + + private native final void native_setup(); + private native final void native_finalize(); +} diff --git a/media/java/android/media/MtpServer.java b/media/java/android/media/MtpServer.java new file mode 100644 index 0000000..b0945a5 --- /dev/null +++ b/media/java/android/media/MtpServer.java @@ -0,0 +1,67 @@ +/* + * 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.media; + +import android.util.Log; + +/** + * Java wrapper for MTP/PTP support as USB responder. + * {@hide} + */ +public class MtpServer { + + private static final String TAG = "MtpServer"; + + static { + System.loadLibrary("media_jni"); + } + + public MtpServer(MtpDatabase database, String storagePath) { + native_setup(database, storagePath); + } + + @Override + protected void finalize() { + native_finalize(); + } + + public void start() { + native_start(); + } + + public void stop() { + native_stop(); + } + + public void sendObjectAdded(int handle) { + native_send_object_added(handle); + } + + public void sendObjectRemoved(int handle) { + native_send_object_removed(handle); + } + + // used by the JNI code + private int mNativeContext; + + private native final void native_setup(MtpDatabase database, String storagePath); + private native final void native_finalize(); + private native final void native_start(); + private native final void native_stop(); + private native final void native_send_object_added(int handle); + private native final void native_send_object_removed(int handle); +} diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 6eec215..653532c 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -12,7 +12,11 @@ LOCAL_SRC_FILES:= \ android_media_MediaMetadataRetriever.cpp \ android_media_ResampleInputStream.cpp \ android_media_MediaProfiles.cpp \ - android_media_AmrInputStream.cpp + android_media_AmrInputStream.cpp \ + android_media_MtpClient.cpp \ + android_media_MtpCursor.cpp \ + android_media_MtpDatabase.cpp \ + android_media_MtpServer.cpp \ LOCAL_SHARED_LIBRARIES := \ libandroid_runtime \ @@ -25,7 +29,8 @@ LOCAL_SHARED_LIBRARIES := \ libcutils \ libsurfaceflinger_client \ libstagefright \ - libcamera_client + libcamera_client \ + libsqlite ifneq ($(BUILD_WITHOUT_PV),true) @@ -35,7 +40,9 @@ else LOCAL_CFLAGS += -DNO_OPENCORE endif -LOCAL_STATIC_LIBRARIES := +ifneq ($(TARGET_SIMULATOR),true) +LOCAL_STATIC_LIBRARIES := libmtp libusbhost +endif LOCAL_C_INCLUDES += \ external/tremor/Tremor \ @@ -44,6 +51,7 @@ LOCAL_C_INCLUDES += \ frameworks/base/media/libstagefright/codecs/amrnb/enc/src \ frameworks/base/media/libstagefright/codecs/amrnb/common \ frameworks/base/media/libstagefright/codecs/amrnb/common/include \ + frameworks/base/media/mtp \ $(PV_INCLUDES) \ $(JNI_H_INCLUDE) \ $(call include-path-for, corecg graphics) diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 474a174..6710db0 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -788,6 +788,10 @@ extern int register_android_media_MediaRecorder(JNIEnv *env); extern int register_android_media_MediaScanner(JNIEnv *env); extern int register_android_media_ResampleInputStream(JNIEnv *env); extern int register_android_media_MediaProfiles(JNIEnv *env); +extern int register_android_media_MtpClient(JNIEnv *env); +extern int register_android_media_MtpCursor(JNIEnv *env); +extern int register_android_media_MtpDatabase(JNIEnv *env); +extern int register_android_media_MtpServer(JNIEnv *env); #ifndef NO_OPENCORE extern int register_android_media_AmrInputStream(JNIEnv *env); @@ -841,6 +845,26 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) goto bail; } + if (register_android_media_MtpClient(env) < 0) { + LOGE("ERROR: MtpClient native registration failed"); + goto bail; + } + + if (register_android_media_MtpCursor(env) < 0) { + LOGE("ERROR: MtpCursor native registration failed"); + goto bail; + } + + if (register_android_media_MtpDatabase(env) < 0) { + LOGE("ERROR: MtpDatabase native registration failed"); + goto bail; + } + + if (register_android_media_MtpServer(env) < 0) { + LOGE("ERROR: MtpServer native registration failed"); + goto bail; + } + /* success -- return valid version number */ result = JNI_VERSION_1_4; diff --git a/media/jni/android_media_MtpClient.cpp b/media/jni/android_media_MtpClient.cpp new file mode 100644 index 0000000..67740bc --- /dev/null +++ b/media/jni/android_media_MtpClient.cpp @@ -0,0 +1,296 @@ +/* + * 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 "MtpClientJNI" +#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 "MtpClient.h" +#include "MtpDevice.h" +#include "MtpObjectInfo.h" + +using namespace android; + +// ---------------------------------------------------------------------------- + +static jmethodID method_deviceAdded; +static jmethodID method_deviceRemoved; +static jfieldID field_context; + +static struct file_descriptor_offsets_t +{ + jclass mClass; + jmethodID mConstructor; + jfieldID mDescriptor; +} gFileDescriptorOffsets; + +static struct parcel_file_descriptor_offsets_t +{ + jclass mClass; + jmethodID mConstructor; +} gParcelFileDescriptorOffsets; + +#ifdef HAVE_ANDROID_OS + +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(); + } +} + +class MyClient : public MtpClient { +private: + virtual void deviceAdded(MtpDevice *device); + virtual void deviceRemoved(MtpDevice *device); + + jobject mClient; + MtpDevice* mEventDevice; + +public: + MyClient(JNIEnv *env, jobject client); + void cleanup(JNIEnv *env); +}; + +MtpClient* get_client_from_object(JNIEnv* env, jobject javaClient) +{ + return (MtpClient*)env->GetIntField(javaClient, field_context); +} + + +MyClient::MyClient(JNIEnv *env, jobject client) + : mClient(env->NewGlobalRef(client)) +{ +} + +void MyClient::cleanup(JNIEnv *env) { + env->DeleteGlobalRef(mClient); +} + +void MyClient::deviceAdded(MtpDevice *device) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + const char* name = device->getDeviceName(); + LOGD("MyClient::deviceAdded %s\n", name); + + env->CallVoidMethod(mClient, method_deviceAdded, device->getID()); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +void MyClient::deviceRemoved(MtpDevice *device) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + const char* name = device->getDeviceName(); + LOGD("MyClient::deviceRemoved %s\n", name); + + env->CallVoidMethod(mClient, method_deviceRemoved, device->getID()); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +#endif // HAVE_ANDROID_OS + +// ---------------------------------------------------------------------------- + +static void +android_media_MtpClient_setup(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + LOGD("setup\n"); + MyClient* client = new MyClient(env, thiz); + client->start(); + env->SetIntField(thiz, field_context, (int)client); +#endif +} + +static void +android_media_MtpClient_finalize(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + LOGD("finalize\n"); + MyClient *client = (MyClient *)env->GetIntField(thiz, field_context); + client->cleanup(env); + delete client; + env->SetIntField(thiz, field_context, 0); +#endif +} + +static jboolean +android_media_MtpClient_start(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + LOGD("start\n"); + MyClient *client = (MyClient *)env->GetIntField(thiz, field_context); + return client->start(); +#else + return false; +#endif +} + +static void +android_media_MtpClient_stop(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + LOGD("stop\n"); + MyClient *client = (MyClient *)env->GetIntField(thiz, field_context); + client->stop(); +#endif +} + +static jboolean +android_media_MtpClient_delete_object(JNIEnv *env, jobject thiz, + jint device_id, jint object_id) +{ +#ifdef HAVE_ANDROID_OS + MyClient *client = (MyClient *)env->GetIntField(thiz, field_context); + MtpDevice* device = client->getDevice(device_id); + if (device) + return device->deleteObject(object_id); + else + #endif + return NULL; +} + +static jint +android_media_MtpClient_get_parent(JNIEnv *env, jobject thiz, + jint device_id, jint object_id) +{ +#ifdef HAVE_ANDROID_OS + MyClient *client = (MyClient *)env->GetIntField(thiz, field_context); + MtpDevice* device = client->getDevice(device_id); + if (device) + return device->getParent(object_id); + else +#endif + return -1; +} + +static jint +android_media_MtpClient_get_storage_id(JNIEnv *env, jobject thiz, + jint device_id, jint object_id) +{ + #ifdef HAVE_ANDROID_OS + MyClient *client = (MyClient *)env->GetIntField(thiz, field_context); + MtpDevice* device = client->getDevice(device_id); + if (device) + return device->getStorageID(object_id); + else +#endif + return -1; +} + +static jobject +android_media_MtpClient_open_file(JNIEnv *env, jobject thiz, + jint device_id, jint object_id) +{ +#ifdef HAVE_ANDROID_OS + MyClient *client = (MyClient *)env->GetIntField(thiz, field_context); + MtpDevice* device = client->getDevice(device_id); + if (!device) + return NULL; + + MtpObjectInfo* info = device->getObjectInfo(object_id); + if (!info) + return NULL; + int object_size = info->mCompressedSize; + delete info; + int fd = device->readObject(object_id, object_size); + if (fd < 0) + return NULL; + + jobject fileDescriptor = env->NewObject(gFileDescriptorOffsets.mClass, + gFileDescriptorOffsets.mConstructor); + if (fileDescriptor != NULL) { + env->SetIntField(fileDescriptor, gFileDescriptorOffsets.mDescriptor, fd); + } else { + return NULL; + } + return env->NewObject(gParcelFileDescriptorOffsets.mClass, + gParcelFileDescriptorOffsets.mConstructor, fileDescriptor); +#endif + return NULL; +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"native_setup", "()V", (void *)android_media_MtpClient_setup}, + {"native_finalize", "()V", (void *)android_media_MtpClient_finalize}, + {"native_start", "()Z", (void *)android_media_MtpClient_start}, + {"native_stop", "()V", (void *)android_media_MtpClient_stop}, + {"native_delete_object", "(II)Z", (void *)android_media_MtpClient_delete_object}, + {"native_get_parent", "(II)I", (void *)android_media_MtpClient_get_parent}, + {"native_get_storage_id", "(II)I", (void *)android_media_MtpClient_get_storage_id}, + {"native_open_file", "(II)Landroid/os/ParcelFileDescriptor;", + (void *)android_media_MtpClient_open_file}, +}; + +static const char* const kClassPathName = "android/media/MtpClient"; + +int register_android_media_MtpClient(JNIEnv *env) +{ + jclass clazz; + + LOGD("register_android_media_MtpClient\n"); + + clazz = env->FindClass("android/media/MtpClient"); + if (clazz == NULL) { + LOGE("Can't find android/media/MtpClient"); + return -1; + } + method_deviceAdded = env->GetMethodID(clazz, "deviceAdded", "(I)V"); + if (method_deviceAdded == NULL) { + LOGE("Can't find deviceAdded"); + return -1; + } + method_deviceRemoved = env->GetMethodID(clazz, "deviceRemoved", "(I)V"); + if (method_deviceRemoved == NULL) { + LOGE("Can't find deviceRemoved"); + return -1; + } + field_context = env->GetFieldID(clazz, "mNativeContext", "I"); + if (field_context == NULL) { + LOGE("Can't find MtpClient.mNativeContext"); + return -1; + } + + clazz = env->FindClass("java/io/FileDescriptor"); + LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor"); + gFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); + gFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V"); + gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I"); + LOG_FATAL_IF(gFileDescriptorOffsets.mDescriptor == NULL, + "Unable to find descriptor field in java.io.FileDescriptor"); + + clazz = env->FindClass("android/os/ParcelFileDescriptor"); + LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor"); + gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); + gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V"); + LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL, + "Unable to find constructor for android.os.ParcelFileDescriptor"); + + return AndroidRuntime::registerNativeMethods(env, + "android/media/MtpClient", gMethods, NELEM(gMethods)); +} diff --git a/media/jni/android_media_MtpCursor.cpp b/media/jni/android_media_MtpCursor.cpp new file mode 100644 index 0000000..6228b5d --- /dev/null +++ b/media/jni/android_media_MtpCursor.cpp @@ -0,0 +1,136 @@ +/* + * 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 "MtpCursorJNI" +#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 "binder/CursorWindow.h" + +#include "MtpClient.h" +#include "MtpCursor.h" + +using namespace android; + +// ---------------------------------------------------------------------------- + +static jfieldID field_context; + +// From android_media_MtpClient.cpp +MtpClient * get_client_from_object(JNIEnv * env, jobject javaClient); + +// ---------------------------------------------------------------------------- + +static bool ExceptionCheck(void* env) +{ + return ((JNIEnv *)env)->ExceptionCheck(); +} + +static void +android_media_MtpCursor_setup(JNIEnv *env, jobject thiz, jobject javaClient, + jint queryType, jint deviceID, jint storageID, jint objectID, jintArray javaColumns) +{ +#ifdef HAVE_ANDROID_OS + LOGD("android_media_MtpCursor_setup queryType: %d deviceID: %d storageID: %d objectID: %d\n", + queryType, deviceID, storageID, objectID); + + int* columns = NULL; + int columnCount = 0; + if (javaColumns) { + columns = env->GetIntArrayElements(javaColumns, 0); + columnCount = env->GetArrayLength(javaColumns); + } + + MtpClient* client = get_client_from_object(env, javaClient); + MtpCursor* cursor = new MtpCursor(client, queryType, + deviceID, storageID, objectID, columnCount, columns); + + if (columns) + env->ReleaseIntArrayElements(javaColumns, columns, 0); + env->SetIntField(thiz, field_context, (int)cursor); +#endif +} + +static void +android_media_MtpCursor_finalize(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + LOGD("finalize\n"); + MtpCursor *cursor = (MtpCursor *)env->GetIntField(thiz, field_context); + delete cursor; +#endif +} + +static jint +android_media_MtpCursor_fill_window(JNIEnv *env, jobject thiz, jobject javaWindow, jint startPos) +{ +#ifdef HAVE_ANDROID_OS + CursorWindow* window = get_window_from_object(env, javaWindow); + if (!window) { + LOGE("Invalid CursorWindow"); + jniThrowException(env, "java/lang/IllegalArgumentException", + "Bad CursorWindow"); + return 0; + } + MtpCursor *cursor = (MtpCursor *)env->GetIntField(thiz, field_context); + + return cursor->fillWindow(window, startPos); +#else + return 0; +#endif +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"native_setup", "(Landroid/media/MtpClient;IIII[I)V", + (void *)android_media_MtpCursor_setup}, + {"native_finalize", "()V", (void *)android_media_MtpCursor_finalize}, + {"native_fill_window", "(Landroid/database/CursorWindow;I)I", + (void *)android_media_MtpCursor_fill_window}, + +}; + +static const char* const kClassPathName = "android/media/MtpCursor"; + +int register_android_media_MtpCursor(JNIEnv *env) +{ + jclass clazz; + + LOGD("register_android_media_MtpCursor\n"); + + clazz = env->FindClass("android/media/MtpCursor"); + if (clazz == NULL) { + LOGE("Can't find android/media/MtpCursor"); + return -1; + } + field_context = env->GetFieldID(clazz, "mNativeContext", "I"); + if (field_context == NULL) { + LOGE("Can't find MtpCursor.mNativeContext"); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, + "android/media/MtpCursor", gMethods, NELEM(gMethods)); +} diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp new file mode 100644 index 0000000..bf83172 --- /dev/null +++ b/media/jni/android_media_MtpDatabase.cpp @@ -0,0 +1,626 @@ +/* + * 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 "MtpDatabaseJNI" +#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 "MtpDatabase.h" +#include "MtpDataPacket.h" +#include "MtpUtils.h" +#include "mtp.h" + +using namespace android; + +// ---------------------------------------------------------------------------- + +static jmethodID method_beginSendObject; +static jmethodID method_endSendObject; +static jmethodID method_getObjectList; +static jmethodID method_getNumObjects; +static jmethodID method_getSupportedPlaybackFormats; +static jmethodID method_getSupportedCaptureFormats; +static jmethodID method_getSupportedObjectProperties; +static jmethodID method_getSupportedDeviceProperties; +static jmethodID method_getObjectProperty; +static jmethodID method_getObjectInfo; +static jmethodID method_getObjectFilePath; +static jmethodID method_deleteFile; +static jmethodID method_getObjectReferences; +static jmethodID method_setObjectReferences; +static jfieldID field_context; + +MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) { + return (MtpDatabase *)env->GetIntField(database, field_context); +} + +#ifdef HAVE_ANDROID_OS +// ---------------------------------------------------------------------------- + +class MyMtpDatabase : public MtpDatabase { +private: + jobject mDatabase; + jintArray mIntBuffer; + jlongArray mLongBuffer; + jcharArray mStringBuffer; + +public: + MyMtpDatabase(JNIEnv *env, jobject client); + virtual ~MyMtpDatabase(); + void cleanup(JNIEnv *env); + + virtual MtpObjectHandle beginSendObject(const char* path, + MtpObjectFormat format, + MtpObjectHandle parent, + MtpStorageID storage, + uint64_t size, + time_t modified); + + virtual void endSendObject(const char* path, + MtpObjectHandle handle, + MtpObjectFormat format, + bool succeeded); + + virtual MtpObjectHandleList* getObjectList(MtpStorageID storageID, + MtpObjectFormat format, + MtpObjectHandle parent); + + virtual int getNumObjects(MtpStorageID storageID, + MtpObjectFormat format, + MtpObjectHandle parent); + + // callee should delete[] the results from these + // results can be NULL + virtual MtpObjectFormatList* getSupportedPlaybackFormats(); + virtual MtpObjectFormatList* getSupportedCaptureFormats(); + virtual MtpObjectPropertyList* getSupportedObjectProperties(MtpObjectFormat format); + virtual MtpDevicePropertyList* getSupportedDeviceProperties(); + + virtual MtpResponseCode getObjectProperty(MtpObjectHandle handle, + MtpObjectProperty property, + MtpDataPacket& packet); + + virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle, + MtpDataPacket& packet); + + virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle, + MtpString& filePath, + int64_t& fileLength); + virtual MtpResponseCode deleteFile(MtpObjectHandle handle); + + bool getPropertyInfo(MtpObjectProperty property, int& type); + + virtual MtpObjectHandleList* getObjectReferences(MtpObjectHandle handle); + + virtual MtpResponseCode setObjectReferences(MtpObjectHandle handle, + MtpObjectHandleList* references); +}; + +// ---------------------------------------------------------------------------- + +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(); + } +} + +// ---------------------------------------------------------------------------- + +MyMtpDatabase::MyMtpDatabase(JNIEnv *env, jobject client) + : mDatabase(env->NewGlobalRef(client)), + mIntBuffer(NULL), + mLongBuffer(NULL), + mStringBuffer(NULL) +{ + jintArray intArray; + jlongArray longArray; + jcharArray charArray; + + // create buffers for out arguments + // we don't need to be thread-safe so this is OK + intArray = env->NewIntArray(3); + if (!intArray) + goto out_of_memory; + mIntBuffer = (jintArray)env->NewGlobalRef(intArray); + longArray = env->NewLongArray(2); + if (!longArray) + goto out_of_memory; + mLongBuffer = (jlongArray)env->NewGlobalRef(longArray); + charArray = env->NewCharArray(256); + if (!charArray) + goto out_of_memory; + mStringBuffer = (jcharArray)env->NewGlobalRef(charArray); + return; + +out_of_memory: + env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), NULL); +} + +void MyMtpDatabase::cleanup(JNIEnv *env) { + env->DeleteGlobalRef(mDatabase); + env->DeleteGlobalRef(mIntBuffer); + env->DeleteGlobalRef(mLongBuffer); + env->DeleteGlobalRef(mStringBuffer); +} + +MyMtpDatabase::~MyMtpDatabase() { +} + +MtpObjectHandle MyMtpDatabase::beginSendObject(const char* path, + MtpObjectFormat format, + MtpObjectHandle parent, + MtpStorageID storage, + uint64_t size, + time_t modified) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + MtpObjectHandle result = env->CallIntMethod(mDatabase, method_beginSendObject, + env->NewStringUTF(path), (jint)format, (jint)parent, (jint)storage, + (jlong)size, (jlong)modified); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return result; +} + +void MyMtpDatabase::endSendObject(const char* path, MtpObjectHandle handle, + MtpObjectFormat format, bool succeeded) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mDatabase, method_endSendObject, env->NewStringUTF(path), + (jint)handle, (jint)format, (jboolean)succeeded); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +MtpObjectHandleList* MyMtpDatabase::getObjectList(MtpStorageID storageID, + MtpObjectFormat format, + MtpObjectHandle parent) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getObjectList, + (jint)storageID, (jint)format, (jint)parent); + if (!array) + return NULL; + MtpObjectHandleList* list = new MtpObjectHandleList(); + jint* handles = env->GetIntArrayElements(array, 0); + jsize length = env->GetArrayLength(array); + for (int i = 0; i < length; i++) + list->push(handles[i]); + env->ReleaseIntArrayElements(array, handles, 0); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return list; +} + +int MyMtpDatabase::getNumObjects(MtpStorageID storageID, + MtpObjectFormat format, + MtpObjectHandle parent) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + int result = env->CallIntMethod(mDatabase, method_getNumObjects, + (jint)storageID, (jint)format, (jint)parent); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return result; +} + +MtpObjectFormatList* MyMtpDatabase::getSupportedPlaybackFormats() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jintArray array = (jintArray)env->CallObjectMethod(mDatabase, + method_getSupportedPlaybackFormats); + if (!array) + return NULL; + MtpObjectFormatList* list = new MtpObjectFormatList(); + jint* formats = env->GetIntArrayElements(array, 0); + jsize length = env->GetArrayLength(array); + for (int i = 0; i < length; i++) + list->push(formats[i]); + env->ReleaseIntArrayElements(array, formats, 0); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return list; +} + +MtpObjectFormatList* MyMtpDatabase::getSupportedCaptureFormats() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jintArray array = (jintArray)env->CallObjectMethod(mDatabase, + method_getSupportedCaptureFormats); + if (!array) + return NULL; + MtpObjectFormatList* list = new MtpObjectFormatList(); + jint* formats = env->GetIntArrayElements(array, 0); + jsize length = env->GetArrayLength(array); + for (int i = 0; i < length; i++) + list->push(formats[i]); + env->ReleaseIntArrayElements(array, formats, 0); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return list; +} + +MtpObjectPropertyList* MyMtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jintArray array = (jintArray)env->CallObjectMethod(mDatabase, + method_getSupportedObjectProperties, (jint)format); + if (!array) + return NULL; + MtpObjectPropertyList* list = new MtpObjectPropertyList(); + jint* properties = env->GetIntArrayElements(array, 0); + jsize length = env->GetArrayLength(array); + for (int i = 0; i < length; i++) + list->push(properties[i]); + env->ReleaseIntArrayElements(array, properties, 0); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return list; +} + +MtpDevicePropertyList* MyMtpDatabase::getSupportedDeviceProperties() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jintArray array = (jintArray)env->CallObjectMethod(mDatabase, + method_getSupportedDeviceProperties); + if (!array) + return NULL; + MtpDevicePropertyList* list = new MtpDevicePropertyList(); + jint* properties = env->GetIntArrayElements(array, 0); + jsize length = env->GetArrayLength(array); + for (int i = 0; i < length; i++) + list->push(properties[i]); + env->ReleaseIntArrayElements(array, properties, 0); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return list; +} + +MtpResponseCode MyMtpDatabase::getObjectProperty(MtpObjectHandle handle, + MtpObjectProperty property, + MtpDataPacket& packet) { + int type; + + if (!getPropertyInfo(property, type)) + return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE; + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jint result = env->CallIntMethod(mDatabase, method_getObjectProperty, + (jint)handle, (jint)property, mLongBuffer, mStringBuffer); + if (result != MTP_RESPONSE_OK) + return result; + + jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0); + jlong longValue = longValues[0]; + env->ReleaseLongArrayElements(mLongBuffer, longValues, 0); + + switch (type) { + case MTP_TYPE_INT8: + packet.putInt8(longValue); + break; + case MTP_TYPE_UINT8: + packet.putUInt8(longValue); + break; + case MTP_TYPE_INT16: + packet.putInt16(longValue); + break; + case MTP_TYPE_UINT16: + packet.putUInt16(longValue); + break; + case MTP_TYPE_INT32: + packet.putInt32(longValue); + break; + case MTP_TYPE_UINT32: + packet.putUInt32(longValue); + break; + case MTP_TYPE_INT64: + packet.putInt64(longValue); + break; + case MTP_TYPE_UINT64: + packet.putUInt64(longValue); + break; + case MTP_TYPE_STR: + { + jchar* str = env->GetCharArrayElements(mStringBuffer, 0); + packet.putString(str); + env->ReleaseCharArrayElements(mStringBuffer, str, 0); + break; + } + default: + LOGE("unsupported object type\n"); + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return MTP_RESPONSE_OK; +} + +MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle, + MtpDataPacket& packet) { + char date[20]; + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jboolean result = env->CallBooleanMethod(mDatabase, method_getObjectInfo, + (jint)handle, mIntBuffer, mStringBuffer, mLongBuffer); + if (!result) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + + jint* intValues = env->GetIntArrayElements(mIntBuffer, 0); + MtpStorageID storageID = intValues[0]; + MtpObjectFormat format = intValues[1]; + MtpObjectHandle parent = intValues[2]; + env->ReleaseIntArrayElements(mIntBuffer, intValues, 0); + + jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0); + uint64_t size = longValues[0]; + uint64_t modified = longValues[1]; + env->ReleaseLongArrayElements(mLongBuffer, longValues, 0); + + int associationType = (format == MTP_FORMAT_ASSOCIATION ? + MTP_ASSOCIATION_TYPE_GENERIC_FOLDER : + MTP_ASSOCIATION_TYPE_UNDEFINED); + + packet.putUInt32(storageID); + packet.putUInt16(format); + packet.putUInt16(0); // protection status + packet.putUInt32((size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size)); + packet.putUInt16(0); // thumb format + packet.putUInt32(0); // thumb compressed size + packet.putUInt32(0); // thumb pix width + packet.putUInt32(0); // thumb pix height + packet.putUInt32(0); // image pix width + packet.putUInt32(0); // image pix height + packet.putUInt32(0); // image bit depth + packet.putUInt32(parent); + packet.putUInt16(associationType); + packet.putUInt32(0); // association desc + packet.putUInt32(0); // sequence number + + jchar* str = env->GetCharArrayElements(mStringBuffer, 0); + packet.putString(str); // file name + env->ReleaseCharArrayElements(mStringBuffer, str, 0); + + packet.putEmptyString(); + formatDateTime(modified, date, sizeof(date)); + packet.putString(date); // date modified + packet.putEmptyString(); // keywords + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return MTP_RESPONSE_OK; +} + +MtpResponseCode MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle, + MtpString& filePath, + int64_t& fileLength) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jint result = env->CallIntMethod(mDatabase, method_getObjectFilePath, + (jint)handle, mStringBuffer, mLongBuffer); + if (result != MTP_RESPONSE_OK) + return result; + + jchar* str = env->GetCharArrayElements(mStringBuffer, 0); + filePath.setTo(str, strlen16(str)); + env->ReleaseCharArrayElements(mStringBuffer, str, 0); + + jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0); + fileLength = longValues[0]; + env->ReleaseLongArrayElements(mLongBuffer, longValues, 0); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return result; +} + +MtpResponseCode MyMtpDatabase::deleteFile(MtpObjectHandle handle) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + MtpResponseCode result = env->CallIntMethod(mDatabase, method_deleteFile, (jint)handle); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return result; +} + +struct PropertyTableEntry { + MtpObjectProperty property; + int type; +}; + +static const PropertyTableEntry kPropertyTable[] = { + { MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32 }, + { MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32 }, + { MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16 }, + { MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR }, + { MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64 }, + { MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR }, +}; + +bool MyMtpDatabase::getPropertyInfo(MtpObjectProperty property, int& type) { + int count = sizeof(kPropertyTable) / sizeof(kPropertyTable[0]); + const PropertyTableEntry* entry = kPropertyTable; + for (int i = 0; i < count; i++, entry++) { + if (entry->property == property) { + type = entry->type; + return true; + } + } + return false; +} + +MtpObjectHandleList* MyMtpDatabase::getObjectReferences(MtpObjectHandle handle) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getObjectReferences, + (jint)handle); + if (!array) + return NULL; + MtpObjectHandleList* list = new MtpObjectHandleList(); + jint* handles = env->GetIntArrayElements(array, 0); + jsize length = env->GetArrayLength(array); + for (int i = 0; i < length; i++) + list->push(handles[i]); + env->ReleaseIntArrayElements(array, handles, 0); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return list; +} + +MtpResponseCode MyMtpDatabase::setObjectReferences(MtpObjectHandle handle, + MtpObjectHandleList* references) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + int count = references->size(); + jintArray array = env->NewIntArray(count); + if (!array) { + LOGE("out of memory in setObjectReferences"); + return false; + } + jint* handles = env->GetIntArrayElements(array, 0); + for (int i = 0; i < count; i++) + handles[i] = (*references)[i]; + env->ReleaseIntArrayElements(array, handles, 0); + MtpResponseCode result = env->CallIntMethod(mDatabase, method_setObjectReferences, + (jint)handle, array); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return result; +} + +#endif // HAVE_ANDROID_OS + +// ---------------------------------------------------------------------------- + +static void +android_media_MtpDatabase_setup(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + LOGD("setup\n"); + MyMtpDatabase* database = new MyMtpDatabase(env, thiz); + env->SetIntField(thiz, field_context, (int)database); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +#endif +} + +static void +android_media_MtpDatabase_finalize(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + LOGD("finalize\n"); + MyMtpDatabase* database = (MyMtpDatabase *)env->GetIntField(thiz, field_context); + database->cleanup(env); + delete database; + env->SetIntField(thiz, field_context, 0); + checkAndClearExceptionFromCallback(env, __FUNCTION__); +#endif +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"native_setup", "()V", (void *)android_media_MtpDatabase_setup}, + {"native_finalize", "()V", (void *)android_media_MtpDatabase_finalize}, +}; + +static const char* const kClassPathName = "android/media/MtpDatabase"; + +int register_android_media_MtpDatabase(JNIEnv *env) +{ + jclass clazz; + + LOGD("register_android_media_MtpDatabase\n"); + + clazz = env->FindClass("android/media/MtpDatabase"); + if (clazz == NULL) { + LOGE("Can't find android/media/MtpDatabase"); + return -1; + } + method_beginSendObject = env->GetMethodID(clazz, "beginSendObject", "(Ljava/lang/String;IIIJJ)I"); + if (method_beginSendObject == NULL) { + LOGE("Can't find beginSendObject"); + return -1; + } + method_endSendObject = env->GetMethodID(clazz, "endSendObject", "(Ljava/lang/String;IIZ)V"); + if (method_endSendObject == NULL) { + LOGE("Can't find endSendObject"); + return -1; + } + method_getObjectList = env->GetMethodID(clazz, "getObjectList", "(III)[I"); + if (method_getObjectList == NULL) { + LOGE("Can't find getObjectList"); + return -1; + } + method_getNumObjects = env->GetMethodID(clazz, "getNumObjects", "(III)I"); + if (method_getNumObjects == NULL) { + LOGE("Can't find getNumObjects"); + return -1; + } + method_getSupportedPlaybackFormats = env->GetMethodID(clazz, "getSupportedPlaybackFormats", "()[I"); + if (method_getSupportedPlaybackFormats == NULL) { + LOGE("Can't find getSupportedPlaybackFormats"); + return -1; + } + method_getSupportedCaptureFormats = env->GetMethodID(clazz, "getSupportedCaptureFormats", "()[I"); + if (method_getSupportedCaptureFormats == NULL) { + LOGE("Can't find getSupportedCaptureFormats"); + return -1; + } + method_getSupportedObjectProperties = env->GetMethodID(clazz, "getSupportedObjectProperties", "(I)[I"); + if (method_getSupportedObjectProperties == NULL) { + LOGE("Can't find getSupportedObjectProperties"); + return -1; + } + method_getSupportedDeviceProperties = env->GetMethodID(clazz, "getSupportedDeviceProperties", "()[I"); + if (method_getSupportedDeviceProperties == NULL) { + LOGE("Can't find getSupportedDeviceProperties"); + return -1; + } + method_getObjectProperty = env->GetMethodID(clazz, "getObjectProperty", "(II[J[C)I"); + if (method_getObjectProperty == NULL) { + LOGE("Can't find getObjectProperty"); + return -1; + } + method_getObjectInfo = env->GetMethodID(clazz, "getObjectInfo", "(I[I[C[J)Z"); + if (method_getObjectInfo == NULL) { + LOGE("Can't find getObjectInfo"); + return -1; + } + method_getObjectFilePath = env->GetMethodID(clazz, "getObjectFilePath", "(I[C[J)I"); + if (method_getObjectFilePath == NULL) { + LOGE("Can't find getObjectFilePath"); + return -1; + } + method_deleteFile = env->GetMethodID(clazz, "deleteFile", "(I)I"); + if (method_deleteFile == NULL) { + LOGE("Can't find deleteFile"); + return -1; + } + method_getObjectReferences = env->GetMethodID(clazz, "getObjectReferences", "(I)[I"); + if (method_getObjectReferences == NULL) { + LOGE("Can't find getObjectReferences"); + return -1; + } + method_setObjectReferences = env->GetMethodID(clazz, "setObjectReferences", "(I[I)I"); + if (method_setObjectReferences == NULL) { + LOGE("Can't find setObjectReferences"); + return -1; + } + field_context = env->GetFieldID(clazz, "mNativeContext", "I"); + if (field_context == NULL) { + LOGE("Can't find MtpDatabase.mNativeContext"); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, + "android/media/MtpDatabase", gMethods, NELEM(gMethods)); +} diff --git a/media/jni/android_media_MtpServer.cpp b/media/jni/android_media_MtpServer.cpp new file mode 100644 index 0000000..3305136 --- /dev/null +++ b/media/jni/android_media_MtpServer.cpp @@ -0,0 +1,220 @@ +/* + * 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 "MtpServerJNI" +#include "utils/Log.h" + +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <utils/threads.h> + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" +#include "private/android_filesystem_config.h" + +#include "MtpServer.h" + +using namespace android; + +// ---------------------------------------------------------------------------- + +static jfieldID field_context; +static Mutex sMutex; + +// in android_media_MtpDatabase.cpp +extern MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database); + +// ---------------------------------------------------------------------------- + +#ifdef HAVE_ANDROID_OS + +static bool ExceptionCheck(void* env) +{ + return ((JNIEnv *)env)->ExceptionCheck(); +} + +class MtpThread : public Thread { +private: + MtpDatabase* mDatabase; + MtpServer* mServer; + String8 mStoragePath; + jobject mJavaServer; + +public: + MtpThread(MtpDatabase* database, const char* storagePath, jobject javaServer) + : mDatabase(database), + mServer(NULL), + mStoragePath(storagePath), + mJavaServer(javaServer) + { + } + + virtual bool threadLoop() { + int fd = open("/dev/mtp_usb", O_RDWR); + printf("open returned %d\n", fd); + if (fd < 0) { + LOGE("could not open MTP driver\n"); + return false; + } + + sMutex.lock(); + mServer = new MtpServer(fd, mDatabase, AID_SDCARD_RW, 0664, 0775); + mServer->addStorage(mStoragePath); + sMutex.unlock(); + + LOGD("MtpThread mServer->run"); + mServer->run(); + close(fd); + + sMutex.lock(); + delete mServer; + mServer = NULL; + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->SetIntField(mJavaServer, field_context, 0); + env->DeleteGlobalRef(mJavaServer); + sMutex.unlock(); + + LOGD("threadLoop returning"); + return false; + } + + void sendObjectAdded(MtpObjectHandle handle) { + sMutex.lock(); + if (mServer) + mServer->sendObjectAdded(handle); + else + LOGE("sendObjectAdded called while disconnected\n"); + sMutex.unlock(); + } + + void sendObjectRemoved(MtpObjectHandle handle) { + sMutex.lock(); + if (mServer) + mServer->sendObjectRemoved(handle); + else + LOGE("sendObjectRemoved called while disconnected\n"); + sMutex.unlock(); + } +}; + +#endif // HAVE_ANDROID_OS + +static void +android_media_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jstring storagePath) +{ +#ifdef HAVE_ANDROID_OS + LOGD("setup\n"); + + MtpDatabase* database = getMtpDatabase(env, javaDatabase); + const char *storagePathStr = env->GetStringUTFChars(storagePath, NULL); + + MtpThread* thread = new MtpThread(database, storagePathStr, env->NewGlobalRef(thiz)); + env->SetIntField(thiz, field_context, (int)thread); + + env->ReleaseStringUTFChars(storagePath, storagePathStr); +#endif +} + +static void +android_media_MtpServer_finalize(JNIEnv *env, jobject thiz) +{ + LOGD("finalize\n"); +} + + +static void +android_media_MtpServer_start(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + LOGD("start\n"); + MtpThread *thread = (MtpThread *)env->GetIntField(thiz, field_context); + thread->run("MtpThread"); +#endif // HAVE_ANDROID_OS +} + +static void +android_media_MtpServer_stop(JNIEnv *env, jobject thiz) +{ +#ifdef HAVE_ANDROID_OS + LOGD("stop\n"); +#endif +} + +static void +android_media_MtpServer_send_object_added(JNIEnv *env, jobject thiz, jint handle) +{ +#ifdef HAVE_ANDROID_OS + LOGD("send_object_added %d\n", handle); + MtpThread *thread = (MtpThread *)env->GetIntField(thiz, field_context); + if (thread) + thread->sendObjectAdded(handle); + else + LOGE("sendObjectAdded called while disconnected\n"); +#endif +} + +static void +android_media_MtpServer_send_object_removed(JNIEnv *env, jobject thiz, jint handle) +{ +#ifdef HAVE_ANDROID_OS + LOGD("send_object_removed %d\n", handle); + MtpThread *thread = (MtpThread *)env->GetIntField(thiz, field_context); + if (thread) + thread->sendObjectRemoved(handle); + else + LOGE("sendObjectRemoved called while disconnected\n"); +#endif +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"native_setup", "(Landroid/media/MtpDatabase;Ljava/lang/String;)V", + (void *)android_media_MtpServer_setup}, + {"native_finalize", "()V", (void *)android_media_MtpServer_finalize}, + {"native_start", "()V", (void *)android_media_MtpServer_start}, + {"native_stop", "()V", (void *)android_media_MtpServer_stop}, + {"native_send_object_added", "(I)V", (void *)android_media_MtpServer_send_object_added}, + {"native_send_object_removed", "(I)V", (void *)android_media_MtpServer_send_object_removed}, +}; + +static const char* const kClassPathName = "android/media/MtpServer"; + +int register_android_media_MtpServer(JNIEnv *env) +{ + jclass clazz; + + LOGD("register_android_media_MtpServer\n"); + + clazz = env->FindClass("android/media/MtpServer"); + if (clazz == NULL) { + LOGE("Can't find android/media/MtpServer"); + return -1; + } + field_context = env->GetFieldID(clazz, "mNativeContext", "I"); + if (field_context == NULL) { + LOGE("Can't find MtpServer.mNativeContext"); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, + "android/media/MtpServer", gMethods, NELEM(gMethods)); +} diff --git a/media/libeffects/visualizer/Android.mk b/media/libeffects/visualizer/Android.mk index 48b45ff..e6ff654 100644 --- a/media/libeffects/visualizer/Android.mk +++ b/media/libeffects/visualizer/Android.mk @@ -27,4 +27,4 @@ LOCAL_C_INCLUDES := \ LOCAL_PRELINK_MODULE := false -include $(BUILD_SHARED_LIBRARY)
\ No newline at end of file +include $(BUILD_SHARED_LIBRARY) diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp index 7e3b743..9c2a8ba 100644 --- a/media/libmedia/AudioSystem.cpp +++ b/media/libmedia/AudioSystem.cpp @@ -763,7 +763,8 @@ bool AudioSystem::isBluetoothScoDevice(audio_devices device) if ((popCount(device) == 1 ) && (device & (AudioSystem::DEVICE_OUT_BLUETOOTH_SCO | AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET | - AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT))) { + AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT | + AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET))) { return true; } else { return false; diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp index 0f55b19d..9dfdcb0 100644 --- a/media/libmedia/IMediaPlayer.cpp +++ b/media/libmedia/IMediaPlayer.cpp @@ -22,12 +22,14 @@ #include <media/IMediaPlayer.h> #include <surfaceflinger/ISurface.h> +#include <surfaceflinger/Surface.h> namespace android { enum { DISCONNECT = IBinder::FIRST_CALL_TRANSACTION, SET_VIDEO_SURFACE, + SET_VIDEO_ISURFACE, PREPARE_ASYNC, START, STOP, @@ -65,11 +67,20 @@ public: remote()->transact(DISCONNECT, data, &reply); } - status_t setVideoSurface(const sp<ISurface>& surface) + status_t setVideoISurface(const sp<ISurface>& surface) { Parcel data, reply; data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor()); data.writeStrongBinder(surface->asBinder()); + remote()->transact(SET_VIDEO_ISURFACE, data, &reply); + return reply.readInt32(); + } + + status_t setVideoSurface(const sp<Surface>& surface) + { + Parcel data, reply; + data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor()); + Surface::writeToParcel(surface, &data); remote()->transact(SET_VIDEO_SURFACE, data, &reply); return reply.readInt32(); } @@ -256,9 +267,15 @@ status_t BnMediaPlayer::onTransact( disconnect(); return NO_ERROR; } break; - case SET_VIDEO_SURFACE: { + case SET_VIDEO_ISURFACE: { CHECK_INTERFACE(IMediaPlayer, data, reply); sp<ISurface> surface = interface_cast<ISurface>(data.readStrongBinder()); + reply->writeInt32(setVideoISurface(surface)); + return NO_ERROR; + } break; + case SET_VIDEO_SURFACE: { + CHECK_INTERFACE(IMediaPlayer, data, reply); + sp<Surface> surface = Surface::readFromParcel(data); reply->writeInt32(setVideoSurface(surface)); return NO_ERROR; } break; diff --git a/media/libmedia/IMediaRecorder.cpp b/media/libmedia/IMediaRecorder.cpp index 947ff34..f55a01e 100644 --- a/media/libmedia/IMediaRecorder.cpp +++ b/media/libmedia/IMediaRecorder.cpp @@ -19,7 +19,7 @@ #define LOG_TAG "IMediaRecorder" #include <utils/Log.h> #include <binder/Parcel.h> -#include <surfaceflinger/ISurface.h> +#include <surfaceflinger/Surface.h> #include <camera/ICamera.h> #include <media/IMediaRecorderClient.h> #include <media/IMediaRecorder.h> @@ -69,12 +69,12 @@ public: return reply.readInt32(); } - status_t setPreviewSurface(const sp<ISurface>& surface) + status_t setPreviewSurface(const sp<Surface>& surface) { LOGV("setPreviewSurface(%p)", surface.get()); Parcel data, reply; data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor()); - data.writeStrongBinder(surface->asBinder()); + Surface::writeToParcel(surface, &data); remote()->transact(SET_PREVIEW_SURFACE, data, &reply); return reply.readInt32(); } @@ -409,7 +409,7 @@ status_t BnMediaRecorder::onTransact( case SET_PREVIEW_SURFACE: { LOGV("SET_PREVIEW_SURFACE"); CHECK_INTERFACE(IMediaRecorder, data, reply); - sp<ISurface> surface = interface_cast<ISurface>(data.readStrongBinder()); + sp<Surface> surface = Surface::readFromParcel(data); reply->writeInt32(setPreviewSurface(surface)); return NO_ERROR; } break; diff --git a/media/libmedia/MediaScanner.cpp b/media/libmedia/MediaScanner.cpp index c5112a5..ba98f04 100644 --- a/media/libmedia/MediaScanner.cpp +++ b/media/libmedia/MediaScanner.cpp @@ -133,6 +133,13 @@ status_t MediaScanner::doProcessDirectory( continue; } + int nameLength = strlen(name); + if (nameLength + 1 > pathRemaining) { + // path too long! + continue; + } + strcpy(fileSpot, name); + int type = entry->d_type; if (type == DT_UNKNOWN) { // If the type is unknown, stat() the file instead. @@ -150,16 +157,7 @@ status_t MediaScanner::doProcessDirectory( } } if (type == DT_REG || type == DT_DIR) { - int nameLength = strlen(name); - bool isDirectory = (type == DT_DIR); - - if (nameLength > pathRemaining || (isDirectory && nameLength + 1 > pathRemaining)) { - // path too long! - continue; - } - - strcpy(fileSpot, name); - if (isDirectory) { + if (type == DT_DIR) { // ignore directories with a name that starts with '.' // for example, the Mac ".Trashes" directory if (name[0] == '.') continue; diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp index f3229c0..32b20c7 100644 --- a/media/libmedia/mediaplayer.cpp +++ b/media/libmedia/mediaplayer.cpp @@ -206,10 +206,15 @@ status_t MediaPlayer::setVideoSurface(const sp<Surface>& surface) LOGV("setVideoSurface"); Mutex::Autolock _l(mLock); if (mPlayer == 0) return NO_INIT; - if (surface != NULL) - return mPlayer->setVideoSurface(surface->getISurface()); - else - return mPlayer->setVideoSurface(NULL); + + status_t err = mPlayer->setVideoISurface( + surface == NULL ? NULL : surface->getISurface()); + + if (err != OK) { + return err; + } + + return mPlayer->setVideoSurface(surface); } // must call with lock held diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp index 9d53c25..7f25359 100644 --- a/media/libmedia/mediarecorder.cpp +++ b/media/libmedia/mediarecorder.cpp @@ -65,7 +65,7 @@ status_t MediaRecorder::setPreviewSurface(const sp<Surface>& surface) return INVALID_OPERATION; } - status_t ret = mMediaRecorder->setPreviewSurface(surface->getISurface()); + status_t ret = mMediaRecorder->setPreviewSurface(surface); if (OK != ret) { LOGV("setPreviewSurface failed: %d", ret); mCurrentState = MEDIA_RECORDER_ERROR; @@ -643,4 +643,3 @@ void MediaRecorder::died() } }; // namespace android - diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index b5972e7..d9b0c69 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -889,7 +889,15 @@ status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64 return mStatus; } -status_t MediaPlayerService::Client::setVideoSurface(const sp<ISurface>& surface) +status_t MediaPlayerService::Client::setVideoISurface(const sp<ISurface>& surface) +{ + LOGV("[%d] setVideoISurface(%p)", mConnId, surface.get()); + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + return p->setVideoISurface(surface); +} + +status_t MediaPlayerService::Client::setVideoSurface(const sp<Surface>& surface) { LOGV("[%d] setVideoSurface(%p)", mConnId, surface.get()); sp<MediaPlayerBase> p = getPlayer(); diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index a967ee2..deb458c 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -204,7 +204,8 @@ private: // IMediaPlayer interface virtual void disconnect(); - virtual status_t setVideoSurface(const sp<ISurface>& surface); + virtual status_t setVideoISurface(const sp<ISurface>& surface); + virtual status_t setVideoSurface(const sp<Surface>& surface); virtual status_t prepareAsync(); virtual status_t start(); virtual status_t stop(); diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp index 73862c3..7e05043 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.cpp +++ b/media/libmediaplayerservice/MediaRecorderClient.cpp @@ -70,7 +70,7 @@ status_t MediaRecorderClient::setCamera(const sp<ICamera>& camera) return mRecorder->setCamera(camera); } -status_t MediaRecorderClient::setPreviewSurface(const sp<ISurface>& surface) +status_t MediaRecorderClient::setPreviewSurface(const sp<Surface>& surface) { LOGV("setPreviewSurface"); Mutex::Autolock lock(mLock); @@ -337,4 +337,3 @@ status_t MediaRecorderClient::dump(int fd, const Vector<String16>& args) const { } }; // namespace android - diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h index 1d1913d..6c17217 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.h +++ b/media/libmediaplayerservice/MediaRecorderClient.h @@ -29,7 +29,7 @@ class MediaRecorderClient : public BnMediaRecorder { public: virtual status_t setCamera(const sp<ICamera>& camera); - virtual status_t setPreviewSurface(const sp<ISurface>& surface); + virtual status_t setPreviewSurface(const sp<Surface>& surface); virtual status_t setVideoSource(int vs); virtual status_t setAudioSource(int as); virtual status_t setOutputFormat(int of); @@ -66,4 +66,3 @@ private: }; // namespace android #endif // ANDROID_MEDIARECORDERCLIENT_H - diff --git a/media/libmediaplayerservice/MidiFile.h b/media/libmediaplayerservice/MidiFile.h index 4a60ece..06e4b70 100644 --- a/media/libmediaplayerservice/MidiFile.h +++ b/media/libmediaplayerservice/MidiFile.h @@ -35,7 +35,8 @@ public: const char* path, const KeyedVector<String8, String8> *headers); virtual status_t setDataSource(int fd, int64_t offset, int64_t length); - virtual status_t setVideoSurface(const sp<ISurface>& surface) { return UNKNOWN_ERROR; } + virtual status_t setVideoISurface(const sp<ISurface>& surface) { return UNKNOWN_ERROR; } + virtual status_t setVideoSurface(const sp<Surface>& surface) { return UNKNOWN_ERROR; } virtual status_t prepare(); virtual status_t prepareAsync(); virtual status_t start(); diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp index 2c96d6d..6b9bf85 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.cpp +++ b/media/libmediaplayerservice/StagefrightPlayer.cpp @@ -44,13 +44,20 @@ status_t StagefrightPlayer::setDataSource(int fd, int64_t offset, int64_t length return mPlayer->setDataSource(dup(fd), offset, length); } -status_t StagefrightPlayer::setVideoSurface(const sp<ISurface> &surface) { - LOGV("setVideoSurface"); +status_t StagefrightPlayer::setVideoISurface(const sp<ISurface> &surface) { + LOGV("setVideoISurface"); mPlayer->setISurface(surface); return OK; } +status_t StagefrightPlayer::setVideoSurface(const sp<Surface> &surface) { + LOGV("setVideoSurface"); + + mPlayer->setSurface(surface); + return OK; +} + status_t StagefrightPlayer::prepare() { return mPlayer->prepare(); } diff --git a/media/libmediaplayerservice/StagefrightPlayer.h b/media/libmediaplayerservice/StagefrightPlayer.h index 781eb44..dd37102 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.h +++ b/media/libmediaplayerservice/StagefrightPlayer.h @@ -35,7 +35,8 @@ public: const char *url, const KeyedVector<String8, String8> *headers); virtual status_t setDataSource(int fd, int64_t offset, int64_t length); - virtual status_t setVideoSurface(const sp<ISurface> &surface); + virtual status_t setVideoISurface(const sp<ISurface> &surface); + virtual status_t setVideoSurface(const sp<Surface> &surface); virtual status_t prepare(); virtual status_t prepareAsync(); virtual status_t start(); diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp index cf01ff6..a24e56c 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.cpp +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -24,6 +24,7 @@ #include <media/stagefright/AudioSource.h> #include <media/stagefright/AMRWriter.h> #include <media/stagefright/CameraSource.h> +#include <media/stagefright/CameraSourceTimeLapse.h> #include <media/stagefright/MPEG4Writer.h> #include <media/stagefright/MediaDebug.h> #include <media/stagefright/MediaDefs.h> @@ -34,11 +35,11 @@ #include <camera/ICamera.h> #include <camera/Camera.h> #include <camera/CameraParameters.h> -#include <surfaceflinger/ISurface.h> +#include <surfaceflinger/Surface.h> #include <utils/Errors.h> #include <sys/types.h> -#include <unistd.h> #include <ctype.h> +#include <unistd.h> #include "ARTPWriter.h" @@ -200,7 +201,7 @@ status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera) { return OK; } -status_t StagefrightRecorder::setPreviewSurface(const sp<ISurface> &surface) { +status_t StagefrightRecorder::setPreviewSurface(const sp<Surface> &surface) { LOGV("setPreviewSurface: %p", surface.get()); mPreviewSurface = surface; @@ -473,6 +474,45 @@ status_t StagefrightRecorder::setParamAudioTimeScale(int32_t timeScale) { return OK; } +status_t StagefrightRecorder::setParamTimeLapseEnable(int32_t timeLapseEnable) { + LOGV("setParamTimeLapseEnable: %d", timeLapseEnable); + + if(timeLapseEnable == 0) { + mCaptureTimeLapse = false; + } else if (timeLapseEnable == 1) { + mCaptureTimeLapse = true; + } else { + return BAD_VALUE; + } + return OK; +} + +status_t StagefrightRecorder::setParamUseStillCameraForTimeLapse(int32_t useStillCamera) { + LOGV("setParamUseStillCameraForTimeLapse: %d", useStillCamera); + + if(useStillCamera == 0) { + mUseStillCameraForTimeLapse= false; + } else if (useStillCamera == 1) { + mUseStillCameraForTimeLapse= true; + } else { + return BAD_VALUE; + } + return OK; +} + +status_t StagefrightRecorder::setParamTimeBetweenTimeLapseFrameCapture(int64_t timeUs) { + LOGV("setParamTimeBetweenTimeLapseFrameCapture: %lld us", timeUs); + + // Not allowing time more than a day + if (timeUs <= 0 || timeUs > 86400*1E6) { + LOGE("Time between time lapse frame capture (%lld) is out of range [0, 1 Day]", timeUs); + return BAD_VALUE; + } + + mTimeBetweenTimeLapseFrameCaptureUs = timeUs; + return OK; +} + status_t StagefrightRecorder::setParameter( const String8 &key, const String8 &value) { LOGV("setParameter: key (%s) => value (%s)", key.string(), value.string()); @@ -556,6 +596,22 @@ status_t StagefrightRecorder::setParameter( if (safe_strtoi32(value.string(), &timeScale)) { return setParamVideoTimeScale(timeScale); } + } else if (key == "time-lapse-enable") { + int32_t timeLapseEnable; + if (safe_strtoi32(value.string(), &timeLapseEnable)) { + return setParamTimeLapseEnable(timeLapseEnable); + } + } else if (key == "use-still-camera-for-time-lapse") { + int32_t useStillCamera; + if (safe_strtoi32(value.string(), &useStillCamera)) { + return setParamUseStillCameraForTimeLapse(useStillCamera); + } + } else if (key == "time-between-time-lapse-frame-capture") { + int64_t timeBetweenTimeLapseFrameCaptureMs; + if (safe_strtoi64(value.string(), &timeBetweenTimeLapseFrameCaptureMs)) { + return setParamTimeBetweenTimeLapseFrameCapture( + 1000LL * timeBetweenTimeLapseFrameCaptureMs); + } } else { LOGE("setParameter: failed to find key %s", key.string()); } @@ -851,10 +907,14 @@ void StagefrightRecorder::clipVideoFrameWidth() { } status_t StagefrightRecorder::setupCameraSource() { - clipVideoBitRate(); - clipVideoFrameRate(); - clipVideoFrameWidth(); - clipVideoFrameHeight(); + if (!mCaptureTimeLapse) { + // Dont clip for time lapse capture as encoder will have enough + // time to encode because of slow capture rate of time lapse. + clipVideoBitRate(); + clipVideoFrameRate(); + clipVideoFrameWidth(); + clipVideoFrameHeight(); + } int64_t token = IPCThreadState::self()->clearCallingIdentity(); if (mCamera == 0) { @@ -869,7 +929,14 @@ status_t StagefrightRecorder::setupCameraSource() { // Set the actual video recording frame size CameraParameters params(mCamera->getParameters()); - params.setPreviewSize(mVideoWidth, mVideoHeight); + + // dont change the preview size when using still camera for time lapse + // as mVideoWidth, mVideoHeight may correspond to HD resolution not + // supported by the video camera. + if (!(mCaptureTimeLapse && mUseStillCameraForTimeLapse)) { + params.setPreviewSize(mVideoWidth, mVideoHeight); + } + params.setPreviewFrameRate(mFrameRate); String8 s = params.flatten(); if (OK != mCamera->setParameters(s)) { @@ -882,8 +949,9 @@ status_t StagefrightRecorder::setupCameraSource() { // Check on video frame size int frameWidth = 0, frameHeight = 0; newCameraParams.getPreviewSize(&frameWidth, &frameHeight); - if (frameWidth < 0 || frameWidth != mVideoWidth || - frameHeight < 0 || frameHeight != mVideoHeight) { + if (!(mCaptureTimeLapse && mUseStillCameraForTimeLapse) && + (frameWidth < 0 || frameWidth != mVideoWidth || + frameHeight < 0 || frameHeight != mVideoHeight)) { LOGE("Failed to set the video frame size to %dx%d", mVideoWidth, mVideoHeight); IPCThreadState::self()->restoreCallingIdentity(token); @@ -927,7 +995,10 @@ status_t StagefrightRecorder::setupVideoEncoder(sp<MediaSource> *source) { status_t err = setupCameraSource(); if (err != OK) return err; - sp<CameraSource> cameraSource = CameraSource::CreateFromCamera(mCamera); + sp<CameraSource> cameraSource = (mCaptureTimeLapse) ? + CameraSourceTimeLapse::CreateFromCamera(mCamera, mUseStillCameraForTimeLapse, + mTimeBetweenTimeLapseFrameCaptureUs, mVideoWidth, mVideoHeight, mFrameRate): + CameraSource::CreateFromCamera(mCamera); CHECK(cameraSource != NULL); sp<MetaData> enc_meta = new MetaData; @@ -980,9 +1051,12 @@ status_t StagefrightRecorder::setupVideoEncoder(sp<MediaSource> *source) { OMXClient client; CHECK_EQ(client.connect(), OK); + // Use software codec for time lapse + uint32_t encoder_flags = (mCaptureTimeLapse) ? OMXCodec::kPreferSoftwareCodecs : 0; sp<MediaSource> encoder = OMXCodec::Create( client.interface(), enc_meta, - true /* createEncoder */, cameraSource); + true /* createEncoder */, cameraSource, + NULL, encoder_flags); if (encoder == NULL) { return UNKNOWN_ERROR; } @@ -1019,7 +1093,7 @@ status_t StagefrightRecorder::startMPEG4Recording() { sp<MediaWriter> writer = new MPEG4Writer(dup(mOutputFd)); // Add audio source first if it exists - if (mAudioSource != AUDIO_SOURCE_LIST_END) { + if (!mCaptureTimeLapse && (mAudioSource != AUDIO_SOURCE_LIST_END)) { err = setupAudioEncoder(writer); if (err != OK) return err; totalBitRate += mAudioBitRate; @@ -1137,6 +1211,9 @@ status_t StagefrightRecorder::reset() { mMaxFileDurationUs = 0; mMaxFileSizeBytes = 0; mTrackEveryTimeDurationUs = 0; + mCaptureTimeLapse = false; + mUseStillCameraForTimeLapse = true; + mTimeBetweenTimeLapseFrameCaptureUs = -1; mEncoderProfiles = MediaProfiles::getInstance(); mOutputFd = -1; diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h index 216f6bc..628e19b 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.h +++ b/media/libmediaplayerservice/StagefrightRecorder.h @@ -42,7 +42,7 @@ struct StagefrightRecorder : public MediaRecorderBase { virtual status_t setVideoSize(int width, int height); virtual status_t setVideoFrameRate(int frames_per_second); virtual status_t setCamera(const sp<ICamera>& camera); - virtual status_t setPreviewSurface(const sp<ISurface>& surface); + virtual status_t setPreviewSurface(const sp<Surface>& surface); virtual status_t setOutputFile(const char *path); virtual status_t setOutputFile(int fd, int64_t offset, int64_t length); virtual status_t setParameters(const String8& params); @@ -63,7 +63,7 @@ private: }; sp<Camera> mCamera; - sp<ISurface> mPreviewSurface; + sp<Surface> mPreviewSurface; sp<IMediaRecorderClient> mListener; sp<MediaWriter> mWriter; sp<AudioSource> mAudioSourceNode; @@ -92,6 +92,10 @@ private: int64_t mMaxFileDurationUs; int64_t mTrackEveryTimeDurationUs; + bool mCaptureTimeLapse; + bool mUseStillCameraForTimeLapse; + int64_t mTimeBetweenTimeLapseFrameCaptureUs; + String8 mParams; int mOutputFd; int32_t mFlags; @@ -113,6 +117,9 @@ private: status_t setParamAudioNumberOfChannels(int32_t channles); status_t setParamAudioSamplingRate(int32_t sampleRate); status_t setParamAudioTimeScale(int32_t timeScale); + status_t setParamTimeLapseEnable(int32_t timeLapseEnable); + status_t setParamUseStillCameraForTimeLapse(int32_t useStillCamera); + status_t setParamTimeBetweenTimeLapseFrameCapture(int64_t timeUs); status_t setParamVideoEncodingBitRate(int32_t bitRate); status_t setParamVideoIFramesInterval(int32_t seconds); status_t setParamVideoEncoderProfile(int32_t profile); @@ -137,4 +144,3 @@ private: } // namespace android #endif // STAGEFRIGHT_RECORDER_H_ - diff --git a/media/libmediaplayerservice/TestPlayerStub.h b/media/libmediaplayerservice/TestPlayerStub.h index 6e6c3cd..5eaf592 100644 --- a/media/libmediaplayerservice/TestPlayerStub.h +++ b/media/libmediaplayerservice/TestPlayerStub.h @@ -75,7 +75,10 @@ class TestPlayerStub : public MediaPlayerInterface { // All the methods below wrap the mPlayer instance. - virtual status_t setVideoSurface(const android::sp<android::ISurface>& s) { + virtual status_t setVideoISurface(const android::sp<android::ISurface>& s) { + return mPlayer->setVideoISurface(s); + } + virtual status_t setVideoSurface(const android::sp<android::Surface>& s) { return mPlayer->setVideoSurface(s); } virtual status_t prepare() {return mPlayer->prepare();} diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 86fa668..b5a6327 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -10,6 +10,7 @@ LOCAL_SRC_FILES:= \ AudioSource.cpp \ AwesomePlayer.cpp \ CameraSource.cpp \ + CameraSourceTimeLapse.cpp \ DataSource.cpp \ ESDS.cpp \ FileSource.cpp \ @@ -58,6 +59,7 @@ LOCAL_SHARED_LIBRARIES := \ libsonivox \ libvorbisidec \ libsurfaceflinger_client \ + libstagefright_yuv \ libcamera_client LOCAL_STATIC_LIBRARIES := \ diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 11fdf56..e167afa 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -44,7 +44,7 @@ #include <media/stagefright/MetaData.h> #include <media/stagefright/OMXCodec.h> -#include <surfaceflinger/ISurface.h> +#include <surfaceflinger/Surface.h> #include <media/stagefright/foundation/ALooper.h> @@ -97,13 +97,14 @@ struct AwesomeLocalRenderer : public AwesomeRenderer { bool previewOnly, const char *componentName, OMX_COLOR_FORMATTYPE colorFormat, - const sp<ISurface> &surface, + const sp<ISurface> &isurface, + const sp<Surface> &surface, size_t displayWidth, size_t displayHeight, size_t decodedWidth, size_t decodedHeight) : mTarget(NULL), mLibHandle(NULL) { init(previewOnly, componentName, - colorFormat, surface, displayWidth, + colorFormat, isurface, surface, displayWidth, displayHeight, decodedWidth, decodedHeight); } @@ -135,7 +136,8 @@ private: bool previewOnly, const char *componentName, OMX_COLOR_FORMATTYPE colorFormat, - const sp<ISurface> &surface, + const sp<ISurface> &isurface, + const sp<Surface> &surface, size_t displayWidth, size_t displayHeight, size_t decodedWidth, size_t decodedHeight); @@ -147,7 +149,8 @@ void AwesomeLocalRenderer::init( bool previewOnly, const char *componentName, OMX_COLOR_FORMATTYPE colorFormat, - const sp<ISurface> &surface, + const sp<ISurface> &isurface, + const sp<Surface> &surface, size_t displayWidth, size_t displayHeight, size_t decodedWidth, size_t decodedHeight) { if (!previewOnly) { @@ -173,7 +176,7 @@ void AwesomeLocalRenderer::init( if (func) { mTarget = - (*func)(surface, componentName, colorFormat, + (*func)(isurface, componentName, colorFormat, displayWidth, displayHeight, decodedWidth, decodedHeight); } @@ -624,8 +627,18 @@ status_t AwesomePlayer::play_l() { return OK; } +void AwesomePlayer::notifyVideoSize_l() { + sp<MetaData> meta = mVideoSource->getFormat(); + + int32_t decodedWidth, decodedHeight; + CHECK(meta->findInt32(kKeyWidth, &decodedWidth)); + CHECK(meta->findInt32(kKeyHeight, &decodedHeight)); + + notifyListener_l(MEDIA_SET_VIDEO_SIZE, decodedWidth, decodedHeight); +} + void AwesomePlayer::initRenderer_l() { - if (mISurface != NULL) { + if (mSurface != NULL || mISurface != NULL) { sp<MetaData> meta = mVideoSource->getFormat(); int32_t format; @@ -642,17 +655,7 @@ void AwesomePlayer::initRenderer_l() { // before creating a new one. IPCThreadState::self()->flushCommands(); - if (!strncmp("OMX.", component, 4)) { - // Our OMX codecs allocate buffers on the media_server side - // therefore they require a remote IOMXRenderer that knows how - // to display them. - mVideoRenderer = new AwesomeRemoteRenderer( - mClient.interface()->createRenderer( - mISurface, component, - (OMX_COLOR_FORMATTYPE)format, - decodedWidth, decodedHeight, - mVideoWidth, mVideoHeight)); - } else { + if (mSurface != NULL) { // Other decoders are instantiated locally and as a consequence // allocate their buffers in local address space. mVideoRenderer = new AwesomeLocalRenderer( @@ -660,8 +663,19 @@ void AwesomePlayer::initRenderer_l() { component, (OMX_COLOR_FORMATTYPE)format, mISurface, + mSurface, mVideoWidth, mVideoHeight, decodedWidth, decodedHeight); + } else { + // Our OMX codecs allocate buffers on the media_server side + // therefore they require a remote IOMXRenderer that knows how + // to display them. + mVideoRenderer = new AwesomeRemoteRenderer( + mClient.interface()->createRenderer( + mISurface, component, + (OMX_COLOR_FORMATTYPE)format, + decodedWidth, decodedHeight, + mVideoWidth, mVideoHeight)); } } } @@ -700,6 +714,12 @@ void AwesomePlayer::setISurface(const sp<ISurface> &isurface) { mISurface = isurface; } +void AwesomePlayer::setSurface(const sp<Surface> &surface) { + Mutex::Autolock autoLock(mLock); + + mSurface = surface; +} + void AwesomePlayer::setAudioSink( const sp<MediaPlayerBase::AudioSink> &audioSink) { Mutex::Autolock autoLock(mLock); @@ -957,6 +977,8 @@ void AwesomePlayer::onVideoEvent() { if (err == INFO_FORMAT_CHANGED) { LOGV("VideoSource signalled format change."); + notifyVideoSize_l(); + if (mVideoRenderer != NULL) { mVideoRendererIsPreview = false; initRenderer_l(); @@ -1442,10 +1464,10 @@ void AwesomePlayer::onPrepareAsyncEvent() { Mutex::Autolock autoLock(mLock); if (mIsAsyncPrepare) { - if (mVideoWidth < 0 || mVideoHeight < 0) { + if (mVideoSource == NULL) { notifyListener_l(MEDIA_SET_VIDEO_SIZE, 0, 0); } else { - notifyListener_l(MEDIA_SET_VIDEO_SIZE, mVideoWidth, mVideoHeight); + notifyVideoSize_l(); } notifyListener_l(MEDIA_PREPARED); @@ -1560,13 +1582,14 @@ status_t AwesomePlayer::resume() { mFlags = state->mFlags & (LOOPING | AT_EOS); - if (state->mLastVideoFrame && mISurface != NULL) { + if (state->mLastVideoFrame && (mSurface != NULL || mISurface != NULL)) { mVideoRenderer = new AwesomeLocalRenderer( true, // previewOnly "", (OMX_COLOR_FORMATTYPE)state->mColorFormat, mISurface, + mSurface, state->mVideoWidth, state->mVideoHeight, state->mDecodedWidth, diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp index 3e31d61..9ccd140 100644 --- a/media/libstagefright/CameraSource.cpp +++ b/media/libstagefright/CameraSource.cpp @@ -65,6 +65,11 @@ void CameraSourceListener::notify(int32_t msgType, int32_t ext1, int32_t ext2) { void CameraSourceListener::postData(int32_t msgType, const sp<IMemory> &dataPtr) { LOGV("postData(%d, ptr:%p, size:%d)", msgType, dataPtr->pointer(), dataPtr->size()); + + sp<CameraSource> source = mSource.promote(); + if (source.get() != NULL) { + source->dataCallback(msgType, dataPtr); + } } void CameraSourceListener::postDataTimestamp( @@ -118,15 +123,15 @@ CameraSource *CameraSource::CreateFromCamera(const sp<Camera> &camera) { CameraSource::CameraSource(const sp<Camera> &camera) : mCamera(camera), - mFirstFrameTimeUs(0), - mLastFrameTimestampUs(0), mNumFramesReceived(0), + mLastFrameTimestampUs(0), + mStarted(false), + mFirstFrameTimeUs(0), mNumFramesEncoded(0), mNumFramesDropped(0), mNumGlitches(0), mGlitchDurationThresholdUs(200000), - mCollectStats(false), - mStarted(false) { + mCollectStats(false) { int64_t token = IPCThreadState::self()->clearCallingIdentity(); String8 s = mCamera->getParameters(); @@ -161,7 +166,6 @@ CameraSource::CameraSource(const sp<Camera> &camera) mMeta->setInt32(kKeyHeight, height); mMeta->setInt32(kKeyStride, stride); mMeta->setInt32(kKeySliceHeight, sliceHeight); - } CameraSource::~CameraSource() { @@ -170,6 +174,10 @@ CameraSource::~CameraSource() { } } +void CameraSource::startCameraRecording() { + CHECK_EQ(OK, mCamera->startRecording()); +} + status_t CameraSource::start(MetaData *meta) { CHECK(!mStarted); @@ -187,13 +195,17 @@ status_t CameraSource::start(MetaData *meta) { int64_t token = IPCThreadState::self()->clearCallingIdentity(); mCamera->setListener(new CameraSourceListener(this)); - CHECK_EQ(OK, mCamera->startRecording()); + startCameraRecording(); IPCThreadState::self()->restoreCallingIdentity(token); mStarted = true; return OK; } +void CameraSource::stopCameraRecording() { + mCamera->stopRecording(); +} + status_t CameraSource::stop() { LOGV("stop"); Mutex::Autolock autoLock(mLock); @@ -202,7 +214,7 @@ status_t CameraSource::stop() { int64_t token = IPCThreadState::self()->clearCallingIdentity(); mCamera->setListener(NULL); - mCamera->stopRecording(); + stopCameraRecording(); releaseQueuedFrames(); while (!mFramesBeingEncoded.empty()) { LOGI("Waiting for outstanding frames being encoded: %d", @@ -222,11 +234,15 @@ status_t CameraSource::stop() { return OK; } +void CameraSource::releaseRecordingFrame(const sp<IMemory>& frame) { + mCamera->releaseRecordingFrame(frame); +} + void CameraSource::releaseQueuedFrames() { List<sp<IMemory> >::iterator it; while (!mFramesReceived.empty()) { it = mFramesReceived.begin(); - mCamera->releaseRecordingFrame(*it); + releaseRecordingFrame(*it); mFramesReceived.erase(it); ++mNumFramesDropped; } @@ -238,7 +254,7 @@ sp<MetaData> CameraSource::getFormat() { void CameraSource::releaseOneRecordingFrame(const sp<IMemory>& frame) { int64_t token = IPCThreadState::self()->clearCallingIdentity(); - mCamera->releaseRecordingFrame(frame); + releaseRecordingFrame(frame); IPCThreadState::self()->restoreCallingIdentity(token); } @@ -248,7 +264,6 @@ void CameraSource::signalBufferReturned(MediaBuffer *buffer) { for (List<sp<IMemory> >::iterator it = mFramesBeingEncoded.begin(); it != mFramesBeingEncoded.end(); ++it) { if ((*it)->pointer() == buffer->data()) { - releaseOneRecordingFrame((*it)); mFramesBeingEncoded.erase(it); ++mNumFramesEncoded; @@ -340,6 +355,13 @@ void CameraSource::dataCallbackTimestamp(int64_t timestampUs, ++mNumGlitches; } + // May need to skip frame or modify timestamp. Currently implemented + // by the subclass CameraSourceTimeLapse. + if(skipCurrentFrame(timestampUs)) { + releaseOneRecordingFrame(data); + return; + } + mLastFrameTimestampUs = timestampUs; if (mNumFramesReceived == 0) { mFirstFrameTimeUs = timestampUs; diff --git a/media/libstagefright/CameraSourceTimeLapse.cpp b/media/libstagefright/CameraSourceTimeLapse.cpp new file mode 100644 index 0000000..ba99501 --- /dev/null +++ b/media/libstagefright/CameraSourceTimeLapse.cpp @@ -0,0 +1,350 @@ +/* + * 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 "CameraSourceTimeLapse" + +#include <binder/IPCThreadState.h> +#include <binder/MemoryBase.h> +#include <binder/MemoryHeapBase.h> +#include <media/stagefright/CameraSource.h> +#include <media/stagefright/CameraSourceTimeLapse.h> +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/YUVImage.h> +#include <media/stagefright/YUVCanvas.h> +#include <camera/Camera.h> +#include <camera/CameraParameters.h> +#include <ui/Rect.h> +#include <utils/String8.h> +#include <utils/Vector.h> +#include "OMX_Video.h" +#include <limits.h> + +namespace android { + +// static +CameraSourceTimeLapse *CameraSourceTimeLapse::Create(bool useStillCameraForTimeLapse, + int64_t timeBetweenTimeLapseFrameCaptureUs, + int32_t width, int32_t height, + int32_t videoFrameRate) { + sp<Camera> camera = Camera::connect(0); + + if (camera.get() == NULL) { + return NULL; + } + + return new CameraSourceTimeLapse(camera, useStillCameraForTimeLapse, + timeBetweenTimeLapseFrameCaptureUs, width, height, videoFrameRate); +} + +// static +CameraSourceTimeLapse *CameraSourceTimeLapse::CreateFromCamera(const sp<Camera> &camera, + bool useStillCameraForTimeLapse, + int64_t timeBetweenTimeLapseFrameCaptureUs, + int32_t width, int32_t height, + int32_t videoFrameRate) { + if (camera.get() == NULL) { + return NULL; + } + + return new CameraSourceTimeLapse(camera, useStillCameraForTimeLapse, + timeBetweenTimeLapseFrameCaptureUs, width, height, videoFrameRate); +} + +CameraSourceTimeLapse::CameraSourceTimeLapse(const sp<Camera> &camera, + bool useStillCameraForTimeLapse, + int64_t timeBetweenTimeLapseFrameCaptureUs, + int32_t width, int32_t height, + int32_t videoFrameRate) + : CameraSource(camera), + mUseStillCameraForTimeLapse(useStillCameraForTimeLapse), + mTimeBetweenTimeLapseFrameCaptureUs(timeBetweenTimeLapseFrameCaptureUs), + mTimeBetweenTimeLapseVideoFramesUs(1E6/videoFrameRate), + mLastTimeLapseFrameRealTimestampUs(0), + mSkipCurrentFrame(false) { + + LOGV("starting time lapse mode"); + mVideoWidth = width; + mVideoHeight = height; + if (mUseStillCameraForTimeLapse) { + CHECK(setPictureSizeToClosestSupported(width, height)); + mNeedCropping = computeCropRectangleOffset(); + mMeta->setInt32(kKeyWidth, width); + mMeta->setInt32(kKeyHeight, height); + } +} + +CameraSourceTimeLapse::~CameraSourceTimeLapse() { +} + +bool CameraSourceTimeLapse::setPictureSizeToClosestSupported(int32_t width, int32_t height) { + int64_t token = IPCThreadState::self()->clearCallingIdentity(); + String8 s = mCamera->getParameters(); + IPCThreadState::self()->restoreCallingIdentity(token); + + CameraParameters params(s); + Vector<Size> supportedSizes; + params.getSupportedPictureSizes(supportedSizes); + + int32_t minPictureSize = INT_MAX; + for (uint32_t i = 0; i < supportedSizes.size(); ++i) { + int32_t pictureWidth = supportedSizes[i].width; + int32_t pictureHeight = supportedSizes[i].height; + + if ((pictureWidth >= width) && (pictureHeight >= height)) { + int32_t pictureSize = pictureWidth*pictureHeight; + if (pictureSize < minPictureSize) { + minPictureSize = pictureSize; + mPictureWidth = pictureWidth; + mPictureHeight = pictureHeight; + } + } + } + LOGV("Picture size = (%d, %d)", mPictureWidth, mPictureHeight); + return (minPictureSize != INT_MAX); +} + +bool CameraSourceTimeLapse::computeCropRectangleOffset() { + if ((mPictureWidth == mVideoWidth) && (mPictureHeight == mVideoHeight)) { + return false; + } + + CHECK((mPictureWidth > mVideoWidth) && (mPictureHeight > mVideoHeight)); + + int32_t widthDifference = mPictureWidth - mVideoWidth; + int32_t heightDifference = mPictureHeight - mVideoHeight; + + mCropRectStartX = widthDifference/2; + mCropRectStartY = heightDifference/2; + + LOGV("setting crop rectangle offset to (%d, %d)", mCropRectStartX, mCropRectStartY); + + return true; +} + +// static +void *CameraSourceTimeLapse::ThreadTimeLapseWrapper(void *me) { + CameraSourceTimeLapse *source = static_cast<CameraSourceTimeLapse *>(me); + source->threadTimeLapseEntry(); + return NULL; +} + +void CameraSourceTimeLapse::threadTimeLapseEntry() { + while(mStarted) { + if (mCameraIdle) { + LOGV("threadTimeLapseEntry: taking picture"); + CHECK_EQ(OK, mCamera->takePicture()); + mCameraIdle = false; + usleep(mTimeBetweenTimeLapseFrameCaptureUs); + } else { + LOGV("threadTimeLapseEntry: camera busy with old takePicture. Sleeping a little."); + usleep(1E4); + } + } +} + +void CameraSourceTimeLapse::startCameraRecording() { + if (mUseStillCameraForTimeLapse) { + LOGV("start time lapse recording using still camera"); + + int64_t token = IPCThreadState::self()->clearCallingIdentity(); + String8 s = mCamera->getParameters(); + IPCThreadState::self()->restoreCallingIdentity(token); + + CameraParameters params(s); + params.setPictureSize(mPictureWidth, mPictureHeight); + mCamera->setParameters(params.flatten()); + mCameraIdle = true; + + // create a thread which takes pictures in a loop + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + pthread_create(&mThreadTimeLapse, &attr, ThreadTimeLapseWrapper, this); + pthread_attr_destroy(&attr); + } else { + LOGV("start time lapse recording using video camera"); + CHECK_EQ(OK, mCamera->startRecording()); + } +} + +void CameraSourceTimeLapse::stopCameraRecording() { + if (mUseStillCameraForTimeLapse) { + void *dummy; + pthread_join(mThreadTimeLapse, &dummy); + CHECK_EQ(OK, mCamera->startPreview()); + } else { + mCamera->stopRecording(); + } +} + +void CameraSourceTimeLapse::releaseRecordingFrame(const sp<IMemory>& frame) { + if (!mUseStillCameraForTimeLapse) { + mCamera->releaseRecordingFrame(frame); + } +} + +sp<IMemory> CameraSourceTimeLapse::createIMemoryCopy(const sp<IMemory> &source_data) { + size_t source_size = source_data->size(); + void* source_pointer = source_data->pointer(); + + sp<MemoryHeapBase> newMemoryHeap = new MemoryHeapBase(source_size); + sp<MemoryBase> newMemory = new MemoryBase(newMemoryHeap, 0, source_size); + memcpy(newMemory->pointer(), source_pointer, source_size); + return newMemory; +} + +// Allocates IMemory of final type MemoryBase with the given size. +sp<IMemory> allocateIMemory(size_t size) { + sp<MemoryHeapBase> newMemoryHeap = new MemoryHeapBase(size); + sp<MemoryBase> newMemory = new MemoryBase(newMemoryHeap, 0, size); + return newMemory; +} + +// static +void *CameraSourceTimeLapse::ThreadStartPreviewWrapper(void *me) { + CameraSourceTimeLapse *source = static_cast<CameraSourceTimeLapse *>(me); + source->threadStartPreview(); + return NULL; +} + +void CameraSourceTimeLapse::threadStartPreview() { + CHECK_EQ(OK, mCamera->startPreview()); + mCameraIdle = true; +} + +void CameraSourceTimeLapse::restartPreview() { + // Start this in a different thread, so that the dataCallback can return + LOGV("restartPreview"); + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + pthread_t threadPreview; + pthread_create(&threadPreview, &attr, ThreadStartPreviewWrapper, this); + pthread_attr_destroy(&attr); +} + +sp<IMemory> CameraSourceTimeLapse::cropYUVImage(const sp<IMemory> &source_data) { + // find the YUV format + int32_t srcFormat; + CHECK(mMeta->findInt32(kKeyColorFormat, &srcFormat)); + YUVImage::YUVFormat yuvFormat; + if (srcFormat == OMX_COLOR_FormatYUV420SemiPlanar) { + yuvFormat = YUVImage::YUV420SemiPlanar; + } else if (srcFormat == OMX_COLOR_FormatYUV420Planar) { + yuvFormat = YUVImage::YUV420Planar; + } + + // allocate memory for cropped image and setup a canvas using it. + sp<IMemory> croppedImageMemory = allocateIMemory( + YUVImage::bufferSize(yuvFormat, mVideoWidth, mVideoHeight)); + YUVImage yuvImageCropped(yuvFormat, + mVideoWidth, mVideoHeight, + (uint8_t *)croppedImageMemory->pointer()); + YUVCanvas yuvCanvasCrop(yuvImageCropped); + + YUVImage yuvImageSource(yuvFormat, + mPictureWidth, mPictureHeight, + (uint8_t *)source_data->pointer()); + yuvCanvasCrop.CopyImageRect( + Rect(mCropRectStartX, mCropRectStartY, + mCropRectStartX + mVideoWidth, + mCropRectStartY + mVideoHeight), + 0, 0, + yuvImageSource); + + return croppedImageMemory; +} + +void CameraSourceTimeLapse::dataCallback(int32_t msgType, const sp<IMemory> &data) { + if (msgType == CAMERA_MSG_COMPRESSED_IMAGE) { + // takePicture will complete after this callback, so restart preview. + restartPreview(); + return; + } + if (msgType != CAMERA_MSG_RAW_IMAGE) { + return; + } + + LOGV("dataCallback for timelapse still frame"); + CHECK_EQ(true, mUseStillCameraForTimeLapse); + + int64_t timestampUs; + if (mNumFramesReceived == 0) { + timestampUs = mStartTimeUs; + } else { + timestampUs = mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs; + } + + if (mNeedCropping) { + sp<IMemory> croppedImageData = cropYUVImage(data); + dataCallbackTimestamp(timestampUs, msgType, croppedImageData); + } else { + sp<IMemory> dataCopy = createIMemoryCopy(data); + dataCallbackTimestamp(timestampUs, msgType, dataCopy); + } +} + +bool CameraSourceTimeLapse::skipCurrentFrame(int64_t timestampUs) { + if (mSkipCurrentFrame) { + mSkipCurrentFrame = false; + return true; + } else { + return false; + } +} + +bool CameraSourceTimeLapse::skipFrameAndModifyTimeStamp(int64_t *timestampUs) { + if (!mUseStillCameraForTimeLapse) { + if (mLastTimeLapseFrameRealTimestampUs == 0) { + // First time lapse frame. Initialize mLastTimeLapseFrameRealTimestampUs + // to current time (timestampUs) and save frame data. + LOGV("dataCallbackTimestamp timelapse: initial frame"); + + mLastTimeLapseFrameRealTimestampUs = *timestampUs; + } else if (*timestampUs < + (mLastTimeLapseFrameRealTimestampUs + mTimeBetweenTimeLapseFrameCaptureUs)) { + // Skip all frames from last encoded frame until + // sufficient time (mTimeBetweenTimeLapseFrameCaptureUs) has passed. + // Tell the camera to release its recording frame and return. + LOGV("dataCallbackTimestamp timelapse: skipping intermediate frame"); + return true; + } else { + // Desired frame has arrived after mTimeBetweenTimeLapseFrameCaptureUs time: + // - Reset mLastTimeLapseFrameRealTimestampUs to current time. + // - Artificially modify timestampUs to be one frame time (1/framerate) ahead + // of the last encoded frame's time stamp. + LOGV("dataCallbackTimestamp timelapse: got timelapse frame"); + + mLastTimeLapseFrameRealTimestampUs = *timestampUs; + *timestampUs = mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs; + } + } + return false; +} + +void CameraSourceTimeLapse::dataCallbackTimestamp(int64_t timestampUs, int32_t msgType, + const sp<IMemory> &data) { + if (!mUseStillCameraForTimeLapse) { + mSkipCurrentFrame = skipFrameAndModifyTimeStamp(×tampUs); + } + CameraSource::dataCallbackTimestamp(timestampUs, msgType, data); +} + +} // namespace android diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp index ab9285d..332bab3 100644 --- a/media/libstagefright/NuHTTPDataSource.cpp +++ b/media/libstagefright/NuHTTPDataSource.cpp @@ -42,7 +42,7 @@ static bool ParseURL( path->setTo(slashPos); } - char *colonPos = strchr(host->string(), ':'); + const char *colonPos = strchr(host->string(), ':'); if (colonPos != NULL) { unsigned long x; diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 4741b1d..b39157e 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -146,29 +146,36 @@ static sp<MediaSource> InstantiateSoftwareCodec( static const CodecInfo kDecoderInfo[] = { { MEDIA_MIMETYPE_IMAGE_JPEG, "OMX.TI.JPEG.decode" }, + { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.Nvidia.mp3.decoder" }, // { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.TI.MP3.decode" }, { MEDIA_MIMETYPE_AUDIO_MPEG, "MP3Decoder" }, // { MEDIA_MIMETYPE_AUDIO_MPEG, "OMX.PV.mp3dec" }, // { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.TI.AMR.decode" }, + { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.Nvidia.amr.decoder" }, { MEDIA_MIMETYPE_AUDIO_AMR_NB, "AMRNBDecoder" }, // { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.PV.amrdec" }, + { MEDIA_MIMETYPE_AUDIO_AMR_NB, "OMX.Nvidia.amrwb.decoder" }, { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.TI.WBAMR.decode" }, { MEDIA_MIMETYPE_AUDIO_AMR_WB, "AMRWBDecoder" }, // { MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.PV.amrdec" }, + { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.Nvidia.aac.decoder" }, { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.decode" }, { MEDIA_MIMETYPE_AUDIO_AAC, "AACDecoder" }, // { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.PV.aacdec" }, { MEDIA_MIMETYPE_AUDIO_G711_ALAW, "G711Decoder" }, { MEDIA_MIMETYPE_AUDIO_G711_MLAW, "G711Decoder" }, + { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.Nvidia.mp4.decode" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.decoder.mpeg4" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.decoder.mpeg4" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.Video.Decoder" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "M4vH263Decoder" }, // { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.PV.mpeg4dec" }, + { MEDIA_MIMETYPE_VIDEO_H263, "OMX.Nvidia.h263.decode" }, { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.7x30.video.decoder.h263" }, { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.video.decoder.h263" }, { MEDIA_MIMETYPE_VIDEO_H263, "M4vH263Decoder" }, // { MEDIA_MIMETYPE_VIDEO_H263, "OMX.PV.h263dec" }, + { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.Nvidia.h264.decode" }, { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.7x30.video.decoder.avc" }, { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.video.decoder.avc" }, { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.Video.Decoder" }, @@ -199,6 +206,7 @@ static const CodecInfo kEncoderInfo[] = { { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.7x30.video.encoder.avc" }, { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.video.encoder.avc" }, { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.Video.encoder" }, + { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.Nvidia.h264.encoder" }, { MEDIA_MIMETYPE_VIDEO_AVC, "AVCEncoder" }, // { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.PV.avcenc" }, }; @@ -337,6 +345,13 @@ static int CompareSoftwareCodecsFirst( uint32_t OMXCodec::getComponentQuirks(const char *componentName) { uint32_t quirks = 0; + if (!strcmp(componentName, "OMX.Nvidia.amr.decoder") || + !strcmp(componentName, "OMX.Nvidia.amrwb.decoder") || + !strcmp(componentName, "OMX.Nvidia.aac.decoder") || + !strcmp(componentName, "OMX.Nvidia.mp3.decoder")) { + quirks |= kDecoderLiesAboutNumberOfChannels; + } + if (!strcmp(componentName, "OMX.PV.avcdec")) { quirks |= kWantsNALFragments; } @@ -854,6 +869,10 @@ void OMXCodec::setVideoInputFormat( OMX_COLOR_FORMATTYPE colorFormat; CHECK_EQ(OK, findTargetColorFormat(meta, &colorFormat)); + if (!strcasecmp("OMX.Nvidia.h264.encoder", mComponentName)) { + colorFormat = OMX_COLOR_FormatYUV420Planar; + } + status_t err; OMX_PARAM_PORTDEFINITIONTYPE def; OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; @@ -1193,6 +1212,10 @@ status_t OMXCodec::setupAVCEncoderParameters(const sp<MetaData>& meta) { h264type.bMBAFF = OMX_FALSE; h264type.eLoopFilterMode = OMX_VIDEO_AVCLoopFilterEnable; + if (!strcasecmp("OMX.Nvidia.h264.encoder", mComponentName)) { + h264type.eLevel = OMX_VIDEO_AVCLevelMax; + } + err = mOMX->setParameter( mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type)); CHECK_EQ(err, OK); diff --git a/media/libstagefright/colorconversion/Android.mk b/media/libstagefright/colorconversion/Android.mk index b9ba1be..2b63235 100644 --- a/media/libstagefright/colorconversion/Android.mk +++ b/media/libstagefright/colorconversion/Android.mk @@ -6,7 +6,8 @@ LOCAL_SRC_FILES:= \ SoftwareRenderer.cpp LOCAL_C_INCLUDES := \ - $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ + $(TOP)/hardware/msm7k LOCAL_SHARED_LIBRARIES := \ libbinder \ @@ -17,6 +18,11 @@ LOCAL_SHARED_LIBRARIES := \ libsurfaceflinger_client\ libcamera_client +# ifeq ($(TARGET_BOARD_PLATFORM),msm7k) +ifeq ($(TARGET_PRODUCT),passion) + LOCAL_CFLAGS += -DHAS_YCBCR420_SP_ADRENO +endif + LOCAL_MODULE:= libstagefright_color_conversion include $(BUILD_SHARED_LIBRARY) diff --git a/media/libstagefright/colorconversion/SoftwareRenderer.cpp b/media/libstagefright/colorconversion/SoftwareRenderer.cpp index a6dbf69..507fa5a 100644 --- a/media/libstagefright/colorconversion/SoftwareRenderer.cpp +++ b/media/libstagefright/colorconversion/SoftwareRenderer.cpp @@ -22,65 +22,172 @@ #include <binder/MemoryHeapBase.h> #include <binder/MemoryHeapPmem.h> #include <media/stagefright/MediaDebug.h> -#include <surfaceflinger/ISurface.h> +#include <surfaceflinger/Surface.h> +#include <ui/android_native_buffer.h> +#include <ui/GraphicBufferMapper.h> + +// XXX: Temporary hack to allow referencing the _ADRENO pixel format here. +#include <libgralloc-qsd8k/gralloc_priv.h> namespace android { SoftwareRenderer::SoftwareRenderer( OMX_COLOR_FORMATTYPE colorFormat, - const sp<ISurface> &surface, + const sp<Surface> &surface, size_t displayWidth, size_t displayHeight, size_t decodedWidth, size_t decodedHeight) : mColorFormat(colorFormat), - mConverter(colorFormat, OMX_COLOR_Format16bitRGB565), - mISurface(surface), + mConverter(NULL), + mYUVMode(None), + mSurface(surface), mDisplayWidth(displayWidth), mDisplayHeight(displayHeight), mDecodedWidth(decodedWidth), - mDecodedHeight(decodedHeight), - mFrameSize(mDecodedWidth * mDecodedHeight * 2), // RGB565 - mIndex(0) { - mMemoryHeap = new MemoryHeapBase("/dev/pmem_adsp", 2 * mFrameSize); - if (mMemoryHeap->heapID() < 0) { - LOGI("Creating physical memory heap failed, reverting to regular heap."); - mMemoryHeap = new MemoryHeapBase(2 * mFrameSize); - } else { - sp<MemoryHeapPmem> pmemHeap = new MemoryHeapPmem(mMemoryHeap); - pmemHeap->slap(); - mMemoryHeap = pmemHeap; + mDecodedHeight(decodedHeight) { + LOGI("input format = %d", mColorFormat); + LOGI("display = %d x %d, decoded = %d x %d", + mDisplayWidth, mDisplayHeight, mDecodedWidth, mDecodedHeight); + + int halFormat; + switch (mColorFormat) { +#if HAS_YCBCR420_SP_ADRENO + case OMX_COLOR_FormatYUV420Planar: + { + halFormat = HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO; + mYUVMode = YUV420ToYUV420sp; + break; + } + + case 0x7fa30c00: + { + halFormat = HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO; + mYUVMode = YUV420spToYUV420sp; + break; + } +#endif + + default: + halFormat = HAL_PIXEL_FORMAT_RGB_565; + + mConverter = new ColorConverter( + mColorFormat, OMX_COLOR_Format16bitRGB565); + CHECK(mConverter->isValid()); + break; } - CHECK(mISurface.get() != NULL); + CHECK(mSurface.get() != NULL); CHECK(mDecodedWidth > 0); CHECK(mDecodedHeight > 0); - CHECK(mMemoryHeap->heapID() >= 0); - CHECK(mConverter.isValid()); + CHECK(mConverter == NULL || mConverter->isValid()); - ISurface::BufferHeap bufferHeap( - mDisplayWidth, mDisplayHeight, - mDecodedWidth, mDecodedHeight, - PIXEL_FORMAT_RGB_565, - mMemoryHeap); + CHECK_EQ(0, + native_window_set_usage( + mSurface.get(), + GRALLOC_USAGE_SW_READ_NEVER | GRALLOC_USAGE_SW_WRITE_OFTEN + | GRALLOC_USAGE_HW_TEXTURE)); - status_t err = mISurface->registerBuffers(bufferHeap); - CHECK_EQ(err, OK); + CHECK_EQ(0, native_window_set_buffer_count(mSurface.get(), 2)); + + // Width must be multiple of 32??? + CHECK_EQ(0, native_window_set_buffers_geometry( + mSurface.get(), mDecodedWidth, mDecodedHeight, + halFormat)); } SoftwareRenderer::~SoftwareRenderer() { - mISurface->unregisterBuffers(); + delete mConverter; + mConverter = NULL; +} + +static inline size_t ALIGN(size_t x, size_t alignment) { + return (x + alignment - 1) & ~(alignment - 1); } void SoftwareRenderer::render( const void *data, size_t size, void *platformPrivate) { - size_t offset = mIndex * mFrameSize; - void *dst = (uint8_t *)mMemoryHeap->getBase() + offset; + android_native_buffer_t *buf; + CHECK_EQ(0, mSurface->dequeueBuffer(mSurface.get(), &buf)); + CHECK_EQ(0, mSurface->lockBuffer(mSurface.get(), buf)); + + GraphicBufferMapper &mapper = GraphicBufferMapper::get(); + + Rect bounds(mDecodedWidth, mDecodedHeight); + + void *dst; + CHECK_EQ(0, mapper.lock( + buf->handle, GRALLOC_USAGE_SW_WRITE_OFTEN, bounds, &dst)); + + if (mConverter) { + mConverter->convert( + mDecodedWidth, mDecodedHeight, + data, 0, dst, buf->stride * 2); + } else if (mYUVMode == YUV420spToYUV420sp) { + // Input and output are both YUV420sp, but the alignment requirements + // are different. + size_t srcYStride = mDecodedWidth; + const uint8_t *srcY = (const uint8_t *)data; + uint8_t *dstY = (uint8_t *)dst; + for (size_t i = 0; i < mDecodedHeight; ++i) { + memcpy(dstY, srcY, mDecodedWidth); + srcY += srcYStride; + dstY += buf->stride; + } + + size_t srcUVStride = (mDecodedWidth + 1) & ~1; + size_t dstUVStride = ALIGN(mDecodedWidth / 2, 32) * 2; + + const uint8_t *srcUV = (const uint8_t *)data + + mDecodedHeight * mDecodedWidth; + + size_t dstUVOffset = ALIGN(ALIGN(mDecodedHeight, 32) * buf->stride, 4096); + uint8_t *dstUV = (uint8_t *)dst + dstUVOffset; + + for (size_t i = 0; i < (mDecodedHeight + 1) / 2; ++i) { + memcpy(dstUV, srcUV, (mDecodedWidth + 1) & ~1); + srcUV += srcUVStride; + dstUV += dstUVStride; + } + } else if (mYUVMode == YUV420ToYUV420sp) { + // Input is YUV420 planar, output is YUV420sp, adhere to proper + // alignment requirements. + size_t srcYStride = mDecodedWidth; + const uint8_t *srcY = (const uint8_t *)data; + uint8_t *dstY = (uint8_t *)dst; + for (size_t i = 0; i < mDecodedHeight; ++i) { + memcpy(dstY, srcY, mDecodedWidth); + srcY += srcYStride; + dstY += buf->stride; + } + + size_t srcUVStride = (mDecodedWidth + 1) / 2; + size_t dstUVStride = ALIGN(mDecodedWidth / 2, 32) * 2; + + const uint8_t *srcU = (const uint8_t *)data + + mDecodedHeight * mDecodedWidth; + + const uint8_t *srcV = + srcU + ((mDecodedWidth + 1) / 2) * ((mDecodedHeight + 1) / 2); + + size_t dstUVOffset = ALIGN(ALIGN(mDecodedHeight, 32) * buf->stride, 4096); + uint8_t *dstUV = (uint8_t *)dst + dstUVOffset; + + for (size_t i = 0; i < (mDecodedHeight + 1) / 2; ++i) { + for (size_t j = 0; j < (mDecodedWidth + 1) / 2; ++j) { + dstUV[2 * j + 1] = srcU[j]; + dstUV[2 * j] = srcV[j]; + } + srcU += srcUVStride; + srcV += srcUVStride; + dstUV += dstUVStride; + } + } else { + memcpy(dst, data, size); + } - mConverter.convert( - mDecodedWidth, mDecodedHeight, - data, 0, dst, 2 * mDecodedWidth); + CHECK_EQ(0, mapper.unlock(buf->handle)); - mISurface->postBuffer(offset); - mIndex = 1 - mIndex; + CHECK_EQ(0, mSurface->queueBuffer(mSurface.get(), buf)); + buf = NULL; } } // namespace android diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp index edd8648..17771c4 100644 --- a/media/libstagefright/httplive/M3UParser.cpp +++ b/media/libstagefright/httplive/M3UParser.cpp @@ -90,7 +90,7 @@ static bool MakeURL(const char *baseURL, const char *url, AString *out) { out->setTo(baseURL); out->append(url); } else { - char *slashPos = strrchr(baseURL, '/'); + const char *slashPos = strrchr(baseURL, '/'); if (slashPos > &baseURL[6]) { out->setTo(baseURL, slashPos - baseURL); diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index 49b5c78..f5df1b5 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -76,6 +76,7 @@ struct AwesomePlayer { bool isPlaying() const; void setISurface(const sp<ISurface> &isurface); + void setSurface(const sp<Surface> &surface); void setAudioSink(const sp<MediaPlayerBase::AudioSink> &audioSink); status_t setLooping(bool shouldLoop); @@ -117,6 +118,7 @@ private: wp<MediaPlayerBase> mListener; sp<ISurface> mISurface; + sp<Surface> mSurface; sp<MediaPlayerBase::AudioSink> mAudioSink; SystemTimeSource mSystemTimeSource; @@ -220,6 +222,7 @@ private: status_t seekTo_l(int64_t timeUs); status_t pause_l(); void initRenderer_l(); + void notifyVideoSize_l(); void seekAudioIfNecessary_l(); void cancelPlayerEvents(bool keepBufferingGoing = false); diff --git a/media/libstagefright/include/SoftwareRenderer.h b/media/libstagefright/include/SoftwareRenderer.h index 9eed089..8d58056 100644 --- a/media/libstagefright/include/SoftwareRenderer.h +++ b/media/libstagefright/include/SoftwareRenderer.h @@ -24,14 +24,14 @@ namespace android { -class ISurface; +class Surface; class MemoryHeapBase; class SoftwareRenderer : public VideoRenderer { public: SoftwareRenderer( OMX_COLOR_FORMATTYPE colorFormat, - const sp<ISurface> &surface, + const sp<Surface> &surface, size_t displayWidth, size_t displayHeight, size_t decodedWidth, size_t decodedHeight); @@ -41,14 +41,18 @@ public: const void *data, size_t size, void *platformPrivate); private: + enum YUVMode { + None, + YUV420ToYUV420sp, + YUV420spToYUV420sp, + }; + OMX_COLOR_FORMATTYPE mColorFormat; - ColorConverter mConverter; - sp<ISurface> mISurface; + ColorConverter *mConverter; + YUVMode mYUVMode; + sp<Surface> mSurface; size_t mDisplayWidth, mDisplayHeight; size_t mDecodedWidth, mDecodedHeight; - size_t mFrameSize; - sp<MemoryHeapBase> mMemoryHeap; - int mIndex; SoftwareRenderer(const SoftwareRenderer &); SoftwareRenderer &operator=(const SoftwareRenderer &); diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp index 6de761f..88b9605 100644 --- a/media/libstagefright/omx/OMX.cpp +++ b/media/libstagefright/omx/OMX.cpp @@ -495,12 +495,17 @@ sp<IOMXRenderer> OMX::createRenderer( } if (!impl) { +#if 0 LOGW("Using software renderer."); impl = new SoftwareRenderer( colorFormat, surface, displayWidth, displayHeight, encodedWidth, encodedHeight); +#else + CHECK(!"Should not be here."); + return NULL; +#endif } return new OMXRenderer(impl); diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp index 5f8f5fd..5345218 100644 --- a/media/libstagefright/rtsp/ARTSPConnection.cpp +++ b/media/libstagefright/rtsp/ARTSPConnection.cpp @@ -116,7 +116,7 @@ bool ARTSPConnection::ParseURL( path->setTo(slashPos); } - char *colonPos = strchr(host->c_str(), ':'); + const char *colonPos = strchr(host->c_str(), ':'); if (colonPos != NULL) { unsigned long x; diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp index 4ea7fda..4a8cce8 100644 --- a/media/libstagefright/rtsp/ASessionDescription.cpp +++ b/media/libstagefright/rtsp/ASessionDescription.cpp @@ -182,7 +182,7 @@ void ASessionDescription::getFormatType( AString format; getFormat(index, &format); - char *lastSpacePos = strrchr(format.c_str(), ' '); + const char *lastSpacePos = strrchr(format.c_str(), ' '); CHECK(lastSpacePos != NULL); char *end; diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h index e248463..caff435 100644 --- a/media/libstagefright/rtsp/MyHandler.h +++ b/media/libstagefright/rtsp/MyHandler.h @@ -563,7 +563,7 @@ private: out->setTo(baseURL); out->append(url); } else { - char *slashPos = strrchr(baseURL, '/'); + const char *slashPos = strrchr(baseURL, '/'); if (slashPos > &baseURL[6]) { out->setTo(baseURL, slashPos - baseURL); diff --git a/media/libstagefright/yuv/Android.mk b/media/libstagefright/yuv/Android.mk new file mode 100644 index 0000000..0794ad1 --- /dev/null +++ b/media/libstagefright/yuv/Android.mk @@ -0,0 +1,13 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + YUVImage.cpp \ + YUVCanvas.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils + +LOCAL_MODULE:= libstagefright_yuv + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/libstagefright/yuv/YUVCanvas.cpp b/media/libstagefright/yuv/YUVCanvas.cpp new file mode 100644 index 0000000..38aa779 --- /dev/null +++ b/media/libstagefright/yuv/YUVCanvas.cpp @@ -0,0 +1,111 @@ +/* + * 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 "YUVCanvas" + +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/YUVCanvas.h> +#include <media/stagefright/YUVImage.h> +#include <ui/Rect.h> + +namespace android { + +YUVCanvas::YUVCanvas(YUVImage &yuvImage) + : mYUVImage(yuvImage) { +} + +YUVCanvas::~YUVCanvas() { +} + +void YUVCanvas::FillYUV(uint8_t yValue, uint8_t uValue, uint8_t vValue) { + for (int32_t y = 0; y < mYUVImage.height(); ++y) { + for (int32_t x = 0; x < mYUVImage.width(); ++x) { + mYUVImage.setPixelValue(x, y, yValue, uValue, vValue); + } + } +} + +void YUVCanvas::FillYUVRectangle(const Rect& rect, + uint8_t yValue, uint8_t uValue, uint8_t vValue) { + for (int32_t y = rect.top; y < rect.bottom; ++y) { + for (int32_t x = rect.left; x < rect.right; ++x) { + mYUVImage.setPixelValue(x, y, yValue, uValue, vValue); + } + } +} + +void YUVCanvas::CopyImageRect( + const Rect& srcRect, + int32_t destStartX, int32_t destStartY, + const YUVImage &srcImage) { + + // Try fast copy first + if (YUVImage::fastCopyRectangle( + srcRect, + destStartX, destStartY, + srcImage, mYUVImage)) { + return; + } + + int32_t srcStartX = srcRect.left; + int32_t srcStartY = srcRect.top; + for (int32_t offsetY = 0; offsetY < srcRect.height(); ++offsetY) { + for (int32_t offsetX = 0; offsetX < srcRect.width(); ++offsetX) { + int32_t srcX = srcStartX + offsetX; + int32_t srcY = srcStartY + offsetY; + + int32_t destX = destStartX + offsetX; + int32_t destY = destStartY + offsetY; + + uint8_t yValue; + uint8_t uValue; + uint8_t vValue; + + srcImage.getPixelValue(srcX, srcY, &yValue, &uValue, &vValue); + mYUVImage.setPixelValue(destX, destY, yValue, uValue, vValue); + } + } +} + +void YUVCanvas::downsample( + int32_t srcOffsetX, int32_t srcOffsetY, + int32_t skipX, int32_t skipY, + const YUVImage &srcImage) { + // TODO: Add a low pass filter for downsampling. + + // Check that srcImage is big enough to fill mYUVImage. + CHECK((srcOffsetX + (mYUVImage.width() - 1) * skipX) < srcImage.width()); + CHECK((srcOffsetY + (mYUVImage.height() - 1) * skipY) < srcImage.height()); + + uint8_t yValue; + uint8_t uValue; + uint8_t vValue; + + int32_t srcY = srcOffsetY; + for (int32_t y = 0; y < mYUVImage.height(); ++y) { + int32_t srcX = srcOffsetX; + for (int32_t x = 0; x < mYUVImage.width(); ++x) { + srcImage.getPixelValue(srcX, srcY, &yValue, &uValue, &vValue); + mYUVImage.setPixelValue(x, y, yValue, uValue, vValue); + + srcX += skipX; + } + srcY += skipY; + } +} + +} // namespace android diff --git a/media/libstagefright/yuv/YUVImage.cpp b/media/libstagefright/yuv/YUVImage.cpp new file mode 100644 index 0000000..b712062 --- /dev/null +++ b/media/libstagefright/yuv/YUVImage.cpp @@ -0,0 +1,413 @@ +/* + * 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 "YUVImage" + +#include <media/stagefright/YUVImage.h> +#include <ui/Rect.h> +#include <media/stagefright/MediaDebug.h> + +namespace android { + +YUVImage::YUVImage(YUVFormat yuvFormat, int32_t width, int32_t height) { + mYUVFormat = yuvFormat; + mWidth = width; + mHeight = height; + + size_t numberOfBytes = bufferSize(yuvFormat, width, height); + uint8_t *buffer = new uint8_t[numberOfBytes]; + mBuffer = buffer; + mOwnBuffer = true; + + initializeYUVPointers(); +} + +YUVImage::YUVImage(YUVFormat yuvFormat, int32_t width, int32_t height, uint8_t *buffer) { + mYUVFormat = yuvFormat; + mWidth = width; + mHeight = height; + mBuffer = buffer; + mOwnBuffer = false; + + initializeYUVPointers(); +} + +//static +size_t YUVImage::bufferSize(YUVFormat yuvFormat, int32_t width, int32_t height) { + int32_t numberOfPixels = width*height; + size_t numberOfBytes = 0; + if (yuvFormat == YUV420Planar || yuvFormat == YUV420SemiPlanar) { + // Y takes numberOfPixels bytes and U/V take numberOfPixels/4 bytes each. + numberOfBytes = (size_t)(numberOfPixels + (numberOfPixels >> 1)); + } else { + LOGE("Format not supported"); + } + return numberOfBytes; +} + +bool YUVImage::initializeYUVPointers() { + int32_t numberOfPixels = mWidth * mHeight; + + if (mYUVFormat == YUV420Planar) { + mYdata = (uint8_t *)mBuffer; + mUdata = mYdata + numberOfPixels; + mVdata = mUdata + (numberOfPixels >> 2); + } else if (mYUVFormat == YUV420SemiPlanar) { + // U and V channels are interleaved as VUVUVU. + // So V data starts at the end of Y channel and + // U data starts right after V's start. + mYdata = (uint8_t *)mBuffer; + mVdata = mYdata + numberOfPixels; + mUdata = mVdata + 1; + } else { + LOGE("Format not supported"); + return false; + } + return true; +} + +YUVImage::~YUVImage() { + if (mOwnBuffer) delete[] mBuffer; +} + +bool YUVImage::getOffsets(int32_t x, int32_t y, + int32_t *yOffset, int32_t *uOffset, int32_t *vOffset) const { + *yOffset = y*mWidth + x; + + int32_t uvOffset = (y >> 1) * (mWidth >> 1) + (x >> 1); + if (mYUVFormat == YUV420Planar) { + *uOffset = uvOffset; + *vOffset = uvOffset; + } else if (mYUVFormat == YUV420SemiPlanar) { + // Since U and V channels are interleaved, offsets need + // to be doubled. + *uOffset = 2*uvOffset; + *vOffset = 2*uvOffset; + } else { + LOGE("Format not supported"); + return false; + } + + return true; +} + +bool YUVImage::getOffsetIncrementsPerDataRow( + int32_t *yDataOffsetIncrement, + int32_t *uDataOffsetIncrement, + int32_t *vDataOffsetIncrement) const { + *yDataOffsetIncrement = mWidth; + + int32_t uvDataOffsetIncrement = mWidth >> 1; + + if (mYUVFormat == YUV420Planar) { + *uDataOffsetIncrement = uvDataOffsetIncrement; + *vDataOffsetIncrement = uvDataOffsetIncrement; + } else if (mYUVFormat == YUV420SemiPlanar) { + // Since U and V channels are interleaved, offsets need + // to be doubled. + *uDataOffsetIncrement = 2*uvDataOffsetIncrement; + *vDataOffsetIncrement = 2*uvDataOffsetIncrement; + } else { + LOGE("Format not supported"); + return false; + } + + return true; +} + +uint8_t* YUVImage::getYAddress(int32_t offset) const { + return mYdata + offset; +} + +uint8_t* YUVImage::getUAddress(int32_t offset) const { + return mUdata + offset; +} + +uint8_t* YUVImage::getVAddress(int32_t offset) const { + return mVdata + offset; +} + +bool YUVImage::getYUVAddresses(int32_t x, int32_t y, + uint8_t **yAddr, uint8_t **uAddr, uint8_t **vAddr) const { + int32_t yOffset; + int32_t uOffset; + int32_t vOffset; + if (!getOffsets(x, y, &yOffset, &uOffset, &vOffset)) return false; + + *yAddr = getYAddress(yOffset); + *uAddr = getUAddress(uOffset); + *vAddr = getVAddress(vOffset); + + return true; +} + +bool YUVImage::validPixel(int32_t x, int32_t y) const { + return (x >= 0 && x < mWidth && + y >= 0 && y < mHeight); +} + +bool YUVImage::getPixelValue(int32_t x, int32_t y, + uint8_t *yPtr, uint8_t *uPtr, uint8_t *vPtr) const { + CHECK(validPixel(x, y)); + + uint8_t *yAddr; + uint8_t *uAddr; + uint8_t *vAddr; + if (!getYUVAddresses(x, y, &yAddr, &uAddr, &vAddr)) return false; + + *yPtr = *yAddr; + *uPtr = *uAddr; + *vPtr = *vAddr; + + return true; +} + +bool YUVImage::setPixelValue(int32_t x, int32_t y, + uint8_t yValue, uint8_t uValue, uint8_t vValue) { + CHECK(validPixel(x, y)); + + uint8_t *yAddr; + uint8_t *uAddr; + uint8_t *vAddr; + if (!getYUVAddresses(x, y, &yAddr, &uAddr, &vAddr)) return false; + + *yAddr = yValue; + *uAddr = uValue; + *vAddr = vValue; + + return true; +} + +void YUVImage::fastCopyRectangle420Planar( + const Rect& srcRect, + int32_t destStartX, int32_t destStartY, + const YUVImage &srcImage, YUVImage &destImage) { + CHECK(srcImage.mYUVFormat == YUV420Planar); + CHECK(destImage.mYUVFormat == YUV420Planar); + + int32_t srcStartX = srcRect.left; + int32_t srcStartY = srcRect.top; + int32_t width = srcRect.width(); + int32_t height = srcRect.height(); + + // Get source and destination start addresses + uint8_t *ySrcAddrBase; + uint8_t *uSrcAddrBase; + uint8_t *vSrcAddrBase; + srcImage.getYUVAddresses(srcStartX, srcStartY, + &ySrcAddrBase, &uSrcAddrBase, &vSrcAddrBase); + + uint8_t *yDestAddrBase; + uint8_t *uDestAddrBase; + uint8_t *vDestAddrBase; + destImage.getYUVAddresses(destStartX, destStartY, + &yDestAddrBase, &uDestAddrBase, &vDestAddrBase); + + // Get source and destination offset increments incurred in going + // from one data row to next. + int32_t ySrcOffsetIncrement; + int32_t uSrcOffsetIncrement; + int32_t vSrcOffsetIncrement; + srcImage.getOffsetIncrementsPerDataRow( + &ySrcOffsetIncrement, &uSrcOffsetIncrement, &vSrcOffsetIncrement); + + int32_t yDestOffsetIncrement; + int32_t uDestOffsetIncrement; + int32_t vDestOffsetIncrement; + destImage.getOffsetIncrementsPerDataRow( + &yDestOffsetIncrement, &uDestOffsetIncrement, &vDestOffsetIncrement); + + // Copy Y + { + size_t numberOfYBytesPerRow = (size_t) width; + uint8_t *ySrcAddr = ySrcAddrBase; + uint8_t *yDestAddr = yDestAddrBase; + for (int32_t offsetY = 0; offsetY < height; ++offsetY) { + memcpy(yDestAddr, ySrcAddr, numberOfYBytesPerRow); + + ySrcAddr += ySrcOffsetIncrement; + yDestAddr += yDestOffsetIncrement; + } + } + + // Copy U + { + size_t numberOfUBytesPerRow = (size_t) (width >> 1); + uint8_t *uSrcAddr = uSrcAddrBase; + uint8_t *uDestAddr = uDestAddrBase; + // Every other row has an entry for U/V channel values. Hence only + // go half the height. + for (int32_t offsetY = 0; offsetY < (height >> 1); ++offsetY) { + memcpy(uDestAddr, uSrcAddr, numberOfUBytesPerRow); + + uSrcAddr += uSrcOffsetIncrement; + uDestAddr += uDestOffsetIncrement; + } + } + + // Copy V + { + size_t numberOfVBytesPerRow = (size_t) (width >> 1); + uint8_t *vSrcAddr = vSrcAddrBase; + uint8_t *vDestAddr = vDestAddrBase; + // Every other pixel row has a U/V data row. Hence only go half the height. + for (int32_t offsetY = 0; offsetY < (height >> 1); ++offsetY) { + memcpy(vDestAddr, vSrcAddr, numberOfVBytesPerRow); + + vSrcAddr += vSrcOffsetIncrement; + vDestAddr += vDestOffsetIncrement; + } + } +} + +void YUVImage::fastCopyRectangle420SemiPlanar( + const Rect& srcRect, + int32_t destStartX, int32_t destStartY, + const YUVImage &srcImage, YUVImage &destImage) { + CHECK(srcImage.mYUVFormat == YUV420SemiPlanar); + CHECK(destImage.mYUVFormat == YUV420SemiPlanar); + + int32_t srcStartX = srcRect.left; + int32_t srcStartY = srcRect.top; + int32_t width = srcRect.width(); + int32_t height = srcRect.height(); + + // Get source and destination start addresses + uint8_t *ySrcAddrBase; + uint8_t *uSrcAddrBase; + uint8_t *vSrcAddrBase; + srcImage.getYUVAddresses(srcStartX, srcStartY, + &ySrcAddrBase, &uSrcAddrBase, &vSrcAddrBase); + + uint8_t *yDestAddrBase; + uint8_t *uDestAddrBase; + uint8_t *vDestAddrBase; + destImage.getYUVAddresses(destStartX, destStartY, + &yDestAddrBase, &uDestAddrBase, &vDestAddrBase); + + // Get source and destination offset increments incurred in going + // from one data row to next. + int32_t ySrcOffsetIncrement; + int32_t uSrcOffsetIncrement; + int32_t vSrcOffsetIncrement; + srcImage.getOffsetIncrementsPerDataRow( + &ySrcOffsetIncrement, &uSrcOffsetIncrement, &vSrcOffsetIncrement); + + int32_t yDestOffsetIncrement; + int32_t uDestOffsetIncrement; + int32_t vDestOffsetIncrement; + destImage.getOffsetIncrementsPerDataRow( + &yDestOffsetIncrement, &uDestOffsetIncrement, &vDestOffsetIncrement); + + // Copy Y + { + size_t numberOfYBytesPerRow = (size_t) width; + uint8_t *ySrcAddr = ySrcAddrBase; + uint8_t *yDestAddr = yDestAddrBase; + for (int32_t offsetY = 0; offsetY < height; ++offsetY) { + memcpy(yDestAddr, ySrcAddr, numberOfYBytesPerRow); + + ySrcAddr = ySrcAddr + ySrcOffsetIncrement; + yDestAddr = yDestAddr + yDestOffsetIncrement; + } + } + + // Copy UV + { + // UV are interleaved. So number of UV bytes per row is 2*(width/2). + size_t numberOfUVBytesPerRow = (size_t) width; + uint8_t *vSrcAddr = vSrcAddrBase; + uint8_t *vDestAddr = vDestAddrBase; + // Every other pixel row has a U/V data row. Hence only go half the height. + for (int32_t offsetY = 0; offsetY < (height >> 1); ++offsetY) { + memcpy(vDestAddr, vSrcAddr, numberOfUVBytesPerRow); + + vSrcAddr += vSrcOffsetIncrement; + vDestAddr += vDestOffsetIncrement; + } + } +} + +// static +bool YUVImage::fastCopyRectangle( + const Rect& srcRect, + int32_t destStartX, int32_t destStartY, + const YUVImage &srcImage, YUVImage &destImage) { + if (srcImage.mYUVFormat == destImage.mYUVFormat) { + if (srcImage.mYUVFormat == YUV420Planar) { + fastCopyRectangle420Planar( + srcRect, + destStartX, destStartY, + srcImage, destImage); + } else if (srcImage.mYUVFormat == YUV420SemiPlanar) { + fastCopyRectangle420SemiPlanar( + srcRect, + destStartX, destStartY, + srcImage, destImage); + } + return true; + } + return false; +} + +uint8_t clamp(uint8_t v, uint8_t minValue, uint8_t maxValue) { + CHECK(maxValue >= minValue); + + if (v < minValue) return minValue; + else if (v > maxValue) return maxValue; + else return v; +} + +void YUVImage::yuv2rgb(uint8_t yValue, uint8_t uValue, uint8_t vValue, + uint8_t *r, uint8_t *g, uint8_t *b) const { + *r = yValue + (1.370705 * (vValue-128)); + *g = yValue - (0.698001 * (vValue-128)) - (0.337633 * (uValue-128)); + *b = yValue + (1.732446 * (uValue-128)); + + *r = clamp(*r, 0, 255); + *g = clamp(*g, 0, 255); + *b = clamp(*b, 0, 255); +} + +bool YUVImage::writeToPPM(const char *filename) const { + FILE *fp = fopen(filename, "w"); + if (fp == NULL) { + return false; + } + fprintf(fp, "P3\n"); + fprintf(fp, "%d %d\n", mWidth, mHeight); + fprintf(fp, "255\n"); + for (int32_t y = 0; y < mHeight; ++y) { + for (int32_t x = 0; x < mWidth; ++x) { + uint8_t yValue; + uint8_t uValue; + uint8_t vValue; + getPixelValue(x, y, &yValue, &uValue, & vValue); + + uint8_t rValue; + uint8_t gValue; + uint8_t bValue; + yuv2rgb(yValue, uValue, vValue, &rValue, &gValue, &bValue); + + fprintf(fp, "%d %d %d\n", (int32_t)rValue, (int32_t)gValue, (int32_t)bValue); + } + } + fclose(fp); + return true; +} + +} // namespace android diff --git a/media/mtp/Android.mk b/media/mtp/Android.mk new file mode 100644 index 0000000..7502f6e --- /dev/null +++ b/media/mtp/Android.mk @@ -0,0 +1,78 @@ +# +# 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. +# + +LOCAL_PATH:= $(call my-dir) + +ifneq ($(TARGET_SIMULATOR),true) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + MtpClient.cpp \ + MtpCursor.cpp \ + MtpDataPacket.cpp \ + MtpDebug.cpp \ + MtpDevice.cpp \ + MtpEventPacket.cpp \ + MtpDeviceInfo.cpp \ + MtpObjectInfo.cpp \ + MtpPacket.cpp \ + MtpProperty.cpp \ + MtpRequestPacket.cpp \ + MtpResponsePacket.cpp \ + MtpServer.cpp \ + MtpStorageInfo.cpp \ + MtpStringBuffer.cpp \ + MtpStorage.cpp \ + MtpUtils.cpp \ + +LOCAL_MODULE:= libmtp + +LOCAL_CFLAGS := -DMTP_DEVICE -DMTP_HOST + +include $(BUILD_STATIC_LIBRARY) + +endif + +ifeq ($(HOST_OS),linux) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + MtpClient.cpp \ + MtpCursor.cpp \ + MtpDataPacket.cpp \ + MtpDebug.cpp \ + MtpDevice.cpp \ + MtpEventPacket.cpp \ + MtpDeviceInfo.cpp \ + MtpObjectInfo.cpp \ + MtpPacket.cpp \ + MtpProperty.cpp \ + MtpRequestPacket.cpp \ + MtpResponsePacket.cpp \ + MtpStorageInfo.cpp \ + MtpStringBuffer.cpp \ + MtpStorage.cpp \ + MtpUtils.cpp \ + +LOCAL_MODULE:= libmtp + +LOCAL_CFLAGS := -DMTP_HOST + +include $(BUILD_HOST_STATIC_LIBRARY) + +endif diff --git a/media/mtp/MtpClient.cpp b/media/mtp/MtpClient.cpp new file mode 100644 index 0000000..ceb6a43 --- /dev/null +++ b/media/mtp/MtpClient.cpp @@ -0,0 +1,262 @@ +/* + * 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> +#include <linux/version.h> +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20) +#include <linux/usb/ch9.h> +#else +#include <linux/usb_ch9.h> +#endif + +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; + } + + struct usb_endpoint *ep_in = usb_endpoint_open(device, ep_in_desc); + struct usb_endpoint *ep_out = usb_endpoint_open(device, ep_out_desc); + struct usb_endpoint *ep_intr = usb_endpoint_open(device, ep_intr_desc); + + if (usb_device_claim_interface(device, interface->bInterfaceNumber)) { + LOGE("usb_device_claim_interface failed errno: %d\n", errno); + usb_endpoint_close(ep_in); + usb_endpoint_close(ep_out); + usb_endpoint_close(ep_intr); + return mDone; + } + + MtpDevice* mtpDevice = new MtpDevice(device, interface->bInterfaceNumber, + ep_in, ep_out, ep_intr); + 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 new file mode 100644 index 0000000..fa5c527 --- /dev/null +++ b/media/mtp/MtpClient.h @@ -0,0 +1,68 @@ +/* + * 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/MtpCursor.cpp b/media/mtp/MtpCursor.cpp new file mode 100644 index 0000000..3ba07cc --- /dev/null +++ b/media/mtp/MtpCursor.cpp @@ -0,0 +1,460 @@ +/* + * 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 "MtpCursor" + +#include "MtpDebug.h" +#include "MtpClient.h" +#include "MtpCursor.h" +#include "MtpDevice.h" +#include "MtpDeviceInfo.h" +#include "MtpObjectInfo.h" +#include "MtpStorageInfo.h" + + +#include "binder/CursorWindow.h" + +namespace android { + +/* Device Column IDs */ +/* These must match the values in MtpCursor.java */ +#define DEVICE_ROW_ID 1 +#define DEVICE_MANUFACTURER 2 +#define DEVICE_MODEL 3 + +/* Storage Column IDs */ +/* These must match the values in MtpCursor.java */ +#define STORAGE_ROW_ID 101 +#define STORAGE_IDENTIFIER 102 +#define STORAGE_DESCRIPTION 103 + +/* Object Column IDs */ +/* These must match the values in MtpCursor.java */ +#define OBJECT_ROW_ID 201 +#define OBJECT_STORAGE_ID 202 +#define OBJECT_FORMAT 203 +#define OBJECT_PROTECTION_STATUS 204 +#define OBJECT_SIZE 205 +#define OBJECT_THUMB_FORMAT 206 +#define OBJECT_THUMB_SIZE 207 +#define OBJECT_THUMB_WIDTH 208 +#define OBJECT_THUMB_HEIGHT 209 +#define OBJECT_IMAGE_WIDTH 210 +#define OBJECT_IMAGE_HEIGHT 211 +#define OBJECT_IMAGE_DEPTH 212 +#define OBJECT_PARENT 213 +#define OBJECT_ASSOCIATION_TYPE 214 +#define OBJECT_ASSOCIATION_DESC 215 +#define OBJECT_SEQUENCE_NUMBER 216 +#define OBJECT_NAME 217 +#define OBJECT_DATE_CREATED 218 +#define OBJECT_DATE_MODIFIED 219 +#define OBJECT_KEYWORDS 220 +#define OBJECT_THUMB 221 + +MtpCursor::MtpCursor(MtpClient* client, int queryType, int deviceID, + int storageID, int objectID, int columnCount, int* columns) + : mClient(client), + mQueryType(queryType), + mDeviceID(deviceID), + mStorageID(storageID), + mQbjectID(objectID), + mColumnCount(columnCount), + mColumns(NULL) +{ + if (columns) { + mColumns = new int[columnCount]; + memcpy(mColumns, columns, columnCount * sizeof(int)); + } +} + +MtpCursor::~MtpCursor() { + delete[] mColumns; +} + +int MtpCursor::fillWindow(CursorWindow* window, int startPos) { + LOGD("MtpCursor::fillWindow mQueryType: %d\n", mQueryType); + + switch (mQueryType) { + case DEVICE: + return fillDevices(window, startPos); + case DEVICE_ID: + return fillDevice(window, startPos); + case STORAGE: + return fillStorages(window, startPos); + case STORAGE_ID: + return fillStorage(window, startPos); + case OBJECT: + return fillObjects(window, 0, startPos); + case OBJECT_ID: + return fillObject(window, startPos); + case STORAGE_CHILDREN: + return fillObjects(window, -1, startPos); + case OBJECT_CHILDREN: + return fillObjects(window, mQbjectID, startPos); + default: + LOGE("MtpCursor::fillWindow: unknown query type %d\n", mQueryType); + return 0; + } +} + +int MtpCursor::fillDevices(CursorWindow* window, int startPos) { + int count = 0; + MtpDeviceList& deviceList = mClient->getDeviceList(); + for (int i = 0; i < deviceList.size(); i++) { + MtpDevice* device = deviceList[i]; + if (fillDevice(window, device, startPos)) { + count++; + startPos++; + } else { + break; + } + } + return count; +} + +int MtpCursor::fillDevice(CursorWindow* window, int startPos) { + MtpDevice* device = mClient->getDevice(mDeviceID); + if (device && fillDevice(window, device, startPos)) + return 1; + else + return 0; +} + +int MtpCursor::fillStorages(CursorWindow* window, int startPos) { + int count = 0; + MtpDevice* device = mClient->getDevice(mDeviceID); + if (!device) + return 0; + MtpStorageIDList* storageIDs = device->getStorageIDs(); + if (!storageIDs) + return 0; + + for (int i = 0; i < storageIDs->size(); i++) { + MtpStorageID storageID = (*storageIDs)[i]; + if (fillStorage(window, device, storageID, startPos)) { + count++; + startPos++; + } else { + break; + } + } + delete storageIDs; + return count; +} + +int MtpCursor::fillStorage(CursorWindow* window, int startPos) { + MtpDevice* device = mClient->getDevice(mDeviceID); + if (device && fillStorage(window, device, mStorageID, startPos)) + return 1; + else + return 0; +} + +int MtpCursor::fillObjects(CursorWindow* window, int parent, int startPos) { + int count = 0; + MtpDevice* device = mClient->getDevice(mDeviceID); + if (!device) + return 0; + MtpObjectHandleList* handles = device->getObjectHandles(mStorageID, 0, parent); + if (!handles) + return 0; + + for (int i = 0; i < handles->size(); i++) { + MtpObjectHandle handle = (*handles)[i]; + if (fillObject(window, device, handle, startPos)) { + count++; + startPos++; + } else { + break; + } + } + delete handles; + return count; +} + +int MtpCursor::fillObject(CursorWindow* window, int startPos) { + MtpDevice* device = mClient->getDevice(mDeviceID); + if (device && fillObject(window, device, mQbjectID, startPos)) + return 1; + else + return 0; +} + +bool MtpCursor::fillDevice(CursorWindow* window, MtpDevice* device, int row) { + MtpDeviceInfo* deviceInfo = device->getDeviceInfo(); + if (!deviceInfo) + return false; + if (!prepareRow(window)) + return false; + + for (int i = 0; i < mColumnCount; i++) { + switch (mColumns[i]) { + case DEVICE_ROW_ID: + if (!putLong(window, device->getID(), row, i)) + return false; + break; + case DEVICE_MANUFACTURER: + if (!putString(window, deviceInfo->mManufacturer, row, i)) + return false; + break; + case DEVICE_MODEL: + if (!putString(window, deviceInfo->mModel, row, i)) + return false; + break; + default: + LOGE("fillDevice: unknown column %d\n", mColumns[i]); + return false; + } + } + + return true; +} + +bool MtpCursor::fillStorage(CursorWindow* window, MtpDevice* device, + MtpStorageID storageID, int row) { + +LOGD("fillStorage %d\n", storageID); + + MtpStorageInfo* storageInfo = device->getStorageInfo(storageID); + if (!storageInfo) + return false; + if (!prepareRow(window)) { + delete storageInfo; + return false; + } + + const char* text; + for (int i = 0; i < mColumnCount; i++) { + switch (mColumns[i]) { + case STORAGE_ROW_ID: + if (!putLong(window, storageID, row, i)) + goto fail; + break; + case STORAGE_IDENTIFIER: + text = storageInfo->mVolumeIdentifier; + if (!text || !text[0]) + text = "Camera Storage"; + if (!putString(window, text, row, i)) + goto fail; + break; + case STORAGE_DESCRIPTION: + text = storageInfo->mStorageDescription; + if (!text || !text[0]) + text = "Storage Description"; + if (!putString(window, text, row, i)) + goto fail; + break; + default: + LOGE("fillStorage: unknown column %d\n", mColumns[i]); + goto fail; + } + } + + delete storageInfo; + return true; + +fail: + delete storageInfo; + return false; +} + +bool MtpCursor::fillObject(CursorWindow* window, MtpDevice* device, + MtpObjectHandle objectID, int row) { + + MtpObjectInfo* objectInfo = device->getObjectInfo(objectID); + if (!objectInfo) + return false; + // objectInfo->print(); + if (!prepareRow(window)) { + delete objectInfo; + return false; + } + + for (int i = 0; i < mColumnCount; i++) { + switch (mColumns[i]) { + case OBJECT_ROW_ID: + if (!putLong(window, objectID, row, i)) + goto fail; + break; + case OBJECT_STORAGE_ID: + if (!putLong(window, objectInfo->mStorageID, row, i)) + goto fail; + break; + case OBJECT_FORMAT: + if (!putLong(window, objectInfo->mFormat, row, i)) + goto fail; + break; + case OBJECT_PROTECTION_STATUS: + if (!putLong(window, objectInfo->mProtectionStatus, row, i)) + goto fail; + break; + case OBJECT_SIZE: + if (!putLong(window, objectInfo->mCompressedSize, row, i)) + goto fail; + break; + case OBJECT_THUMB_FORMAT: + if (!putLong(window, objectInfo->mThumbFormat, row, i)) + goto fail; + break; + case OBJECT_THUMB_SIZE: + if (!putLong(window, objectInfo->mThumbCompressedSize, row, i)) + goto fail; + break; + case OBJECT_THUMB_WIDTH: + if (!putLong(window, objectInfo->mThumbPixWidth, row, i)) + goto fail; + break; + case OBJECT_THUMB_HEIGHT: + if (!putLong(window, objectInfo->mThumbPixHeight, row, i)) + goto fail; + break; + case OBJECT_IMAGE_WIDTH: + if (!putLong(window, objectInfo->mImagePixWidth, row, i)) + goto fail; + break; + case OBJECT_IMAGE_HEIGHT: + if (!putLong(window, objectInfo->mImagePixHeight, row, i)) + goto fail; + break; + case OBJECT_IMAGE_DEPTH: + if (!putLong(window, objectInfo->mImagePixDepth, row, i)) + goto fail; + break; + case OBJECT_PARENT: + if (!putLong(window, objectInfo->mParent, row, i)) + goto fail; + break; + case OBJECT_ASSOCIATION_TYPE: + if (!putLong(window, objectInfo->mAssociationType, row, i)) + goto fail; + break; + case OBJECT_ASSOCIATION_DESC: + if (!putLong(window, objectInfo->mAssociationDesc, row, i)) + goto fail; + break; + case OBJECT_SEQUENCE_NUMBER: + if (!putLong(window, objectInfo->mSequenceNumber, row, i)) + goto fail; + break; + case OBJECT_NAME: + if (!putString(window, objectInfo->mName, row, i)) + goto fail; + break; + case OBJECT_DATE_CREATED: + if (!putLong(window, objectInfo->mDateCreated, row, i)) + goto fail; + break; + case OBJECT_DATE_MODIFIED: + if (!putLong(window, objectInfo->mDateModified, row, i)) + goto fail; + break; + case OBJECT_KEYWORDS: + if (!putString(window, objectInfo->mKeywords, row, i)) + goto fail; + break; + case OBJECT_THUMB: + if (!putThumbnail(window, objectID, objectInfo->mFormat, row, i)) + goto fail; + break; + default: + LOGE("fillStorage: unknown column %d\n", mColumns[i]); + goto fail; + } + } + + delete objectInfo; + return true; + +fail: + delete objectInfo; + return false; +} + +bool MtpCursor::prepareRow(CursorWindow* window) { + if (!window->setNumColumns(mColumnCount)) { + LOGE("Failed to change column count from %d to %d", window->getNumColumns(), mColumnCount); + return false; + } + field_slot_t * fieldDir = window->allocRow(); + if (!fieldDir) { + LOGE("Failed allocating fieldDir"); + return false; + } + return true; +} + + +bool MtpCursor::putLong(CursorWindow* window, int value, int row, int column) { + + if (!window->putLong(row, column, value)) { + window->freeLastRow(); + LOGE("Failed allocating space for a long in column %d", column); + return false; + } + return true; +} + +bool MtpCursor::putString(CursorWindow* window, const char* text, int row, int column) { + int size = strlen(text) + 1; + int offset = window->alloc(size); + if (!offset) { + window->freeLastRow(); + LOGE("Failed allocating %u bytes for text/blob %s", size, text); + return false; + } + window->copyIn(offset, (const uint8_t*)text, size); + + // This must be updated after the call to alloc(), since that + // may move the field around in the window + field_slot_t * fieldSlot = window->getFieldSlot(row, column); + fieldSlot->type = FIELD_TYPE_STRING; + fieldSlot->data.buffer.offset = offset; + fieldSlot->data.buffer.size = size; + return true; +} + +bool MtpCursor::putThumbnail(CursorWindow* window, int objectID, int format, int row, int column) { + MtpDevice* device = mClient->getDevice(mDeviceID); + void* thumbnail; + int size, offset; + if (format == MTP_FORMAT_ASSOCIATION) { + thumbnail = NULL; + size = offset = 0; + } else { + thumbnail = device->getThumbnail(objectID, size); + + LOGV("putThumbnail: %p, size: %d\n", thumbnail, size); + offset = window->alloc(size); + if (!offset) { + window->freeLastRow(); + LOGE("Failed allocating %u bytes for thumbnail", size); + return false; + } + } + if (thumbnail) + window->copyIn(offset, (const uint8_t*)thumbnail, size); + + // This must be updated after the call to alloc(), since that + // may move the field around in the window + field_slot_t * fieldSlot = window->getFieldSlot(row, column); + fieldSlot->type = FIELD_TYPE_BLOB; + fieldSlot->data.buffer.offset = offset; + fieldSlot->data.buffer.size = size; + return true; +} + +} // namespace android diff --git a/media/mtp/MtpCursor.h b/media/mtp/MtpCursor.h new file mode 100644 index 0000000..3f84753 --- /dev/null +++ b/media/mtp/MtpCursor.h @@ -0,0 +1,76 @@ +/* + * 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_CURSOR_H +#define _MTP_CURSOR_H + +#include "MtpTypes.h" + +namespace android { + +class CursorWindow; + +class MtpCursor { +private: + enum { + DEVICE = 1, + DEVICE_ID = 2, + STORAGE = 3, + STORAGE_ID = 4, + OBJECT = 5, + OBJECT_ID = 6, + STORAGE_CHILDREN = 7, + OBJECT_CHILDREN = 8, + }; + + MtpClient* mClient; + int mQueryType; + int mDeviceID; + int mStorageID; + int mQbjectID; + int mColumnCount; + int* mColumns; + +public: + MtpCursor(MtpClient* client, int queryType, int deviceID, + int storageID, int objectID, int columnCount, int* columns); + virtual ~MtpCursor(); + + int fillWindow(CursorWindow* window, int startPos); + +private: + int fillDevices(CursorWindow* window, int startPos); + int fillDevice(CursorWindow* window, int startPos); + int fillStorages(CursorWindow* window, int startPos); + int fillStorage(CursorWindow* window, int startPos); + int fillObjects(CursorWindow* window, int parent, int startPos); + int fillObject(CursorWindow* window, int startPos); + + bool fillDevice(CursorWindow* window, MtpDevice* device, int startPos); + bool fillStorage(CursorWindow* window, MtpDevice* device, + MtpStorageID storageID, int row); + bool fillObject(CursorWindow* window, MtpDevice* device, + MtpObjectHandle objectID, int row); + + bool prepareRow(CursorWindow* window); + bool putLong(CursorWindow* window, int value, int row, int column); + bool putString(CursorWindow* window, const char* text, int row, int column); + bool putThumbnail(CursorWindow* window, int objectID, int format, int row, int column); +}; + +}; // namespace android + +#endif // _MTP_CURSOR_H diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp new file mode 100644 index 0000000..27dc796 --- /dev/null +++ b/media/mtp/MtpDataPacket.cpp @@ -0,0 +1,476 @@ +/* + * 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 "MtpDataPacket" + +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> + +#include <usbhost/usbhost.h> + +#include "MtpDataPacket.h" +#include "MtpStringBuffer.h" + +namespace android { + +MtpDataPacket::MtpDataPacket() + : MtpPacket(512), + mOffset(MTP_CONTAINER_HEADER_SIZE) +{ +} + +MtpDataPacket::~MtpDataPacket() { +} + +void MtpDataPacket::reset() { + MtpPacket::reset(); + mOffset = MTP_CONTAINER_HEADER_SIZE; +} + +void MtpDataPacket::setOperationCode(MtpOperationCode code) { + MtpPacket::putUInt16(MTP_CONTAINER_CODE_OFFSET, code); +} + +void MtpDataPacket::setTransactionID(MtpTransactionID id) { + MtpPacket::putUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET, id); +} + +uint16_t MtpDataPacket::getUInt16() { + int offset = mOffset; + uint16_t result = (uint16_t)mBuffer[offset] | ((uint16_t)mBuffer[offset + 1] << 8); + mOffset += 2; + return result; +} + +uint32_t MtpDataPacket::getUInt32() { + int offset = mOffset; + uint32_t result = (uint32_t)mBuffer[offset] | ((uint32_t)mBuffer[offset + 1] << 8) | + ((uint32_t)mBuffer[offset + 2] << 16) | ((uint32_t)mBuffer[offset + 3] << 24); + mOffset += 4; + return result; +} + +uint64_t MtpDataPacket::getUInt64() { + int offset = mOffset; + uint64_t result = (uint64_t)mBuffer[offset] | ((uint64_t)mBuffer[offset + 1] << 8) | + ((uint64_t)mBuffer[offset + 2] << 16) | ((uint64_t)mBuffer[offset + 3] << 24) | + ((uint64_t)mBuffer[offset + 4] << 32) | ((uint64_t)mBuffer[offset + 5] << 40) | + ((uint64_t)mBuffer[offset + 6] << 48) | ((uint64_t)mBuffer[offset + 7] << 56); + mOffset += 8; + return result; +} + +void MtpDataPacket::getUInt128(uint128_t& value) { + value[0] = getUInt32(); + value[1] = getUInt32(); + value[2] = getUInt32(); + value[3] = getUInt32(); +} + +void MtpDataPacket::getString(MtpStringBuffer& string) +{ + string.readFromPacket(this); +} + +Int8List* MtpDataPacket::getAInt8() { + Int8List* result = new Int8List; + int count = getUInt32(); + for (int i = 0; i < count; i++) + result->push(getInt8()); + return result; +} + +UInt8List* MtpDataPacket::getAUInt8() { + UInt8List* result = new UInt8List; + int count = getUInt32(); + for (int i = 0; i < count; i++) + result->push(getUInt8()); + return result; +} + +Int16List* MtpDataPacket::getAInt16() { + Int16List* result = new Int16List; + int count = getUInt32(); + for (int i = 0; i < count; i++) + result->push(getInt16()); + return result; +} + +UInt16List* MtpDataPacket::getAUInt16() { + UInt16List* result = new UInt16List; + int count = getUInt32(); + for (int i = 0; i < count; i++) + result->push(getUInt16()); + return result; +} + +Int32List* MtpDataPacket::getAInt32() { + Int32List* result = new Int32List; + int count = getUInt32(); + for (int i = 0; i < count; i++) + result->push(getInt32()); + return result; +} + +UInt32List* MtpDataPacket::getAUInt32() { + UInt32List* result = new UInt32List; + int count = getUInt32(); + for (int i = 0; i < count; i++) + result->push(getUInt32()); + return result; +} + +Int64List* MtpDataPacket::getAInt64() { + Int64List* result = new Int64List; + int count = getUInt32(); + for (int i = 0; i < count; i++) + result->push(getInt64()); + return result; +} + +UInt64List* MtpDataPacket::getAUInt64() { + UInt64List* result = new UInt64List; + int count = getUInt32(); + for (int i = 0; i < count; i++) + result->push(getUInt64()); + return result; +} + +void MtpDataPacket::putInt8(int8_t value) { + allocate(mOffset + 1); + mBuffer[mOffset++] = (uint8_t)value; + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putUInt8(uint8_t value) { + allocate(mOffset + 1); + mBuffer[mOffset++] = (uint8_t)value; + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putInt16(int16_t value) { + allocate(mOffset + 2); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putUInt16(uint16_t value) { + allocate(mOffset + 2); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putInt32(int32_t value) { + allocate(mOffset + 4); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putUInt32(uint32_t value) { + allocate(mOffset + 4); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putInt64(int64_t value) { + allocate(mOffset + 8); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 32) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 40) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 48) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 56) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putUInt64(uint64_t value) { + allocate(mOffset + 8); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 32) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 40) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 48) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 56) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putInt128(const int128_t& value) { + putInt32(value[0]); + putInt32(value[1]); + putInt32(value[2]); + putInt32(value[3]); +} + +void MtpDataPacket::putUInt128(const uint128_t& value) { + putUInt32(value[0]); + putUInt32(value[1]); + putUInt32(value[2]); + putUInt32(value[3]); +} + +void MtpDataPacket::putAInt8(const int8_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putInt8(*values++); +} + +void MtpDataPacket::putAUInt8(const uint8_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putUInt8(*values++); +} + +void MtpDataPacket::putAInt16(const int16_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putInt16(*values++); +} + +void MtpDataPacket::putAUInt16(const uint16_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putUInt16(*values++); +} + +void MtpDataPacket::putAUInt16(const UInt16List* values) { + size_t count = (values ? values->size() : 0); + putUInt32(count); + for (size_t i = 0; i < count; i++) + putUInt16((*values)[i]); +} + +void MtpDataPacket::putAInt32(const int32_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putInt32(*values++); +} + +void MtpDataPacket::putAUInt32(const uint32_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putUInt32(*values++); +} + +void MtpDataPacket::putAUInt32(const UInt32List* list) { + if (!list) { + putEmptyArray(); + } else { + size_t size = list->size(); + putUInt32(size); + for (size_t i = 0; i < size; i++) + putUInt32((*list)[i]); + } +} + +void MtpDataPacket::putAInt64(const int64_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putInt64(*values++); +} + +void MtpDataPacket::putAUInt64(const uint64_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putUInt64(*values++); +} + +void MtpDataPacket::putString(const MtpStringBuffer& string) { + string.writeToPacket(this); +} + +void MtpDataPacket::putString(const char* s) { + MtpStringBuffer string(s); + string.writeToPacket(this); +} + +void MtpDataPacket::putString(const uint16_t* string) { + int count = 0; + for (int i = 0; i < 256; i++) { + if (string[i]) + count++; + else + break; + } + putUInt8(count > 0 ? count + 1 : 0); + for (int i = 0; i < count; i++) + putUInt16(string[i]); + // only terminate with zero if string is not empty + if (count > 0) + putUInt16(0); +} + +#ifdef MTP_DEVICE +int MtpDataPacket::read(int fd) { + // first read the header + int ret = ::read(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE); + if (ret != MTP_CONTAINER_HEADER_SIZE) + return -1; + // then the following data + int total = MtpPacket::getUInt32(MTP_CONTAINER_LENGTH_OFFSET); + int remaining = total - MTP_CONTAINER_HEADER_SIZE; + ret = ::read(fd, &mBuffer[0] + MTP_CONTAINER_HEADER_SIZE, remaining); + if (ret != remaining) + return -1; + + mPacketSize = total; + mOffset = MTP_CONTAINER_HEADER_SIZE; + return total; +} + +int MtpDataPacket::readDataHeader(int fd) { + int ret = ::read(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE); + if (ret > 0) + mPacketSize = ret; + else + mPacketSize = 0; + return ret; +} + +int MtpDataPacket::write(int fd) { + MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize); + MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA); + + // send header separately from data + int ret = ::write(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE); + if (ret == MTP_CONTAINER_HEADER_SIZE) + ret = ::write(fd, mBuffer + MTP_CONTAINER_HEADER_SIZE, + mPacketSize - MTP_CONTAINER_HEADER_SIZE); + return (ret < 0 ? ret : 0); +} + +int MtpDataPacket::writeDataHeader(int fd, uint32_t length) { + MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, length); + MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA); + int ret = ::write(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE); + return (ret < 0 ? ret : 0); +} +#endif // MTP_DEVICE + +#ifdef MTP_HOST +int MtpDataPacket::read(struct usb_endpoint *ep) { + // first read the header + int length = transfer(ep, mBuffer, mBufferSize); + if (length >= MTP_CONTAINER_HEADER_SIZE) { + // look at the length field to see if the data spans multiple packets + uint32_t totalLength = MtpPacket::getUInt32(MTP_CONTAINER_LENGTH_OFFSET); + while (totalLength > length) { + allocate(length + mAllocationIncrement); + int ret = transfer(ep, mBuffer + length, mAllocationIncrement); + if (ret >= 0) + length += ret; + else { + length = ret; + break; + } + } + } + if (length >= 0) + mPacketSize = length; + return length; +} + +int MtpDataPacket::readData(struct usb_endpoint *ep, void* buffer, int length) { + int packetSize = usb_endpoint_max_packet(ep); + int read = 0; + while (read < length) { + int ret = transfer(ep, (char *)buffer + read, packetSize); + if (ret < 0) { +printf("MtpDataPacket::readData returning %d\n", ret); + return ret; + } + read += ret; + } +printf("MtpDataPacket::readData returning %d\n", read); + return read; +} + +int MtpDataPacket::readDataHeader(struct usb_endpoint *ep) { + int length = transfer(ep, mBuffer, usb_endpoint_max_packet(ep)); + if (length >= 0) + mPacketSize = length; + return length; +} + +int MtpDataPacket::writeDataHeader(struct usb_endpoint *ep, uint32_t length) { + MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, length); + MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA); + int ret = transfer(ep, mBuffer, MTP_CONTAINER_HEADER_SIZE); + return (ret < 0 ? ret : 0); +} + +int MtpDataPacket::write(struct usb_endpoint *ep) { + MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize); + MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA); + + // send header separately from data + int ret = transfer(ep, mBuffer, MTP_CONTAINER_HEADER_SIZE); + if (ret == MTP_CONTAINER_HEADER_SIZE) + ret = transfer(ep, mBuffer + MTP_CONTAINER_HEADER_SIZE, + mPacketSize - MTP_CONTAINER_HEADER_SIZE); + return (ret < 0 ? ret : 0); +} + +int MtpDataPacket::write(struct usb_endpoint *ep, void* buffer, uint32_t length) { + int ret = 0; + int packetSize = usb_endpoint_max_packet(ep); + while (length > 0) { + int write = (length > packetSize ? packetSize : length); + int ret = transfer(ep, buffer, write); + if (ret < 0) + break; + length -= ret; + } + return (ret < 0 ? ret : 0); +} + +#endif // MTP_HOST + +void* MtpDataPacket::getData(int& outLength) const { + int length = mPacketSize - MTP_CONTAINER_HEADER_SIZE; + if (length > 0) { + void* result = malloc(length); + if (result) { + memcpy(result, mBuffer + MTP_CONTAINER_HEADER_SIZE, length); + outLength = length; + return result; + } + } + outLength = 0; + return NULL; +} + +} // namespace android diff --git a/media/mtp/MtpDataPacket.h b/media/mtp/MtpDataPacket.h new file mode 100644 index 0000000..1467aab --- /dev/null +++ b/media/mtp/MtpDataPacket.h @@ -0,0 +1,116 @@ +/* + * 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_DATA_PACKET_H +#define _MTP_DATA_PACKET_H + +#include "MtpPacket.h" +#include "mtp.h" + +namespace android { + +class MtpStringBuffer; + +class MtpDataPacket : public MtpPacket { +private: + // current offset for get/put methods + int mOffset; + +public: + MtpDataPacket(); + virtual ~MtpDataPacket(); + + virtual void reset(); + + void setOperationCode(MtpOperationCode code); + void setTransactionID(MtpTransactionID id); + + inline uint8_t getUInt8() { return (uint8_t)mBuffer[mOffset++]; } + inline int8_t getInt8() { return (int8_t)mBuffer[mOffset++]; } + uint16_t getUInt16(); + inline int16_t getInt16() { return (int16_t)getUInt16(); } + uint32_t getUInt32(); + inline int32_t getInt32() { return (int32_t)getUInt32(); } + uint64_t getUInt64(); + inline int64_t getInt64() { return (int64_t)getUInt64(); } + void getUInt128(uint128_t& value); + inline void getInt128(int128_t& value) { getUInt128((uint128_t&)value); } + void getString(MtpStringBuffer& string); + + Int8List* getAInt8(); + UInt8List* getAUInt8(); + Int16List* getAInt16(); + UInt16List* getAUInt16(); + Int32List* getAInt32(); + UInt32List* getAUInt32(); + Int64List* getAInt64(); + UInt64List* getAUInt64(); + + void putInt8(int8_t value); + void putUInt8(uint8_t value); + void putInt16(int16_t value); + void putUInt16(uint16_t value); + void putInt32(int32_t value); + void putUInt32(uint32_t value); + void putInt64(int64_t value); + void putUInt64(uint64_t value); + void putInt128(const int128_t& value); + void putUInt128(const uint128_t& value); + + void putAInt8(const int8_t* values, int count); + void putAUInt8(const uint8_t* values, int count); + void putAInt16(const int16_t* values, int count); + void putAUInt16(const uint16_t* values, int count); + void putAUInt16(const UInt16List* values); + void putAInt32(const int32_t* values, int count); + void putAUInt32(const uint32_t* values, int count); + void putAUInt32(const UInt32List* list); + void putAInt64(const int64_t* values, int count); + void putAUInt64(const uint64_t* values, int count); + void putString(const MtpStringBuffer& string); + void putString(const char* string); + void putString(const uint16_t* string); + inline void putEmptyString() { putUInt8(0); } + inline void putEmptyArray() { putUInt32(0); } + + +#ifdef MTP_DEVICE + // fill our buffer with data from the given file descriptor + int read(int fd); + int readDataHeader(int fd); + + // write our data to the given file descriptor + int write(int fd); + int writeDataHeader(int fd, uint32_t length); +#endif + +#ifdef MTP_HOST + int read(struct usb_endpoint *ep); + int readData(struct usb_endpoint *ep, void* buffer, int length); + int readDataHeader(struct usb_endpoint *ep); + + int writeDataHeader(struct usb_endpoint *ep, uint32_t length); + int write(struct usb_endpoint *ep); + int write(struct usb_endpoint *ep, void* buffer, uint32_t length); +#endif + + inline bool hasData() const { return mPacketSize > MTP_CONTAINER_HEADER_SIZE; } + void* getData(int& outLength) const; +}; + +}; // namespace android + +#endif // _MTP_DATA_PACKET_H diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h new file mode 100644 index 0000000..17823df --- /dev/null +++ b/media/mtp/MtpDatabase.h @@ -0,0 +1,83 @@ +/* + * 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_DATABASE_H +#define _MTP_DATABASE_H + +#include "MtpTypes.h" + +namespace android { + +class MtpDataPacket; + +class MtpDatabase { +public: + virtual ~MtpDatabase() {} + + // called from SendObjectInfo to reserve a database entry for the incoming file + virtual MtpObjectHandle beginSendObject(const char* path, + MtpObjectFormat format, + MtpObjectHandle parent, + MtpStorageID storage, + uint64_t size, + time_t modified) = 0; + + // called to report success or failure of the SendObject file transfer + // success should signal a notification of the new object's creation, + // failure should remove the database entry created in beginSendObject + virtual void endSendObject(const char* path, + MtpObjectHandle handle, + MtpObjectFormat format, + bool succeeded) = 0; + + virtual MtpObjectHandleList* getObjectList(MtpStorageID storageID, + MtpObjectFormat format, + MtpObjectHandle parent) = 0; + + virtual int getNumObjects(MtpStorageID storageID, + MtpObjectFormat format, + MtpObjectHandle parent) = 0; + + // callee should delete[] the results from these + // results can be NULL + virtual MtpObjectFormatList* getSupportedPlaybackFormats() = 0; + virtual MtpObjectFormatList* getSupportedCaptureFormats() = 0; + virtual MtpObjectPropertyList* getSupportedObjectProperties(MtpObjectFormat format) = 0; + virtual MtpDevicePropertyList* getSupportedDeviceProperties() = 0; + + virtual MtpResponseCode getObjectProperty(MtpObjectHandle handle, + MtpObjectProperty property, + MtpDataPacket& packet) = 0; + + virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle, + MtpDataPacket& packet) = 0; + + virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle, + MtpString& filePath, + int64_t& fileLength) = 0; + + virtual MtpResponseCode deleteFile(MtpObjectHandle handle) = 0; + + virtual MtpObjectHandleList* getObjectReferences(MtpObjectHandle handle) = 0; + + virtual MtpResponseCode setObjectReferences(MtpObjectHandle handle, + MtpObjectHandleList* references) = 0; + +}; + +}; // namespace android + +#endif // _MTP_DATABASE_H diff --git a/media/mtp/MtpDebug.cpp b/media/mtp/MtpDebug.cpp new file mode 100644 index 0000000..d6b107d --- /dev/null +++ b/media/mtp/MtpDebug.cpp @@ -0,0 +1,387 @@ +/* + * 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. + */ + +#include "MtpDebug.h" + +namespace android { + +struct CodeEntry { + const char* name; + uint16_t code; +}; + +static const CodeEntry sOperationCodes[] = { + { "MTP_OPERATION_GET_DEVICE_INFO", 0x1001 }, + { "MTP_OPERATION_OPEN_SESSION", 0x1002 }, + { "MTP_OPERATION_CLOSE_SESSION", 0x1003 }, + { "MTP_OPERATION_GET_STORAGE_IDS", 0x1004 }, + { "MTP_OPERATION_GET_STORAGE_INFO", 0x1005 }, + { "MTP_OPERATION_GET_NUM_OBJECTS", 0x1006 }, + { "MTP_OPERATION_GET_OBJECT_HANDLES", 0x1007 }, + { "MTP_OPERATION_GET_OBJECT_INFO", 0x1008 }, + { "MTP_OPERATION_GET_OBJECT", 0x1009 }, + { "MTP_OPERATION_GET_THUMB", 0x100A }, + { "MTP_OPERATION_DELETE_OBJECT", 0x100B }, + { "MTP_OPERATION_SEND_OBJECT_INFO", 0x100C }, + { "MTP_OPERATION_SEND_OBJECT", 0x100D }, + { "MTP_OPERATION_INITIATE_CAPTURE", 0x100E }, + { "MTP_OPERATION_FORMAT_STORE", 0x100F }, + { "MTP_OPERATION_RESET_DEVICE", 0x1010 }, + { "MTP_OPERATION_SELF_TEST", 0x1011 }, + { "MTP_OPERATION_SET_OBJECT_PROTECTION", 0x1012 }, + { "MTP_OPERATION_POWER_DOWN", 0x1013 }, + { "MTP_OPERATION_GET_DEVICE_PROP_DESC", 0x1014 }, + { "MTP_OPERATION_GET_DEVICE_PROP_VALUE", 0x1015 }, + { "MTP_OPERATION_SET_DEVICE_PROP_VALUE", 0x1016 }, + { "MTP_OPERATION_RESET_DEVICE_PROP_VALUE", 0x1017 }, + { "MTP_OPERATION_TERMINATE_OPEN_CAPTURE", 0x1018 }, + { "MTP_OPERATION_MOVE_OBJECT", 0x1019 }, + { "MTP_OPERATION_COPY_OBJECT", 0x101A }, + { "MTP_OPERATION_GET_PARTIAL_OBJECT", 0x101B }, + { "MTP_OPERATION_INITIATE_OPEN_CAPTURE", 0x101C }, + { "MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED", 0x9801 }, + { "MTP_OPERATION_GET_OBJECT_PROP_DESC", 0x9802 }, + { "MTP_OPERATION_GET_OBJECT_PROP_VALUE", 0x9803 }, + { "MTP_OPERATION_SET_OBJECT_PROP_VALUE", 0x9804 }, + { "MTP_OPERATION_GET_OBJECT_REFERENCES", 0x9810 }, + { "MTP_OPERATION_SET_OBJECT_REFERENCES", 0x9811 }, + { "MTP_OPERATION_SKIP", 0x9820 }, + { 0, 0 }, +}; + +static const CodeEntry sFormatCodes[] = { + { "MTP_OPERATION_GET_DEVICE_INFO", 0x1001 }, + { "MTP_FORMAT_UNDEFINED", 0x3000 }, + { "MTP_FORMAT_ASSOCIATION", 0x3001 }, + { "MTP_FORMAT_SCRIPT", 0x3002 }, + { "MTP_FORMAT_EXECUTABLE", 0x3003 }, + { "MTP_FORMAT_TEXT", 0x3004 }, + { "MTP_FORMAT_HTML", 0x3005 }, + { "MTP_FORMAT_DPOF", 0x3006 }, + { "MTP_FORMAT_AIFF", 0x3007 }, + { "MTP_FORMAT_WAV", 0x3008 }, + { "MTP_FORMAT_MP3", 0x3009 }, + { "MTP_FORMAT_AVI", 0x300A }, + { "MTP_FORMAT_MPEG", 0x300B }, + { "MTP_FORMAT_ASF", 0x300C }, + { "MTP_FORMAT_DEFINED", 0x3800 }, + { "MTP_FORMAT_EXIF_JPEG", 0x3801 }, + { "MTP_FORMAT_TIFF_EP", 0x3802 }, + { "MTP_FORMAT_FLASHPIX", 0x3803 }, + { "MTP_FORMAT_BMP", 0x3804 }, + { "MTP_FORMAT_CIFF", 0x3805 }, + { "MTP_FORMAT_GIF", 0x3807 }, + { "MTP_FORMAT_JFIF", 0x3808 }, + { "MTP_FORMAT_CD", 0x3809 }, + { "MTP_FORMAT_PICT", 0x380A }, + { "MTP_FORMAT_PNG", 0x380B }, + { "MTP_FORMAT_TIFF", 0x380D }, + { "MTP_FORMAT_TIFF_IT", 0x380E }, + { "MTP_FORMAT_JP2", 0x380F }, + { "MTP_FORMAT_JPX", 0x3810 }, + { "MTP_FORMAT_UNDEFINED_FIRMWARE", 0xB802 }, + { "MTP_FORMAT_WINDOWS_IMAGE_FORMAT", 0xB881 }, + { "MTP_FORMAT_UNDEFINED_AUDIO", 0xB900 }, + { "MTP_FORMAT_WMA", 0xB901 }, + { "MTP_FORMAT_OGG", 0xB902 }, + { "MTP_FORMAT_AAC", 0xB903 }, + { "MTP_FORMAT_AUDIBLE", 0xB904 }, + { "MTP_FORMAT_FLAC", 0xB906 }, + { "MTP_FORMAT_UNDEFINED_VIDEO", 0xB980 }, + { "MTP_FORMAT_WMV", 0xB981 }, + { "MTP_FORMAT_MP4_CONTAINER", 0xB982 }, + { "MTP_FORMAT_MP2", 0xB983 }, + { "MTP_FORMAT_3GP_CONTAINER", 0xB984 }, + { "MTP_FORMAT_UNDEFINED_COLLECTION", 0xBA00 }, + { "MTP_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM", 0xBA01 }, + { "MTP_FORMAT_ABSTRACT_IMAGE_ALBUM", 0xBA02 }, + { "MTP_FORMAT_ABSTRACT_AUDIO_ALBUM", 0xBA03 }, + { "MTP_FORMAT_ABSTRACT_VIDEO_ALBUM", 0xBA04 }, + { "MTP_FORMAT_ABSTRACT_AV_PLAYLIST", 0xBA05 }, + { "MTP_FORMAT_ABSTRACT_CONTACT_GROUP", 0xBA06 }, + { "MTP_FORMAT_ABSTRACT_MESSAGE_FOLDER", 0xBA07 }, + { "MTP_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION", 0xBA08 }, + { "MTP_FORMAT_ABSTRACT_AUDIO_PLAYLIST", 0xBA09 }, + { "MTP_FORMAT_ABSTRACT_VIDEO_PLAYLIST", 0xBA0A }, + { "MTP_FORMAT_ABSTRACT_MEDIACAST", 0xBA0B }, + { "MTP_FORMAT_WPL_PLAYLIST", 0xBA10 }, + { "MTP_FORMAT_M3U_PLAYLIST", 0xBA11 }, + { "MTP_FORMAT_MPL_PLAYLIST", 0xBA12 }, + { "MTP_FORMAT_ASX_PLAYLIST", 0xBA13 }, + { "MTP_FORMAT_PLS_PLAYLIST", 0xBA14 }, + { "MTP_FORMAT_UNDEFINED_DOCUMENT", 0xBA80 }, + { "MTP_FORMAT_ABSTRACT_DOCUMENT", 0xBA81 }, + { "MTP_FORMAT_XML_DOCUMENT", 0xBA82 }, + { "MTP_FORMAT_MS_WORD_DOCUMENT", 0xBA83 }, + { "MTP_FORMAT_MHT_COMPILED_HTML_DOCUMENT", 0xBA84 }, + { "MTP_FORMAT_MS_EXCEL_SPREADSHEET", 0xBA85 }, + { "MTP_FORMAT_MS_POWERPOINT_PRESENTATION", 0xBA86 }, + { "MTP_FORMAT_UNDEFINED_MESSAGE", 0xBB00 }, + { "MTP_FORMAT_ABSTRACT_MESSSAGE", 0xBB01 }, + { "MTP_FORMAT_UNDEFINED_CONTACT", 0xBB80 }, + { "MTP_FORMAT_ABSTRACT_CONTACT", 0xBB81 }, + { "MTP_FORMAT_VCARD_2", 0xBB82 }, + { 0, 0 }, +}; + +static const CodeEntry sObjectPropCodes[] = { + { "MTP_PROPERTY_STORAGE_ID", 0xDC01 }, + { "MTP_PROPERTY_OBJECT_FORMAT", 0xDC02 }, + { "MTP_PROPERTY_PROTECTION_STATUS", 0xDC03 }, + { "MTP_PROPERTY_OBJECT_SIZE", 0xDC04 }, + { "MTP_PROPERTY_ASSOCIATION_TYPE", 0xDC05 }, + { "MTP_PROPERTY_ASSOCIATION_DESC", 0xDC06 }, + { "MTP_PROPERTY_OBJECT_FILE_NAME", 0xDC07 }, + { "MTP_PROPERTY_DATE_CREATED", 0xDC08 }, + { "MTP_PROPERTY_DATE_MODIFIED", 0xDC09 }, + { "MTP_PROPERTY_KEYWORDS", 0xDC0A }, + { "MTP_PROPERTY_PARENT_OBJECT", 0xDC0B }, + { "MTP_PROPERTY_ALLOWED_FOLDER_CONTENTS", 0xDC0C }, + { "MTP_PROPERTY_HIDDEN", 0xDC0D }, + { "MTP_PROPERTY_SYSTEM_OBJECT", 0xDC0E }, + { "MTP_PROPERTY_PERSISTENT_UID", 0xDC41 }, + { "MTP_PROPERTY_SYNC_ID", 0xDC42 }, + { "MTP_PROPERTY_PROPERTY_BAG", 0xDC43 }, + { "MTP_PROPERTY_NAME", 0xDC44 }, + { "MTP_PROPERTY_CREATED_BY", 0xDC45 }, + { "MTP_PROPERTY_ARTIST", 0xDC46 }, + { "MTP_PROPERTY_DATE_AUTHORED", 0xDC47 }, + { "MTP_PROPERTY_DESCRIPTION", 0xDC48 }, + { "MTP_PROPERTY_URL_REFERENCE", 0xDC49 }, + { "MTP_PROPERTY_LANGUAGE_LOCALE", 0xDC4A }, + { "MTP_PROPERTY_COPYRIGHT_INFORMATION", 0xDC4B }, + { "MTP_PROPERTY_SOURCE", 0xDC4C }, + { "MTP_PROPERTY_ORIGIN_LOCATION", 0xDC4D }, + { "MTP_PROPERTY_DATE_ADDED", 0xDC4E }, + { "MTP_PROPERTY_NON_CONSUMABLE", 0xDC4F }, + { "MTP_PROPERTY_CORRUPT_UNPLAYABLE", 0xDC50 }, + { "MTP_PROPERTY_PRODUCER_SERIAL_NUMBER", 0xDC51 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_FORMAT", 0xDC81 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_SIZE", 0xDC82 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_HEIGHT", 0xDC83 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_WIDTH", 0xDC84 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DURATION", 0xDC85 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DATA", 0xDC86 }, + { "MTP_PROPERTY_WIDTH", 0xDC87 }, + { "MTP_PROPERTY_HEIGHT", 0xDC88 }, + { "MTP_PROPERTY_DURATION", 0xDC89 }, + { "MTP_PROPERTY_RATING", 0xDC8A }, + { "MTP_PROPERTY_TRACK", 0xDC8B }, + { "MTP_PROPERTY_GENRE", 0xDC8C }, + { "MTP_PROPERTY_CREDITS", 0xDC8D }, + { "MTP_PROPERTY_LYRICS", 0xDC8E }, + { "MTP_PROPERTY_SUBSCRIPTION_CONTENT_ID", 0xDC8F }, + { "MTP_PROPERTY_PRODUCED_BY", 0xDC90 }, + { "MTP_PROPERTY_USE_COUNT", 0xDC91 }, + { "MTP_PROPERTY_SKIP_COUNT", 0xDC92 }, + { "MTP_PROPERTY_LAST_ACCESSED", 0xDC93 }, + { "MTP_PROPERTY_PARENTAL_RATING", 0xDC94 }, + { "MTP_PROPERTY_META_GENRE", 0xDC95 }, + { "MTP_PROPERTY_COMPOSER", 0xDC96 }, + { "MTP_PROPERTY_EFFECTIVE_RATING", 0xDC97 }, + { "MTP_PROPERTY_SUBTITLE", 0xDC98 }, + { "MTP_PROPERTY_ORIGINAL_RELEASE_DATE", 0xDC99 }, + { "MTP_PROPERTY_ALBUM_NAME", 0xDC9A }, + { "MTP_PROPERTY_ALBUM_ARTIST", 0xDC9B }, + { "MTP_PROPERTY_MOOD", 0xDC9C }, + { "MTP_PROPERTY_DRM_STATUS", 0xDC9D }, + { "MTP_PROPERTY_SUB_DESCRIPTION", 0xDC9E }, + { "MTP_PROPERTY_IS_CROPPED", 0xDCD1 }, + { "MTP_PROPERTY_IS_COLOUR_CORRECTED", 0xDCD2 }, + { "MTP_PROPERTY_IMAGE_BIT_DEPTH", 0xDCD3 }, + { "MTP_PROPERTY_F_NUMBER", 0xDCD4 }, + { "MTP_PROPERTY_EXPOSURE_TIME", 0xDCD5 }, + { "MTP_PROPERTY_EXPOSURE_INDEX", 0xDCD6 }, + { "MTP_PROPERTY_TOTAL_BITRATE", 0xDE91 }, + { "MTP_PROPERTY_BITRATE_TYPE", 0xDE92 }, + { "MTP_PROPERTY_SAMPLE_RATE", 0xDE93 }, + { "MTP_PROPERTY_NUMBER_OF_CHANNELS", 0xDE94 }, + { "MTP_PROPERTY_AUDIO_BIT_DEPTH", 0xDE95 }, + { "MTP_PROPERTY_SCAN_TYPE", 0xDE97 }, + { "MTP_PROPERTY_AUDIO_WAVE_CODEC", 0xDE99 }, + { "MTP_PROPERTY_AUDIO_BITRATE", 0xDE9A }, + { "MTP_PROPERTY_VIDEO_FOURCC_CODEC", 0xDE9B }, + { "MTP_PROPERTY_VIDEO_BITRATE", 0xDE9C }, + { "MTP_PROPERTY_FRAMES_PER_THOUSAND_SECONDS", 0xDE9D }, + { "MTP_PROPERTY_KEYFRAME_DISTANCE", 0xDE9E }, + { "MTP_PROPERTY_BUFFER_SIZE", 0xDE9F }, + { "MTP_PROPERTY_ENCODING_QUALITY", 0xDEA0 }, + { "MTP_PROPERTY_ENCODING_PROFILE", 0xDEA1 }, + { "MTP_PROPERTY_DISPLAY_NAME", 0xDCE0 }, + { "MTP_PROPERTY_BODY_TEXT", 0xDCE1 }, + { "MTP_PROPERTY_SUBJECT", 0xDCE2 }, + { "MTP_PROPERTY_PRIORITY", 0xDCE3 }, + { "MTP_PROPERTY_GIVEN_NAME", 0xDD00 }, + { "MTP_PROPERTY_MIDDLE_NAMES", 0xDD01 }, + { "MTP_PROPERTY_FAMILY_NAME", 0xDD02 }, + { "MTP_PROPERTY_PREFIX", 0xDD03 }, + { "MTP_PROPERTY_SUFFIX", 0xDD04 }, + { "MTP_PROPERTY_PHONETIC_GIVEN_NAME", 0xDD05 }, + { "MTP_PROPERTY_PHONETIC_FAMILY_NAME", 0xDD06 }, + { "MTP_PROPERTY_EMAIL_PRIMARY", 0xDD07 }, + { "MTP_PROPERTY_EMAIL_PERSONAL_1", 0xDD08 }, + { "MTP_PROPERTY_EMAIL_PERSONAL_2", 0xDD09 }, + { "MTP_PROPERTY_EMAIL_BUSINESS_1", 0xDD0A }, + { "MTP_PROPERTY_EMAIL_BUSINESS_2", 0xDD0B }, + { "MTP_PROPERTY_EMAIL_OTHERS", 0xDD0C }, + { "MTP_PROPERTY_PHONE_NUMBER_PRIMARY", 0xDD0D }, + { "MTP_PROPERTY_PHONE_NUMBER_PERSONAL", 0xDD0E }, + { "MTP_PROPERTY_PHONE_NUMBER_PERSONAL_2", 0xDD0F }, + { "MTP_PROPERTY_PHONE_NUMBER_BUSINESS", 0xDD10 }, + { "MTP_PROPERTY_PHONE_NUMBER_BUSINESS_2", 0xDD11 }, + { "MTP_PROPERTY_PHONE_NUMBER_MOBILE", 0xDD12 }, + { "MTP_PROPERTY_PHONE_NUMBER_MOBILE_2", 0xDD13 }, + { "MTP_PROPERTY_FAX_NUMBER_PRIMARY", 0xDD14 }, + { "MTP_PROPERTY_FAX_NUMBER_PERSONAL", 0xDD15 }, + { "MTP_PROPERTY_FAX_NUMBER_BUSINESS", 0xDD16 }, + { "MTP_PROPERTY_PAGER_NUMBER", 0xDD17 }, + { "MTP_PROPERTY_PHONE_NUMBER_OTHERS", 0xDD18 }, + { "MTP_PROPERTY_PRIMARY_WEB_ADDRESS", 0xDD19 }, + { "MTP_PROPERTY_PERSONAL_WEB_ADDRESS", 0xDD1A }, + { "MTP_PROPERTY_BUSINESS_WEB_ADDRESS", 0xDD1B }, + { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS", 0xDD1C }, + { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_2", 0xDD1D }, + { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_3", 0xDD1E }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_FULL", 0xDD1F }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_1", 0xDD20 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_2", 0xDD21 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_CITY", 0xDD22 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_REGION", 0xDD23 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_POSTAL_CODE", 0xDD24 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_COUNTRY", 0xDD25 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_FULL", 0xDD26 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_1", 0xDD27 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_2", 0xDD28 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_CITY", 0xDD29 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_REGION", 0xDD2A }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_POSTAL_CODE", 0xDD2B }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_COUNTRY", 0xDD2C }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_FULL", 0xDD2D }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_1", 0xDD2E }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_2", 0xDD2F }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_CITY", 0xDD30 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_REGION", 0xDD31 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_POSTAL_CODE", 0xDD32 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_COUNTRY", 0xDD33 }, + { "MTP_PROPERTY_ORGANIZATION_NAME", 0xDD34 }, + { "MTP_PROPERTY_PHONETIC_ORGANIZATION_NAME", 0xDD35 }, + { "MTP_PROPERTY_ROLE", 0xDD36 }, + { "MTP_PROPERTY_BIRTHDATE", 0xDD37 }, + { "MTP_PROPERTY_MESSAGE_TO", 0xDD40 }, + { "MTP_PROPERTY_MESSAGE_CC", 0xDD41 }, + { "MTP_PROPERTY_MESSAGE_BCC", 0xDD42 }, + { "MTP_PROPERTY_MESSAGE_READ", 0xDD43 }, + { "MTP_PROPERTY_MESSAGE_RECEIVED_TIME", 0xDD44 }, + { "MTP_PROPERTY_MESSAGE_SENDER", 0xDD45 }, + { "MTP_PROPERTY_ACTIVITY_BEGIN_TIME", 0xDD50 }, + { "MTP_PROPERTY_ACTIVITY_END_TIME", 0xDD51 }, + { "MTP_PROPERTY_ACTIVITY_LOCATION", 0xDD52 }, + { "MTP_PROPERTY_ACTIVITY_REQUIRED_ATTENDEES", 0xDD54 }, + { "MTP_PROPERTY_ACTIVITY_OPTIONAL_ATTENDEES", 0xDD55 }, + { "MTP_PROPERTY_ACTIVITY_RESOURCES", 0xDD56 }, + { "MTP_PROPERTY_ACTIVITY_ACCEPTED", 0xDD57 }, + { "MTP_PROPERTY_ACTIVITY_TENTATIVE", 0xDD58 }, + { "MTP_PROPERTY_ACTIVITY_DECLINED", 0xDD59 }, + { "MTP_PROPERTY_ACTIVITY_REMAINDER_TIME", 0xDD5A }, + { "MTP_PROPERTY_ACTIVITY_OWNER", 0xDD5B }, + { "MTP_PROPERTY_ACTIVITY_STATUS", 0xDD5C }, + { "MTP_PROPERTY_OWNER", 0xDD5D }, + { "MTP_PROPERTY_EDITOR", 0xDD5E }, + { "MTP_PROPERTY_WEBMASTER", 0xDD5F }, + { "MTP_PROPERTY_URL_SOURCE", 0xDD60 }, + { "MTP_PROPERTY_URL_DESTINATION", 0xDD61 }, + { "MTP_PROPERTY_TIME_BOOKMARK", 0xDD62 }, + { "MTP_PROPERTY_OBJECT_BOOKMARK", 0xDD63 }, + { "MTP_PROPERTY_BYTE_BOOKMARK", 0xDD64 }, + { "MTP_PROPERTY_LAST_BUILD_DATE", 0xDD70 }, + { "MTP_PROPERTY_TIME_TO_LIVE", 0xDD71 }, + { "MTP_PROPERTY_MEDIA_GUID", 0xDD72 }, + { 0, 0 }, +}; + +static const CodeEntry sDevicePropCodes[] = { + { "MTP_DEVICE_PROPERTY_UNDEFINED", 0x5000 }, + { "MTP_DEVICE_PROPERTY_BATTERY_LEVEL", 0x5001 }, + { "MTP_DEVICE_PROPERTY_FUNCTIONAL_MODE", 0x5002 }, + { "MTP_DEVICE_PROPERTY_IMAGE_SIZE", 0x5003 }, + { "MTP_DEVICE_PROPERTY_COMPRESSION_SETTING", 0x5004 }, + { "MTP_DEVICE_PROPERTY_WHITE_BALANCE", 0x5005 }, + { "MTP_DEVICE_PROPERTY_RGB_GAIN", 0x5006 }, + { "MTP_DEVICE_PROPERTY_F_NUMBER", 0x5007 }, + { "MTP_DEVICE_PROPERTY_FOCAL_LENGTH", 0x5008 }, + { "MTP_DEVICE_PROPERTY_FOCUS_DISTANCE", 0x5009 }, + { "MTP_DEVICE_PROPERTY_FOCUS_MODE", 0x500A }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_METERING_MODE", 0x500B }, + { "MTP_DEVICE_PROPERTY_FLASH_MODE", 0x500C }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_TIME", 0x500D }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_PROGRAM_MODE", 0x500E }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_INDEX", 0x500F }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_BIAS_COMPENSATION", 0x5010 }, + { "MTP_DEVICE_PROPERTY_DATETIME", 0x5011 }, + { "MTP_DEVICE_PROPERTY_CAPTURE_DELAY", 0x5012 }, + { "MTP_DEVICE_PROPERTY_STILL_CAPTURE_MODE", 0x5013 }, + { "MTP_DEVICE_PROPERTY_CONTRAST", 0x5014 }, + { "MTP_DEVICE_PROPERTY_SHARPNESS", 0x5015 }, + { "MTP_DEVICE_PROPERTY_DIGITAL_ZOOM", 0x5016 }, + { "MTP_DEVICE_PROPERTY_EFFECT_MODE", 0x5017 }, + { "MTP_DEVICE_PROPERTY_BURST_NUMBER", 0x5018 }, + { "MTP_DEVICE_PROPERTY_BURST_INTERVAL", 0x5019 }, + { "MTP_DEVICE_PROPERTY_TIMELAPSE_NUMBER", 0x501A }, + { "MTP_DEVICE_PROPERTY_TIMELAPSE_INTERVAL", 0x501B }, + { "MTP_DEVICE_PROPERTY_FOCUS_METERING_MODE", 0x501C }, + { "MTP_DEVICE_PROPERTY_UPLOAD_URL", 0x501D }, + { "MTP_DEVICE_PROPERTY_ARTIST", 0x501E }, + { "MTP_DEVICE_PROPERTY_COPYRIGHT_INFO", 0x501F }, + { "MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER", 0xD401 }, + { "MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME", 0xD402 }, + { "MTP_DEVICE_PROPERTY_VOLUME", 0xD403 }, + { "MTP_DEVICE_PROPERTY_SUPPORTED_FORMATS_ORDERED", 0xD404 }, + { "MTP_DEVICE_PROPERTY_DEVICE_ICON", 0xD405 }, + { "MTP_DEVICE_PROPERTY_PLAYBACK_RATE", 0xD410 }, + { "MTP_DEVICE_PROPERTY_PLAYBACK_OBJECT", 0xD411 }, + { "MTP_DEVICE_PROPERTY_PLAYBACK_CONTAINER_INDEX", 0xD412 }, + { "MTP_DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO", 0xD406 }, + { "MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE", 0xD407 }, + { 0, 0 }, +}; + +static const char* getCodeName(uint16_t code, const CodeEntry* table) { + const CodeEntry* entry = table; + while (entry->name) { + if (entry->code == code) + return entry->name; + entry++; + } + return "UNKNOWN"; +} + +const char* MtpDebug::getOperationCodeName(MtpOperationCode code) { + return getCodeName(code, sOperationCodes); +} + +const char* MtpDebug::getFormatCodeName(MtpOperationCode code) { + return getCodeName(code, sFormatCodes); +} + +const char* MtpDebug::getObjectPropCodeName(MtpPropertyCode code) { + return getCodeName(code, sObjectPropCodes); +} + +const char* MtpDebug::getDevicePropCodeName(MtpPropertyCode code) { + return getCodeName(code, sDevicePropCodes); +} + +} // namespace android diff --git a/media/mtp/MtpDebug.h b/media/mtp/MtpDebug.h new file mode 100644 index 0000000..5b53e31 --- /dev/null +++ b/media/mtp/MtpDebug.h @@ -0,0 +1,37 @@ +/* + * 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_DEBUG_H +#define _MTP_DEBUG_H + +// #define LOG_NDEBUG 0 +#include <utils/Log.h> + +#include "MtpTypes.h" + +namespace android { + +class MtpDebug { +public: + static const char* getOperationCodeName(MtpOperationCode code); + static const char* getFormatCodeName(MtpObjectFormat code); + static const char* getObjectPropCodeName(MtpPropertyCode code); + static const char* getDevicePropCodeName(MtpPropertyCode code); +}; + +}; // namespace android + +#endif // _MTP_DEBUG_H diff --git a/media/mtp/MtpDevice.cpp b/media/mtp/MtpDevice.cpp new file mode 100644 index 0000000..bd68f75 --- /dev/null +++ b/media/mtp/MtpDevice.cpp @@ -0,0 +1,496 @@ +/* + * 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 "MtpDevice" + +#include "MtpDebug.h" +#include "MtpDevice.h" +#include "MtpDeviceInfo.h" +#include "MtpObjectInfo.h" +#include "MtpProperty.h" +#include "MtpStorageInfo.h" +#include "MtpStringBuffer.h" +#include "MtpUtils.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 <endian.h> + +#include <usbhost/usbhost.h> + +namespace android { + +MtpDevice::MtpDevice(struct usb_device* device, int interface, + struct usb_endpoint *ep_in, struct usb_endpoint *ep_out, + struct usb_endpoint *ep_intr) + : mDevice(device), + mInterface(interface), + mEndpointIn(ep_in), + mEndpointOut(ep_out), + mEndpointIntr(ep_intr), + mDeviceInfo(NULL), + mID(usb_device_get_unique_id(device)), + mSessionID(0), + mTransactionID(0) +{ +} + +MtpDevice::~MtpDevice() { + close(); + for (int i = 0; i < mDeviceProperties.size(); i++) + delete mDeviceProperties[i]; +} + +void MtpDevice::initialize() { + openSession(); + mDeviceInfo = getDeviceInfo(); + if (mDeviceInfo) { + mDeviceInfo->print(); + + if (mDeviceInfo->mDeviceProperties) { + int count = mDeviceInfo->mDeviceProperties->size(); + for (int i = 0; i < count; i++) { + MtpDeviceProperty propCode = (*mDeviceInfo->mDeviceProperties)[i]; + MtpProperty* property = getDevicePropDesc(propCode); + if (property) { + property->print(); + mDeviceProperties.push(property); + } + } + } + } +} + +void MtpDevice::close() { + if (mDevice) { + usb_device_release_interface(mDevice, mInterface); + usb_device_close(mDevice); + mDevice = NULL; + } +} + +const char* MtpDevice::getDeviceName() { + if (mDevice) + return usb_device_get_name(mDevice); + else + return "???"; +} + +bool MtpDevice::openSession() { + Mutex::Autolock autoLock(mMutex); + + mSessionID = 0; + mTransactionID = 0; + MtpSessionID newSession = 1; + mRequest.reset(); + mRequest.setParameter(1, newSession); + if (!sendRequest(MTP_OPERATION_OPEN_SESSION)) + return false; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_SESSION_ALREADY_OPEN) + newSession = mResponse.getParameter(1); + else if (ret != MTP_RESPONSE_OK) + return false; + + mSessionID = newSession; + mTransactionID = 1; + return true; +} + +bool MtpDevice::closeSession() { + // FIXME + return true; +} + +MtpDeviceInfo* MtpDevice::getDeviceInfo() { + Mutex::Autolock autoLock(mMutex); + + mRequest.reset(); + if (!sendRequest(MTP_OPERATION_GET_DEVICE_INFO)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + MtpDeviceInfo* info = new MtpDeviceInfo; + info->read(mData); + return info; + } + return NULL; +} + +MtpStorageIDList* MtpDevice::getStorageIDs() { + Mutex::Autolock autoLock(mMutex); + + mRequest.reset(); + if (!sendRequest(MTP_OPERATION_GET_STORAGE_IDS)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + return mData.getAUInt32(); + } + return NULL; +} + +MtpStorageInfo* MtpDevice::getStorageInfo(MtpStorageID storageID) { + Mutex::Autolock autoLock(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, storageID); + if (!sendRequest(MTP_OPERATION_GET_STORAGE_INFO)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + MtpStorageInfo* info = new MtpStorageInfo(storageID); + info->read(mData); + return info; + } + return NULL; +} + +MtpObjectHandleList* MtpDevice::getObjectHandles(MtpStorageID storageID, + MtpObjectFormat format, MtpObjectHandle parent) { + Mutex::Autolock autoLock(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, storageID); + mRequest.setParameter(2, format); + mRequest.setParameter(3, parent); + if (!sendRequest(MTP_OPERATION_GET_OBJECT_HANDLES)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + return mData.getAUInt32(); + } + return NULL; +} + +MtpObjectInfo* MtpDevice::getObjectInfo(MtpObjectHandle handle) { + Mutex::Autolock autoLock(mMutex); + + // FIXME - we might want to add some caching here + + mRequest.reset(); + mRequest.setParameter(1, handle); + if (!sendRequest(MTP_OPERATION_GET_OBJECT_INFO)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + MtpObjectInfo* info = new MtpObjectInfo(handle); + info->read(mData); + return info; + } + return NULL; +} + +void* MtpDevice::getThumbnail(MtpObjectHandle handle, int& outLength) { + Mutex::Autolock autoLock(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, handle); + if (sendRequest(MTP_OPERATION_GET_THUMB) && readData()) { + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + return mData.getData(outLength); + } + } + outLength = 0; + return NULL; +} + +MtpObjectHandle MtpDevice::sendObjectInfo(MtpObjectInfo* info) { + Mutex::Autolock autoLock(mMutex); + + mRequest.reset(); + MtpObjectHandle parent = info->mParent; + if (parent == 0) + parent = MTP_PARENT_ROOT; + + mRequest.setParameter(1, info->mStorageID); + mRequest.setParameter(2, info->mParent); + + mData.putUInt32(info->mStorageID); + mData.putUInt16(info->mFormat); + mData.putUInt16(info->mProtectionStatus); + mData.putUInt32(info->mCompressedSize); + mData.putUInt16(info->mThumbFormat); + mData.putUInt32(info->mThumbCompressedSize); + mData.putUInt32(info->mThumbPixWidth); + mData.putUInt32(info->mThumbPixHeight); + mData.putUInt32(info->mImagePixWidth); + mData.putUInt32(info->mImagePixHeight); + mData.putUInt32(info->mImagePixDepth); + mData.putUInt32(info->mParent); + mData.putUInt16(info->mAssociationType); + mData.putUInt32(info->mAssociationDesc); + mData.putUInt32(info->mSequenceNumber); + mData.putString(info->mName); + + char created[100], modified[100]; + formatDateTime(info->mDateCreated, created, sizeof(created)); + formatDateTime(info->mDateModified, modified, sizeof(modified)); + + mData.putString(created); + mData.putString(modified); + if (info->mKeywords) + mData.putString(info->mKeywords); + else + mData.putEmptyString(); + + if (sendRequest(MTP_OPERATION_SEND_OBJECT_INFO) && sendData()) { + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + info->mStorageID = mResponse.getParameter(1); + info->mParent = mResponse.getParameter(2); + info->mHandle = mResponse.getParameter(3); + return info->mHandle; + } + } + return (MtpObjectHandle)-1; +} + +bool MtpDevice::sendObject(MtpObjectInfo* info, int srcFD) { + Mutex::Autolock autoLock(mMutex); + + int remaining = info->mCompressedSize; + mRequest.reset(); + mRequest.setParameter(1, info->mHandle); + if (sendRequest(MTP_OPERATION_SEND_OBJECT)) { + // send data header + writeDataHeader(MTP_OPERATION_SEND_OBJECT, remaining); + + char buffer[65536]; + while (remaining > 0) { + int count = read(srcFD, buffer, sizeof(buffer)); + if (count > 0) { + int written = mData.write(mEndpointOut, buffer, count); + // FIXME check error + remaining -= count; + } else { + break; + } + } + } + MtpResponseCode ret = readResponse(); + return (remaining == 0 && ret == MTP_RESPONSE_OK); +} + +bool MtpDevice::deleteObject(MtpObjectHandle handle) { + Mutex::Autolock autoLock(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, handle); + if (sendRequest(MTP_OPERATION_DELETE_OBJECT)) { + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) + return true; + } + return false; +} + +MtpObjectHandle MtpDevice::getParent(MtpObjectHandle handle) { + MtpObjectInfo* info = getObjectInfo(handle); + if (info) + return info->mParent; + else + return -1; +} + +MtpObjectHandle MtpDevice::getStorageID(MtpObjectHandle handle) { + MtpObjectInfo* info = getObjectInfo(handle); + if (info) + return info->mStorageID; + else + return -1; +} + +MtpProperty* MtpDevice::getDevicePropDesc(MtpDeviceProperty code) { + Mutex::Autolock autoLock(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, code); + if (!sendRequest(MTP_OPERATION_GET_DEVICE_PROP_DESC)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + MtpProperty* property = new MtpProperty; + property->read(mData, true); + return property; + } + return NULL; +} + +class ReadObjectThread : public Thread { +private: + MtpDevice* mDevice; + MtpObjectHandle mHandle; + int mObjectSize; + void* mInitialData; + int mInitialDataLength; + int mFD; + +public: + ReadObjectThread(MtpDevice* device, MtpObjectHandle handle, int objectSize) + : mDevice(device), + mHandle(handle), + mObjectSize(objectSize), + mInitialData(NULL), + mInitialDataLength(0) + { + } + + virtual ~ReadObjectThread() { + if (mFD >= 0) + close(mFD); + free(mInitialData); + } + + // returns file descriptor + int init() { + mDevice->mRequest.reset(); + mDevice->mRequest.setParameter(1, mHandle); + if (mDevice->sendRequest(MTP_OPERATION_GET_OBJECT) + && mDevice->mData.readDataHeader(mDevice->mEndpointIn)) { + + // mData will contain header and possibly the beginning of the object data + mInitialData = mDevice->mData.getData(mInitialDataLength); + + // create a pipe for the client to read from + int pipefd[2]; + if (pipe(pipefd) < 0) { + LOGE("pipe failed (%s)", strerror(errno)); + return -1; + } + + mFD = pipefd[1]; + return pipefd[0]; + } else { + return -1; + } + } + + virtual bool threadLoop() { + int remaining = mObjectSize; + if (mInitialData) { + write(mFD, mInitialData, mInitialDataLength); + remaining -= mInitialDataLength; + free(mInitialData); + mInitialData = NULL; + } + + char buffer[16384]; + while (remaining > 0) { + int readSize = (remaining > sizeof(buffer) ? sizeof(buffer) : remaining); + int count = mDevice->mData.readData(mDevice->mEndpointIn, buffer, readSize); + int written; + if (count >= 0) { + int written = write(mFD, buffer, count); + // FIXME check error + remaining -= count; + } else { + break; + } + } + + MtpResponseCode ret = mDevice->readResponse(); + mDevice->mMutex.unlock(); + return false; + } +}; + + // returns the file descriptor for a pipe to read the object's data +int MtpDevice::readObject(MtpObjectHandle handle, int objectSize) { + mMutex.lock(); + + ReadObjectThread* thread = new ReadObjectThread(this, handle, objectSize); + int fd = thread->init(); + if (fd < 0) { + delete thread; + mMutex.unlock(); + } else { + thread->run("ReadObjectThread"); + } + return fd; +} + +bool MtpDevice::sendRequest(MtpOperationCode operation) { + LOGV("sendRequest: %s\n", MtpDebug::getOperationCodeName(operation)); + mRequest.setOperationCode(operation); + if (mTransactionID > 0) + mRequest.setTransactionID(mTransactionID++); + int ret = mRequest.write(mEndpointOut); + mRequest.dump(); + return (ret > 0); +} + +bool MtpDevice::sendData() { + LOGV("sendData\n"); + mData.setOperationCode(mRequest.getOperationCode()); + mData.setTransactionID(mRequest.getTransactionID()); + int ret = mData.write(mEndpointOut); + mData.dump(); + return (ret > 0); +} + +bool MtpDevice::readData() { + mData.reset(); + int ret = mData.read(mEndpointIn); + LOGV("readData returned %d\n", ret); + if (ret >= MTP_CONTAINER_HEADER_SIZE) { + mData.dump(); + return true; + } + else { + LOGV("readResponse failed\n"); + return false; + } +} + +bool MtpDevice::writeDataHeader(MtpOperationCode operation, int dataLength) { + mData.setOperationCode(operation); + mData.setTransactionID(mRequest.getTransactionID()); + return (!mData.writeDataHeader(mEndpointOut, dataLength)); +} + +MtpResponseCode MtpDevice::readResponse() { + LOGV("readResponse\n"); + int ret = mResponse.read(mEndpointIn); + if (ret >= MTP_CONTAINER_HEADER_SIZE) { + mResponse.dump(); + return mResponse.getResponseCode(); + } + else { + LOGD("readResponse failed\n"); + return -1; + } +} + +} // namespace android diff --git a/media/mtp/MtpDevice.h b/media/mtp/MtpDevice.h new file mode 100644 index 0000000..57f492f --- /dev/null +++ b/media/mtp/MtpDevice.h @@ -0,0 +1,105 @@ +/* + * 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_DEVICE_H +#define _MTP_DEVICE_H + +#include "MtpRequestPacket.h" +#include "MtpDataPacket.h" +#include "MtpResponsePacket.h" +#include "MtpTypes.h" + +#include <utils/threads.h> + +struct usb_device; + +namespace android { + +class MtpDeviceInfo; +class MtpObjectInfo; +class MtpStorageInfo; + +class MtpDevice { +private: + struct usb_device* mDevice; + int mInterface; + struct usb_endpoint* mEndpointIn; + struct usb_endpoint* mEndpointOut; + struct usb_endpoint* mEndpointIntr; + MtpDeviceInfo* mDeviceInfo; + MtpPropertyList mDeviceProperties; + + // a unique ID for the device + int mID; + + // current session ID + MtpSessionID mSessionID; + // current transaction ID + MtpTransactionID mTransactionID; + + MtpRequestPacket mRequest; + MtpDataPacket mData; + MtpResponsePacket mResponse; + + // to ensure only one MTP transaction at a time + Mutex mMutex; + +public: + MtpDevice(struct usb_device* device, int interface, + struct usb_endpoint *ep_in, struct usb_endpoint *ep_out, + struct usb_endpoint *ep_intr); + virtual ~MtpDevice(); + + inline int getID() const { return mID; } + + void initialize(); + void close(); + const char* getDeviceName(); + + bool openSession(); + bool closeSession(); + + MtpDeviceInfo* getDeviceInfo(); + MtpStorageIDList* getStorageIDs(); + MtpStorageInfo* getStorageInfo(MtpStorageID storageID); + MtpObjectHandleList* getObjectHandles(MtpStorageID storageID, MtpObjectFormat format, MtpObjectHandle parent); + MtpObjectInfo* getObjectInfo(MtpObjectHandle handle); + void* getThumbnail(MtpObjectHandle handle, int& outLength); + MtpObjectHandle sendObjectInfo(MtpObjectInfo* info); + bool sendObject(MtpObjectInfo* info, int srcFD); + bool deleteObject(MtpObjectHandle handle); + MtpObjectHandle getParent(MtpObjectHandle handle); + MtpObjectHandle getStorageID(MtpObjectHandle handle); + + MtpProperty* getDevicePropDesc(MtpDeviceProperty code); + + // returns the file descriptor for a pipe to read the object's data + int readObject(MtpObjectHandle handle, int objectSize); + +private: + friend class ReadObjectThread; + + bool sendRequest(MtpOperationCode operation); + bool sendData(); + bool readData(); + bool writeDataHeader(MtpOperationCode operation, int dataLength); + MtpResponseCode readResponse(); + +}; + +}; // namespace android + +#endif // _MTP_DEVICE_H diff --git a/media/mtp/MtpDeviceInfo.cpp b/media/mtp/MtpDeviceInfo.cpp new file mode 100644 index 0000000..5a9322e --- /dev/null +++ b/media/mtp/MtpDeviceInfo.cpp @@ -0,0 +1,97 @@ +/* + * 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 "MtpDeviceInfo" + +#include "MtpDebug.h" +#include "MtpDataPacket.h" +#include "MtpDeviceInfo.h" +#include "MtpStringBuffer.h" + +namespace android { + +MtpDeviceInfo::MtpDeviceInfo() + : mStandardVersion(0), + mVendorExtensionID(0), + mVendorExtensionVersion(0), + mVendorExtensionDesc(NULL), + mFunctionalCode(0), + mOperations(NULL), + mEvents(NULL), + mDeviceProperties(NULL), + mCaptureFormats(NULL), + mPlaybackFormats(NULL), + mManufacturer(NULL), + mModel(NULL), + mVersion(NULL), + mSerial(NULL) +{ +} + +MtpDeviceInfo::~MtpDeviceInfo() { + if (mVendorExtensionDesc) + free(mVendorExtensionDesc); + delete mOperations; + delete mEvents; + delete mDeviceProperties; + delete mCaptureFormats; + delete mPlaybackFormats; + if (mManufacturer) + free(mManufacturer); + if (mModel) + free(mModel); + if (mVersion) + free(mVersion); + if (mSerial) + free(mSerial); +} + +void MtpDeviceInfo::read(MtpDataPacket& packet) { + MtpStringBuffer string; + + // read the device info + mStandardVersion = packet.getUInt16(); + mVendorExtensionID = packet.getUInt32(); + mVendorExtensionVersion = packet.getUInt16(); + + packet.getString(string); + mVendorExtensionDesc = strdup((const char *)string); + + mFunctionalCode = packet.getUInt16(); + mOperations = packet.getAUInt16(); + mEvents = packet.getAUInt16(); + mDeviceProperties = packet.getAUInt16(); + mCaptureFormats = packet.getAUInt16(); + mPlaybackFormats = packet.getAUInt16(); + + packet.getString(string); + mManufacturer = strdup((const char *)string); + packet.getString(string); + mModel = strdup((const char *)string); + packet.getString(string); + mVersion = strdup((const char *)string); + packet.getString(string); + mSerial = strdup((const char *)string); +} + +void MtpDeviceInfo::print() { + LOGV("Device Info:\n\tmStandardVersion: %d\n\tmVendorExtensionID: %d\n\tmVendorExtensionVersiony: %d\n", + mStandardVersion, mVendorExtensionID, mVendorExtensionVersion); + LOGV("\tmVendorExtensionDesc: %s\n\tmFunctionalCode: %d\n\tmManufacturer: %s\n\tmModel: %s\n\tmVersion: %s\n\tmSerial: %s\n", + mVendorExtensionDesc, mFunctionalCode, mManufacturer, mModel, mVersion, mSerial); +} + +} // namespace android diff --git a/media/mtp/MtpDeviceInfo.h b/media/mtp/MtpDeviceInfo.h new file mode 100644 index 0000000..2abaa10 --- /dev/null +++ b/media/mtp/MtpDeviceInfo.h @@ -0,0 +1,54 @@ +/* + * 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_DEVICE_INFO_H +#define _MTP_DEVICE_INFO_H + +struct stat; + +namespace android { + +class MtpDataPacket; + +class MtpDeviceInfo { +public: + uint16_t mStandardVersion; + uint32_t mVendorExtensionID; + uint16_t mVendorExtensionVersion; + char* mVendorExtensionDesc; + uint16_t mFunctionalCode; + UInt16List* mOperations; + UInt16List* mEvents; + MtpDevicePropertyList* mDeviceProperties; + MtpObjectFormatList* mCaptureFormats; + MtpObjectFormatList* mPlaybackFormats; + char* mManufacturer; + char* mModel; + char* mVersion; + char* mSerial; + +public: + MtpDeviceInfo(); + virtual ~MtpDeviceInfo(); + + void read(MtpDataPacket& packet); + + void print(); +}; + +}; // namespace android + +#endif // _MTP_DEVICE_INFO_H diff --git a/media/mtp/MtpEventPacket.cpp b/media/mtp/MtpEventPacket.cpp new file mode 100644 index 0000000..fc74542 --- /dev/null +++ b/media/mtp/MtpEventPacket.cpp @@ -0,0 +1,67 @@ +/* + * 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 "MtpEventPacket" + +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#ifdef MTP_DEVICE +#include <linux/usb/f_mtp.h> +#endif + +#include "MtpEventPacket.h" + +namespace android { + +MtpEventPacket::MtpEventPacket() + : MtpPacket(512) +{ +} + +MtpEventPacket::~MtpEventPacket() { +} + +#ifdef MTP_DEVICE +int MtpEventPacket::write(int fd) { + struct mtp_event event; + + putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize); + putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_EVENT); + + event.data = mBuffer; + event.length = mPacketSize; + int ret = ::ioctl(fd, MTP_SEND_EVENT, (unsigned long)&event); + return (ret < 0 ? ret : 0); +} +#endif + +#ifdef MTP_HOST + // read our buffer from the given endpoint +int MtpEventPacket::read(struct usb_endpoint *ep) { + int ret = transfer(ep, mBuffer, mBufferSize); + if (ret >= 0) + mPacketSize = ret; + else + mPacketSize = 0; + return ret; +} +#endif + +} // namespace android + diff --git a/media/mtp/MtpEventPacket.h b/media/mtp/MtpEventPacket.h new file mode 100644 index 0000000..30ae869 --- /dev/null +++ b/media/mtp/MtpEventPacket.h @@ -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. + */ + +#ifndef _MTP_EVENT_PACKET_H +#define _MTP_EVENT_PACKET_H + +#include "MtpPacket.h" +#include "mtp.h" + +namespace android { + +class MtpEventPacket : public MtpPacket { + +public: + MtpEventPacket(); + virtual ~MtpEventPacket(); + +#ifdef MTP_DEVICE + // write our data to the given file descriptor + int write(int fd); +#endif + +#ifdef MTP_HOST + // read our buffer from the given endpoint + int read(struct usb_endpoint *ep); +#endif + + inline MtpEventCode getEventCode() const { return getContainerCode(); } + inline void setEventCode(MtpEventCode code) + { return setContainerCode(code); } +}; + +}; // namespace android + +#endif // _MTP_EVENT_PACKET_H diff --git a/media/mtp/MtpObjectInfo.cpp b/media/mtp/MtpObjectInfo.cpp new file mode 100644 index 0000000..dd4304e --- /dev/null +++ b/media/mtp/MtpObjectInfo.cpp @@ -0,0 +1,108 @@ +/* + * 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 "MtpObjectInfo" + +#include "MtpDebug.h" +#include "MtpDataPacket.h" +#include "MtpObjectInfo.h" +#include "MtpStringBuffer.h" +#include "MtpUtils.h" + +namespace android { + +MtpObjectInfo::MtpObjectInfo(MtpObjectHandle handle) + : mHandle(handle), + mStorageID(0), + mFormat(0), + mProtectionStatus(0), + mCompressedSize(0), + mThumbFormat(0), + mThumbCompressedSize(0), + mThumbPixWidth(0), + mThumbPixHeight(0), + mImagePixWidth(0), + mImagePixHeight(0), + mImagePixDepth(0), + mParent(0), + mAssociationType(0), + mAssociationDesc(0), + mSequenceNumber(0), + mName(NULL), + mDateCreated(0), + mDateModified(0), + mKeywords(NULL) +{ +} + +MtpObjectInfo::~MtpObjectInfo() { + if (mName) + free(mName); + if (mKeywords) + free(mKeywords); +} + +void MtpObjectInfo::read(MtpDataPacket& packet) { + MtpStringBuffer string; + time_t time; + + mStorageID = packet.getUInt32(); + mFormat = packet.getUInt16(); + mProtectionStatus = packet.getUInt16(); + mCompressedSize = packet.getUInt32(); + mThumbFormat = packet.getUInt16(); + mThumbCompressedSize = packet.getUInt32(); + mThumbPixWidth = packet.getUInt32(); + mThumbPixHeight = packet.getUInt32(); + mImagePixWidth = packet.getUInt32(); + mImagePixHeight = packet.getUInt32(); + mImagePixDepth = packet.getUInt32(); + mParent = packet.getUInt32(); + mAssociationType = packet.getUInt16(); + mAssociationDesc = packet.getUInt32(); + mSequenceNumber = packet.getUInt32(); + + packet.getString(string); + mName = strdup((const char *)string); + + packet.getString(string); + if (parseDateTime((const char*)string, time)) + mDateCreated = time; + + packet.getString(string); + if (parseDateTime((const char*)string, time)) + mDateModified = time; + + packet.getString(string); + mKeywords = strdup((const char *)string); +} + +void MtpObjectInfo::print() { + LOGD("MtpObject Info %08X: %s\n", mHandle, mName); + LOGD(" mStorageID: %08X mFormat: %04X mProtectionStatus: %d\n", + mStorageID, mFormat, mProtectionStatus); + LOGD(" mCompressedSize: %d mThumbFormat: %04X mThumbCompressedSize: %d\n", + mCompressedSize, mFormat, mThumbCompressedSize); + LOGD(" mThumbPixWidth: %d mThumbPixHeight: %d\n", mThumbPixWidth, mThumbPixHeight); + LOGD(" mImagePixWidth: %d mImagePixHeight: %d mImagePixDepth: %d\n", + mImagePixWidth, mImagePixHeight, mImagePixDepth); + LOGD(" mParent: %08X mAssociationType: %04X mAssociationDesc: %04X\n", + mParent, mAssociationType, mAssociationDesc); + LOGD(" mSequenceNumber: %d mDateCreated: %d mDateModified: %d mKeywords: %s\n", + mSequenceNumber, mDateCreated, mDateModified, mKeywords); +} + +} // namespace android diff --git a/media/mtp/MtpObjectInfo.h b/media/mtp/MtpObjectInfo.h new file mode 100644 index 0000000..c7a449c --- /dev/null +++ b/media/mtp/MtpObjectInfo.h @@ -0,0 +1,60 @@ +/* + * 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_OBJECT_INFO_H +#define _MTP_OBJECT_INFO_H + +#include "MtpTypes.h" + +namespace android { + +class MtpDataPacket; + +class MtpObjectInfo { +public: + MtpObjectHandle mHandle; + MtpStorageID mStorageID; + MtpObjectFormat mFormat; + uint16_t mProtectionStatus; + uint32_t mCompressedSize; + MtpObjectFormat mThumbFormat; + uint32_t mThumbCompressedSize; + uint32_t mThumbPixWidth; + uint32_t mThumbPixHeight; + uint32_t mImagePixWidth; + uint32_t mImagePixHeight; + uint32_t mImagePixDepth; + MtpObjectHandle mParent; + uint16_t mAssociationType; + uint32_t mAssociationDesc; + uint32_t mSequenceNumber; + char* mName; + time_t mDateCreated; + time_t mDateModified; + char* mKeywords; + +public: + MtpObjectInfo(MtpObjectHandle handle); + virtual ~MtpObjectInfo(); + + void read(MtpDataPacket& packet); + + void print(); +}; + +}; // namespace android + +#endif // _MTP_OBJECT_INFO_H diff --git a/media/mtp/MtpPacket.cpp b/media/mtp/MtpPacket.cpp new file mode 100644 index 0000000..42bf8ba --- /dev/null +++ b/media/mtp/MtpPacket.cpp @@ -0,0 +1,154 @@ +/* + * 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 "MtpPacket" + +#include "MtpDebug.h" +#include "MtpPacket.h" +#include "mtp.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stdio.h> + +#include <usbhost/usbhost.h> + +namespace android { + +MtpPacket::MtpPacket(int bufferSize) + : mBuffer(NULL), + mBufferSize(bufferSize), + mAllocationIncrement(bufferSize), + mPacketSize(0) +{ + mBuffer = (uint8_t *)malloc(bufferSize); + if (!mBuffer) { + LOGE("out of memory!"); + abort(); + } +} + +MtpPacket::~MtpPacket() { + if (mBuffer) + free(mBuffer); +} + +void MtpPacket::reset() { + allocate(MTP_CONTAINER_HEADER_SIZE); + mPacketSize = MTP_CONTAINER_HEADER_SIZE; + memset(mBuffer, 0, mBufferSize); +} + +void MtpPacket::allocate(int length) { + if (length > mBufferSize) { + int newLength = length + mAllocationIncrement; + mBuffer = (uint8_t *)realloc(mBuffer, newLength); + if (!mBuffer) { + LOGE("out of memory!"); + abort(); + } + mBufferSize = newLength; + } +} + +void MtpPacket::dump() { +#define DUMP_BYTES_PER_ROW 16 + char buffer[500]; + char* bufptr = buffer; + + for (int i = 0; i < mPacketSize; i++) { + sprintf(bufptr, "%02X ", mBuffer[i]); + bufptr += strlen(bufptr); + if (i % DUMP_BYTES_PER_ROW == (DUMP_BYTES_PER_ROW - 1)) { + LOGV("%s", buffer); + bufptr = buffer; + } + } + if (bufptr != buffer) { + // print last line + LOGV("%s", buffer); + } + LOGV("\n"); +} + +uint16_t MtpPacket::getUInt16(int offset) const { + return ((uint16_t)mBuffer[offset + 1] << 8) | (uint16_t)mBuffer[offset]; +} + +uint32_t MtpPacket::getUInt32(int offset) const { + return ((uint32_t)mBuffer[offset + 3] << 24) | ((uint32_t)mBuffer[offset + 2] << 16) | + ((uint32_t)mBuffer[offset + 1] << 8) | (uint32_t)mBuffer[offset]; +} + +void MtpPacket::putUInt16(int offset, uint16_t value) { + mBuffer[offset++] = (uint8_t)(value & 0xFF); + mBuffer[offset++] = (uint8_t)((value >> 8) & 0xFF); +} + +void MtpPacket::putUInt32(int offset, uint32_t value) { + mBuffer[offset++] = (uint8_t)(value & 0xFF); + mBuffer[offset++] = (uint8_t)((value >> 8) & 0xFF); + mBuffer[offset++] = (uint8_t)((value >> 16) & 0xFF); + mBuffer[offset++] = (uint8_t)((value >> 24) & 0xFF); +} + +uint16_t MtpPacket::getContainerCode() const { + return getUInt16(MTP_CONTAINER_CODE_OFFSET); +} + +void MtpPacket::setContainerCode(uint16_t code) { + putUInt16(MTP_CONTAINER_CODE_OFFSET, code); +} + +MtpTransactionID MtpPacket::getTransactionID() const { + return getUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET); +} + +void MtpPacket::setTransactionID(MtpTransactionID id) { + putUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET, id); +} + +uint32_t MtpPacket::getParameter(int index) const { + if (index < 1 || index > 5) { + LOGE("index %d out of range in MtpRequestPacket::getParameter", index); + return 0; + } + return getUInt32(MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t)); +} + +void MtpPacket::setParameter(int index, uint32_t value) { + if (index < 1 || index > 5) { + LOGE("index %d out of range in MtpResponsePacket::setParameter", index); + return; + } + int offset = MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t); + if (mPacketSize < offset + sizeof(uint32_t)) + mPacketSize = offset + sizeof(uint32_t); + putUInt32(offset, value); +} + +#ifdef MTP_HOST +int MtpPacket::transfer(struct usb_endpoint *ep, void* buffer, int length) { + if (usb_endpoint_queue(ep, buffer, length)) { + LOGE("usb_endpoint_queue failed, errno: %d", errno); + return -1; + } + int ep_num; + return usb_endpoint_wait(usb_endpoint_get_device(ep), &ep_num); +} +#endif + +} // namespace android diff --git a/media/mtp/MtpPacket.h b/media/mtp/MtpPacket.h new file mode 100644 index 0000000..9c8d6da --- /dev/null +++ b/media/mtp/MtpPacket.h @@ -0,0 +1,69 @@ +/* + * 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_PACKET_H +#define _MTP_PACKET_H + +#include "MtpTypes.h" + +struct usb_endpoint; + +namespace android { + +class MtpPacket { + +protected: + uint8_t* mBuffer; + // current size of the buffer + int mBufferSize; + // number of bytes to add when resizing the buffer + int mAllocationIncrement; + // size of the data in the packet + int mPacketSize; + +public: + MtpPacket(int bufferSize); + virtual ~MtpPacket(); + + // sets packet size to the default container size and sets buffer to zero + virtual void reset(); + + void allocate(int length); + void dump(); + + uint16_t getContainerCode() const; + void setContainerCode(uint16_t code); + + MtpTransactionID getTransactionID() const; + void setTransactionID(MtpTransactionID id); + + uint32_t getParameter(int index) const; + void setParameter(int index, uint32_t value); + +#ifdef MTP_HOST + int transfer(struct usb_endpoint *ep, void* buffer, int length); +#endif + +protected: + uint16_t getUInt16(int offset) const; + uint32_t getUInt32(int offset) const; + void putUInt16(int offset, uint16_t value); + void putUInt32(int offset, uint32_t value); +}; + +}; // namespace android + +#endif // _MTP_PACKET_H diff --git a/media/mtp/MtpProperty.cpp b/media/mtp/MtpProperty.cpp new file mode 100644 index 0000000..932ad6a --- /dev/null +++ b/media/mtp/MtpProperty.cpp @@ -0,0 +1,327 @@ +/* + * 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 "MtpProperty" + +#include "MtpDataPacket.h" +#include "MtpProperty.h" +#include "MtpStringBuffer.h" +#include "MtpUtils.h" + +namespace android { + +MtpProperty::MtpProperty() + : mCode(0), + mType(0), + mWriteable(false), + mDefaultArrayLength(0), + mDefaultArrayValues(NULL), + mCurrentArrayLength(0), + mCurrentArrayValues(NULL), + mGroupCode(0), + mFormFlag(kFormNone), + mEnumLength(0), + mEnumValues(NULL) +{ + mDefaultValue.str = NULL; + mCurrentValue.str = NULL; + mMinimumValue.str = NULL; + mMaximumValue.str = NULL; +} + +MtpProperty::MtpProperty(MtpPropertyCode propCode, + MtpDataType type, + bool writeable, + int defaultValue) + : mCode(propCode), + mType(type), + mWriteable(writeable), + mDefaultArrayLength(0), + mDefaultArrayValues(NULL), + mCurrentArrayLength(0), + mCurrentArrayValues(NULL), + mGroupCode(0), + mFormFlag(kFormNone), + mEnumLength(0), + mEnumValues(NULL) +{ + memset(&mDefaultValue, 0, sizeof(mDefaultValue)); + memset(&mCurrentValue, 0, sizeof(mCurrentValue)); + memset(&mMinimumValue, 0, sizeof(mMinimumValue)); + memset(&mMaximumValue, 0, sizeof(mMaximumValue)); + + if (defaultValue) { + switch (type) { + case MTP_TYPE_INT8: + mDefaultValue.i8 = defaultValue; + break; + case MTP_TYPE_UINT8: + mDefaultValue.u8 = defaultValue; + break; + case MTP_TYPE_INT16: + mDefaultValue.i16 = defaultValue; + break; + case MTP_TYPE_UINT16: + mDefaultValue.u16 = defaultValue; + break; + case MTP_TYPE_INT32: + mDefaultValue.i32 = defaultValue; + break; + case MTP_TYPE_UINT32: + mDefaultValue.u32 = defaultValue; + break; + case MTP_TYPE_INT64: + mDefaultValue.i64 = defaultValue; + break; + case MTP_TYPE_UINT64: + mDefaultValue.u64 = defaultValue; + break; + default: + LOGE("unknown type %04X in MtpProperty::MtpProperty", type); + } + } +} + +MtpProperty::~MtpProperty() { + if (mType == MTP_TYPE_STR) { + // free all strings + free(mDefaultValue.str); + free(mCurrentValue.str); + free(mMinimumValue.str); + free(mMaximumValue.str); + if (mDefaultArrayValues) { + for (int i = 0; i < mDefaultArrayLength; i++) + free(mDefaultArrayValues[i].str); + } + if (mCurrentArrayValues) { + for (int i = 0; i < mCurrentArrayLength; i++) + free(mCurrentArrayValues[i].str); + } + if (mEnumValues) { + for (int i = 0; i < mEnumLength; i++) + free(mEnumValues[i].str); + } + } + delete[] mDefaultArrayValues; + delete[] mCurrentArrayValues; + delete[] mEnumValues; +} + +void MtpProperty::read(MtpDataPacket& packet, bool deviceProp) { + + mCode = packet.getUInt16(); + mType = packet.getUInt16(); + mWriteable = (packet.getUInt8() == 1); + switch (mType) { + case MTP_TYPE_AINT8: + case MTP_TYPE_AUINT8: + case MTP_TYPE_AINT16: + case MTP_TYPE_AUINT16: + case MTP_TYPE_AINT32: + case MTP_TYPE_AUINT32: + case MTP_TYPE_AINT64: + case MTP_TYPE_AUINT64: + case MTP_TYPE_AINT128: + case MTP_TYPE_AUINT128: + mDefaultArrayValues = readArrayValues(packet, mDefaultArrayLength); + mCurrentArrayValues = readArrayValues(packet, mCurrentArrayLength); + break; + default: + readValue(packet, mDefaultValue); + if (deviceProp) + readValue(packet, mCurrentValue); + } + mGroupCode = packet.getUInt32(); + mFormFlag = packet.getUInt8(); + + if (mFormFlag == kFormRange) { + readValue(packet, mMinimumValue); + readValue(packet, mMaximumValue); + readValue(packet, mStepSize); + } else if (mFormFlag == kFormEnum) { + mEnumLength = packet.getUInt16(); + mEnumValues = new MtpPropertyValue[mEnumLength]; + for (int i = 0; i < mEnumLength; i++) + readValue(packet, mEnumValues[i]); + } +} + +// FIXME - only works for object properties +void MtpProperty::write(MtpDataPacket& packet) { + packet.putUInt16(mCode); + packet.putUInt16(mType); + packet.putUInt8(mWriteable ? 1 : 0); + + switch (mType) { + case MTP_TYPE_AINT8: + case MTP_TYPE_AUINT8: + case MTP_TYPE_AINT16: + case MTP_TYPE_AUINT16: + case MTP_TYPE_AINT32: + case MTP_TYPE_AUINT32: + case MTP_TYPE_AINT64: + case MTP_TYPE_AUINT64: + case MTP_TYPE_AINT128: + case MTP_TYPE_AUINT128: + writeArrayValues(packet, mDefaultArrayValues, mDefaultArrayLength); + break; + default: + writeValue(packet, mDefaultValue); + } + packet.putUInt32(mGroupCode); + packet.putUInt8(mFormFlag); + if (mFormFlag == kFormRange) { + writeValue(packet, mMinimumValue); + writeValue(packet, mMaximumValue); + writeValue(packet, mStepSize); + } else if (mFormFlag == kFormEnum) { + packet.putUInt16(mEnumLength); + for (int i = 0; i < mEnumLength; i++) + writeValue(packet, mEnumValues[i]); + } +} + +void MtpProperty::print() { + LOGV("MtpProperty %04X\n", mCode); + LOGV(" type %04X\n", mType); + LOGV(" writeable %s\n", (mWriteable ? "true" : "false")); +} + +void MtpProperty::readValue(MtpDataPacket& packet, MtpPropertyValue& value) { + MtpStringBuffer stringBuffer; + + switch (mType) { + case MTP_TYPE_INT8: + case MTP_TYPE_AINT8: + value.i8 = packet.getInt8(); + break; + case MTP_TYPE_UINT8: + case MTP_TYPE_AUINT8: + value.u8 = packet.getUInt8(); + break; + case MTP_TYPE_INT16: + case MTP_TYPE_AINT16: + value.i16 = packet.getInt16(); + break; + case MTP_TYPE_UINT16: + case MTP_TYPE_AUINT16: + value.u16 = packet.getUInt16(); + break; + case MTP_TYPE_INT32: + case MTP_TYPE_AINT32: + value.i32 = packet.getInt32(); + break; + case MTP_TYPE_UINT32: + case MTP_TYPE_AUINT32: + value.u32 = packet.getUInt32(); + break; + case MTP_TYPE_INT64: + case MTP_TYPE_AINT64: + value.i64 = packet.getInt64(); + break; + case MTP_TYPE_UINT64: + case MTP_TYPE_AUINT64: + value.u64 = packet.getUInt64(); + break; + case MTP_TYPE_INT128: + case MTP_TYPE_AINT128: + packet.getInt128(value.i128); + break; + case MTP_TYPE_UINT128: + case MTP_TYPE_AUINT128: + packet.getUInt128(value.u128); + break; + case MTP_TYPE_STR: + packet.getString(stringBuffer); + value.str = strdup(stringBuffer); + break; + default: + LOGE("unknown type %04X in MtpProperty::readValue", mType); + } +} + +void MtpProperty::writeValue(MtpDataPacket& packet, MtpPropertyValue& value) { + MtpStringBuffer stringBuffer; + + switch (mType) { + case MTP_TYPE_INT8: + case MTP_TYPE_AINT8: + packet.putInt8(value.i8); + break; + case MTP_TYPE_UINT8: + case MTP_TYPE_AUINT8: + packet.putUInt8(value.u8); + break; + case MTP_TYPE_INT16: + case MTP_TYPE_AINT16: + packet.putInt16(value.i16); + break; + case MTP_TYPE_UINT16: + case MTP_TYPE_AUINT16: + packet.putUInt16(value.u16); + break; + case MTP_TYPE_INT32: + case MTP_TYPE_AINT32: + packet.putInt32(value.i32); + break; + case MTP_TYPE_UINT32: + case MTP_TYPE_AUINT32: + packet.putUInt32(value.u32); + break; + case MTP_TYPE_INT64: + case MTP_TYPE_AINT64: + packet.putInt64(value.i64); + break; + case MTP_TYPE_UINT64: + case MTP_TYPE_AUINT64: + packet.putUInt64(value.u64); + break; + case MTP_TYPE_INT128: + case MTP_TYPE_AINT128: + packet.putInt128(value.i128); + break; + case MTP_TYPE_UINT128: + case MTP_TYPE_AUINT128: + packet.putUInt128(value.u128); + break; + case MTP_TYPE_STR: + if (value.str) + packet.putString(value.str); + else + packet.putEmptyString(); + break; + default: + LOGE("unknown type %04X in MtpProperty::writeValue", mType); + } +} + +MtpPropertyValue* MtpProperty::readArrayValues(MtpDataPacket& packet, int& length) { + length = packet.getUInt32(); + if (length == 0) + return NULL; + MtpPropertyValue* result = new MtpPropertyValue[length]; + for (int i = 0; i < length; i++) + readValue(packet, result[i]); + return result; +} + +void MtpProperty::writeArrayValues(MtpDataPacket& packet, MtpPropertyValue* values, int length) { + packet.putUInt32(length); + for (int i = 0; i < length; i++) + writeValue(packet, values[i]); +} + +} // namespace android diff --git a/media/mtp/MtpProperty.h b/media/mtp/MtpProperty.h new file mode 100644 index 0000000..c5b4e28 --- /dev/null +++ b/media/mtp/MtpProperty.h @@ -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. + */ + +#ifndef _MTP_PROPERTY_H +#define _MTP_PROPERTY_H + +#include "MtpTypes.h" + +namespace android { + +class MtpDataPacket; + +class MtpProperty { +public: + MtpPropertyCode mCode; + MtpDataType mType; + bool mWriteable; + MtpPropertyValue mDefaultValue; + MtpPropertyValue mCurrentValue; + + // for array types + int mDefaultArrayLength; + MtpPropertyValue* mDefaultArrayValues; + int mCurrentArrayLength; + MtpPropertyValue* mCurrentArrayValues; + + enum { + kFormNone = 0, + kFormRange = 1, + kFormEnum = 2, + }; + + uint32_t mGroupCode; + uint8_t mFormFlag; + + // for range form + MtpPropertyValue mMinimumValue; + MtpPropertyValue mMaximumValue; + MtpPropertyValue mStepSize; + + // for enum form + int mEnumLength; + MtpPropertyValue* mEnumValues; + +public: + MtpProperty(); + MtpProperty(MtpPropertyCode propCode, + MtpDataType type, + bool writeable = false, + int defaultValue = 0); + virtual ~MtpProperty(); + + inline MtpPropertyCode getPropertyCode() const { return mCode; } + + void read(MtpDataPacket& packet, bool deviceProp); + void write(MtpDataPacket& packet); + + void print(); + +private: + void readValue(MtpDataPacket& packet, MtpPropertyValue& value); + void writeValue(MtpDataPacket& packet, MtpPropertyValue& value); + MtpPropertyValue* readArrayValues(MtpDataPacket& packet, int& length); + void writeArrayValues(MtpDataPacket& packet, MtpPropertyValue* values, int length); +}; + +}; // namespace android + +#endif // _MTP_PROPERTY_H diff --git a/media/mtp/MtpRequestPacket.cpp b/media/mtp/MtpRequestPacket.cpp new file mode 100644 index 0000000..8ece580 --- /dev/null +++ b/media/mtp/MtpRequestPacket.cpp @@ -0,0 +1,56 @@ +/* + * 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 "MtpRequestPacket" + +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> + +#include "MtpRequestPacket.h" + +namespace android { + +MtpRequestPacket::MtpRequestPacket() + : MtpPacket(512) +{ +} + +MtpRequestPacket::~MtpRequestPacket() { +} + +#ifdef MTP_DEVICE +int MtpRequestPacket::read(int fd) { + int ret = ::read(fd, mBuffer, mBufferSize); + if (ret >= 0) + mPacketSize = ret; + else + mPacketSize = 0; + return ret; +} +#endif + +#ifdef MTP_HOST + // write our buffer to the given endpoint (host mode) +int MtpRequestPacket::write(struct usb_endpoint *ep) +{ + putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize); + putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_COMMAND); + return transfer(ep, mBuffer, mPacketSize); +} +#endif + +} // namespace android diff --git a/media/mtp/MtpRequestPacket.h b/media/mtp/MtpRequestPacket.h new file mode 100644 index 0000000..df518f2 --- /dev/null +++ b/media/mtp/MtpRequestPacket.h @@ -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. + */ + +#ifndef _MTP_REQUEST_PACKET_H +#define _MTP_REQUEST_PACKET_H + +#include "MtpPacket.h" +#include "mtp.h" + +namespace android { + +class MtpRequestPacket : public MtpPacket { + +public: + MtpRequestPacket(); + virtual ~MtpRequestPacket(); + +#ifdef MTP_DEVICE + // fill our buffer with data from the given file descriptor + int read(int fd); +#endif + +#ifdef MTP_HOST + // write our buffer to the given endpoint + int write(struct usb_endpoint *ep); +#endif + + inline MtpOperationCode getOperationCode() const { return getContainerCode(); } + inline void setOperationCode(MtpOperationCode code) + { return setContainerCode(code); } +}; + +}; // namespace android + +#endif // _MTP_REQUEST_PACKET_H diff --git a/media/mtp/MtpResponsePacket.cpp b/media/mtp/MtpResponsePacket.cpp new file mode 100644 index 0000000..3ef714e --- /dev/null +++ b/media/mtp/MtpResponsePacket.cpp @@ -0,0 +1,57 @@ +/* + * 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 "MtpResponsePacket" + +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> + +#include "MtpResponsePacket.h" + +namespace android { + +MtpResponsePacket::MtpResponsePacket() + : MtpPacket(512) +{ +} + +MtpResponsePacket::~MtpResponsePacket() { +} + +#ifdef MTP_DEVICE +int MtpResponsePacket::write(int fd) { + putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize); + putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_RESPONSE); + int ret = ::write(fd, mBuffer, mPacketSize); + return (ret < 0 ? ret : 0); +} +#endif + +#ifdef MTP_HOST + // read our buffer from the given endpoint +int MtpResponsePacket::read(struct usb_endpoint *ep) { + int ret = transfer(ep, mBuffer, mBufferSize); + if (ret >= 0) + mPacketSize = ret; + else + mPacketSize = 0; + return ret; +} +#endif + +} // namespace android + diff --git a/media/mtp/MtpResponsePacket.h b/media/mtp/MtpResponsePacket.h new file mode 100644 index 0000000..373f8f9 --- /dev/null +++ b/media/mtp/MtpResponsePacket.h @@ -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. + */ + +#ifndef _MTP_RESPONSE_PACKET_H +#define _MTP_RESPONSE_PACKET_H + +#include "MtpPacket.h" +#include "mtp.h" + +namespace android { + +class MtpResponsePacket : public MtpPacket { + +public: + MtpResponsePacket(); + virtual ~MtpResponsePacket(); + +#ifdef MTP_DEVICE + // write our data to the given file descriptor + int write(int fd); +#endif + +#ifdef MTP_HOST + // read our buffer from the given endpoint + int read(struct usb_endpoint *ep); +#endif + + inline MtpResponseCode getResponseCode() const { return getContainerCode(); } + inline void setResponseCode(MtpResponseCode code) + { return setContainerCode(code); } +}; + +}; // namespace android + +#endif // _MTP_RESPONSE_PACKET_H diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp new file mode 100644 index 0000000..30abfb8 --- /dev/null +++ b/media/mtp/MtpServer.cpp @@ -0,0 +1,688 @@ +/* + * 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. + */ + +#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 <cutils/properties.h> + +#include "MtpDebug.h" +#include "MtpDatabase.h" +#include "MtpProperty.h" +#include "MtpServer.h" +#include "MtpStorage.h" +#include "MtpStringBuffer.h" + +#include <linux/usb/f_mtp.h> + +namespace android { + +static const MtpOperationCode kSupportedOperationCodes[] = { + MTP_OPERATION_GET_DEVICE_INFO, + MTP_OPERATION_OPEN_SESSION, + MTP_OPERATION_CLOSE_SESSION, + MTP_OPERATION_GET_STORAGE_IDS, + MTP_OPERATION_GET_STORAGE_INFO, + MTP_OPERATION_GET_NUM_OBJECTS, + MTP_OPERATION_GET_OBJECT_HANDLES, + MTP_OPERATION_GET_OBJECT_INFO, + MTP_OPERATION_GET_OBJECT, +// MTP_OPERATION_GET_THUMB, + MTP_OPERATION_DELETE_OBJECT, + MTP_OPERATION_SEND_OBJECT_INFO, + MTP_OPERATION_SEND_OBJECT, +// MTP_OPERATION_INITIATE_CAPTURE, +// MTP_OPERATION_FORMAT_STORE, +// MTP_OPERATION_RESET_DEVICE, +// MTP_OPERATION_SELF_TEST, +// MTP_OPERATION_SET_OBJECT_PROTECTION, +// MTP_OPERATION_POWER_DOWN, +// MTP_OPERATION_GET_DEVICE_PROP_DESC, +// MTP_OPERATION_GET_DEVICE_PROP_VALUE, +// MTP_OPERATION_SET_DEVICE_PROP_VALUE, +// MTP_OPERATION_RESET_DEVICE_PROP_VALUE, +// MTP_OPERATION_TERMINATE_OPEN_CAPTURE, +// MTP_OPERATION_MOVE_OBJECT, +// MTP_OPERATION_COPY_OBJECT, +// MTP_OPERATION_GET_PARTIAL_OBJECT, +// MTP_OPERATION_INITIATE_OPEN_CAPTURE, + MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED, +// MTP_OPERATION_GET_OBJECT_PROP_DESC, + MTP_OPERATION_GET_OBJECT_PROP_VALUE, +// MTP_OPERATION_SET_OBJECT_PROP_VALUE, + MTP_OPERATION_GET_OBJECT_REFERENCES, + MTP_OPERATION_SET_OBJECT_REFERENCES, +// MTP_OPERATION_SKIP, +}; + +static const MtpEventCode kSupportedEventCodes[] = { + MTP_EVENT_OBJECT_ADDED, + MTP_EVENT_OBJECT_REMOVED, +}; + +MtpServer::MtpServer(int fd, MtpDatabase* database, + int fileGroup, int filePerm, int directoryPerm) + : mFD(fd), + mDatabase(database), + mFileGroup(fileGroup), + mFilePermission(filePerm), + mDirectoryPermission(directoryPerm), + mSessionID(0), + mSessionOpen(false), + mSendObjectHandle(kInvalidObjectHandle), + mSendObjectFormat(0), + mSendObjectFileSize(0) +{ + initObjectProperties(); +} + +MtpServer::~MtpServer() { +} + +void MtpServer::addStorage(const char* filePath) { + int index = mStorages.size() + 1; + index |= index << 16; // set high and low part to our index + MtpStorage* storage = new MtpStorage(index, filePath, mDatabase); + addStorage(storage); +} + +MtpStorage* MtpServer::getStorage(MtpStorageID id) { + for (int i = 0; i < mStorages.size(); i++) { + MtpStorage* storage = mStorages[i]; + if (storage->getStorageID() == id) + return storage; + } + return NULL; +} + +void MtpServer::run() { + int fd = mFD; + + LOGV("MtpServer::run fd: %d\n", fd); + + while (1) { + int ret = mRequest.read(fd); + if (ret < 0) { + LOGE("request read returned %d, errno: %d", ret, errno); + if (errno == ECANCELED) { + // return to top of loop and wait for next command + continue; + } + break; + } + MtpOperationCode operation = mRequest.getOperationCode(); + MtpTransactionID transaction = mRequest.getTransactionID(); + + LOGV("operation: %s", MtpDebug::getOperationCodeName(operation)); + mRequest.dump(); + + // FIXME need to generalize this + bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO + || operation == MTP_OPERATION_SET_OBJECT_REFERENCES); + if (dataIn) { + int ret = mData.read(fd); + if (ret < 0) { + LOGE("data read returned %d, errno: %d", ret, errno); + if (errno == ECANCELED) { + // return to top of loop and wait for next command + continue; + } + break; + } + LOGV("received data:"); + mData.dump(); + } else { + mData.reset(); + } + + if (handleRequest()) { + if (!dataIn && mData.hasData()) { + mData.setOperationCode(operation); + mData.setTransactionID(transaction); + LOGV("sending data:"); + mData.dump(); + ret = mData.write(fd); + if (ret < 0) { + LOGE("request write returned %d, errno: %d", ret, errno); + if (errno == ECANCELED) { + // return to top of loop and wait for next command + continue; + } + break; + } + } + + mResponse.setTransactionID(transaction); + LOGV("sending response %04X", mResponse.getResponseCode()); + ret = mResponse.write(fd); + if (ret < 0) { + LOGE("request write returned %d, errno: %d", ret, errno); + if (errno == ECANCELED) { + // return to top of loop and wait for next command + continue; + } + break; + } + } else { + LOGV("skipping response\n"); + } + } +} + +MtpProperty* MtpServer::getObjectProperty(MtpPropertyCode propCode) { + for (int i = 0; i < mObjectProperties.size(); i++) { + MtpProperty* property = mObjectProperties[i]; + if (property->getPropertyCode() == propCode) + return property; + } + return NULL; +} + +MtpProperty* MtpServer::getDeviceProperty(MtpPropertyCode propCode) { + for (int i = 0; i < mDeviceProperties.size(); i++) { + MtpProperty* property = mDeviceProperties[i]; + if (property->getPropertyCode() == propCode) + return property; + } + return NULL; +} + +void MtpServer::sendObjectAdded(MtpObjectHandle handle) { + if (mSessionOpen) { + LOGD("sendObjectAdded %d\n", handle); + mEvent.setEventCode(MTP_EVENT_OBJECT_ADDED); + mEvent.setTransactionID(mRequest.getTransactionID()); + mEvent.setParameter(1, handle); + int ret = mEvent.write(mFD); + LOGD("mEvent.write returned %d\n", ret); + } +} + +void MtpServer::sendObjectRemoved(MtpObjectHandle handle) { + if (mSessionOpen) { + LOGD("sendObjectRemoved %d\n", handle); + mEvent.setEventCode(MTP_EVENT_OBJECT_REMOVED); + mEvent.setTransactionID(mRequest.getTransactionID()); + mEvent.setParameter(1, handle); + int ret = mEvent.write(mFD); + LOGD("mEvent.write returned %d\n", ret); + } +} + +void MtpServer::initObjectProperties() { + mObjectProperties.push(new MtpProperty(MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32)); + mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16)); + mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64)); + mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR)); + mObjectProperties.push(new MtpProperty(MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32)); +} + +bool MtpServer::handleRequest() { + MtpOperationCode operation = mRequest.getOperationCode(); + MtpResponseCode response; + + mResponse.reset(); + + if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) { + // FIXME - need to delete mSendObjectHandle from the database + LOGE("expected SendObject after SendObjectInfo"); + mSendObjectHandle = kInvalidObjectHandle; + } + + switch (operation) { + case MTP_OPERATION_GET_DEVICE_INFO: + response = doGetDeviceInfo(); + break; + case MTP_OPERATION_OPEN_SESSION: + response = doOpenSession(); + break; + case MTP_OPERATION_CLOSE_SESSION: + response = doCloseSession(); + break; + case MTP_OPERATION_GET_STORAGE_IDS: + response = doGetStorageIDs(); + break; + case MTP_OPERATION_GET_STORAGE_INFO: + response = doGetStorageInfo(); + break; + case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED: + response = doGetObjectPropsSupported(); + break; + case MTP_OPERATION_GET_OBJECT_HANDLES: + response = doGetObjectHandles(); + break; + case MTP_OPERATION_GET_NUM_OBJECTS: + response = doGetNumObjects(); + break; + case MTP_OPERATION_GET_OBJECT_REFERENCES: + response = doGetObjectReferences(); + break; + case MTP_OPERATION_SET_OBJECT_REFERENCES: + response = doSetObjectReferences(); + break; + case MTP_OPERATION_GET_OBJECT_PROP_VALUE: + response = doGetObjectPropValue(); + break; + case MTP_OPERATION_GET_OBJECT_INFO: + response = doGetObjectInfo(); + break; + case MTP_OPERATION_GET_OBJECT: + response = doGetObject(); + break; + case MTP_OPERATION_SEND_OBJECT_INFO: + response = doSendObjectInfo(); + break; + case MTP_OPERATION_SEND_OBJECT: + response = doSendObject(); + break; + case MTP_OPERATION_DELETE_OBJECT: + response = doDeleteObject(); + break; + case MTP_OPERATION_GET_OBJECT_PROP_DESC: + response = doGetObjectPropDesc(); + break; + default: + response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED; + break; + } + + if (response == MTP_RESPONSE_TRANSACTION_CANCELLED) + return false; + mResponse.setResponseCode(response); + return true; +} + +MtpResponseCode MtpServer::doGetDeviceInfo() { + MtpStringBuffer string; + char prop_value[PROPERTY_VALUE_MAX]; + + MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats(); + MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats(); + MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties(); + + // fill in device info + mData.putUInt16(MTP_STANDARD_VERSION); + mData.putUInt32(6); // MTP Vendor Extension ID + mData.putUInt16(MTP_STANDARD_VERSION); + string.set("microsoft.com: 1.0;"); + mData.putString(string); // MTP Extensions + mData.putUInt16(0); //Functional Mode + mData.putAUInt16(kSupportedOperationCodes, + sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported + mData.putAUInt16(kSupportedEventCodes, + sizeof(kSupportedEventCodes) / sizeof(uint16_t)); // Events Supported + mData.putAUInt16(deviceProperties); // Device Properties Supported + mData.putAUInt16(captureFormats); // Capture Formats + mData.putAUInt16(playbackFormats); // Playback Formats + // FIXME + string.set("Google, Inc."); + mData.putString(string); // Manufacturer + + property_get("ro.product.model", prop_value, "MTP Device"); + string.set(prop_value); + mData.putString(string); // Model + string.set("1.0"); + mData.putString(string); // Device Version + + property_get("ro.serialno", prop_value, "????????"); + string.set(prop_value); + mData.putString(string); // Serial Number + + delete playbackFormats; + delete captureFormats; + delete deviceProperties; + + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doOpenSession() { + if (mSessionOpen) { + mResponse.setParameter(1, mSessionID); + return MTP_RESPONSE_SESSION_ALREADY_OPEN; + } + mSessionID = mRequest.getParameter(1); + mSessionOpen = true; + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doCloseSession() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + mSessionID = 0; + mSessionOpen = false; + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doGetStorageIDs() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + + int count = mStorages.size(); + mData.putUInt32(count); + for (int i = 0; i < count; i++) + mData.putUInt32(mStorages[i]->getStorageID()); + + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doGetStorageInfo() { + MtpStringBuffer string; + + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + MtpStorageID id = mRequest.getParameter(1); + MtpStorage* storage = getStorage(id); + if (!storage) + return MTP_RESPONSE_INVALID_STORAGE_ID; + + mData.putUInt16(storage->getType()); + mData.putUInt16(storage->getFileSystemType()); + mData.putUInt16(storage->getAccessCapability()); + mData.putUInt64(storage->getMaxCapacity()); + mData.putUInt64(storage->getFreeSpace()); + mData.putUInt32(1024*1024*1024); // Free Space in Objects + string.set(storage->getDescription()); + mData.putString(string); + mData.putEmptyString(); // Volume Identifier + + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doGetObjectPropsSupported() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + MtpObjectFormat format = mRequest.getParameter(1); + MtpDevicePropertyList* properties = mDatabase->getSupportedObjectProperties(format); + mData.putAUInt16(properties); + delete properties; + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doGetObjectHandles() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage + MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats + MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent + // 0x00000000 for all objects? + if (parent == 0xFFFFFFFF) + parent = 0; + + MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent); + mData.putAUInt32(handles); + delete handles; + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doGetNumObjects() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage + MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats + MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent + // 0x00000000 for all objects? + if (parent == 0xFFFFFFFF) + parent = 0; + + int count = mDatabase->getNumObjects(storageID, format, parent); + if (count >= 0) { + mResponse.setParameter(1, count); + return MTP_RESPONSE_OK; + } else { + mResponse.setParameter(1, 0); + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } +} + +MtpResponseCode MtpServer::doGetObjectReferences() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + MtpStorageID handle = mRequest.getParameter(1); + MtpObjectHandleList* handles = mDatabase->getObjectReferences(handle); + if (!handles) { + mData.putEmptyArray(); + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + mData.putAUInt32(handles); + delete handles; + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doSetObjectReferences() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + MtpStorageID handle = mRequest.getParameter(1); + MtpObjectHandleList* references = mData.getAUInt32(); + MtpResponseCode result = mDatabase->setObjectReferences(handle, references); + delete references; + return result; +} + +MtpResponseCode MtpServer::doGetObjectPropValue() { + MtpObjectHandle handle = mRequest.getParameter(1); + MtpObjectProperty property = mRequest.getParameter(2); + + return mDatabase->getObjectProperty(handle, property, mData); +} + +MtpResponseCode MtpServer::doGetObjectInfo() { + MtpObjectHandle handle = mRequest.getParameter(1); + return mDatabase->getObjectInfo(handle, mData); +} + +MtpResponseCode MtpServer::doGetObject() { + MtpObjectHandle handle = mRequest.getParameter(1); + MtpString pathBuf; + int64_t fileLength; + int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength); + if (result != MTP_RESPONSE_OK) + return result; + + const char* filePath = (const char *)pathBuf; + mtp_file_range mfr; + mfr.fd = open(filePath, O_RDONLY); + if (mfr.fd < 0) { + return MTP_RESPONSE_GENERAL_ERROR; + } + mfr.offset = 0; + mfr.length = fileLength; + + // send data header + mData.setOperationCode(mRequest.getOperationCode()); + mData.setTransactionID(mRequest.getTransactionID()); + mData.writeDataHeader(mFD, fileLength); + + // then transfer the file + int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr); + close(mfr.fd); + if (ret < 0) { + if (errno == ECANCELED) + return MTP_RESPONSE_TRANSACTION_CANCELLED; + else + return MTP_RESPONSE_GENERAL_ERROR; + } + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doSendObjectInfo() { + MtpString path; + MtpStorageID storageID = mRequest.getParameter(1); + MtpStorage* storage = getStorage(storageID); + MtpObjectHandle parent = mRequest.getParameter(2); + if (!storage) + return MTP_RESPONSE_INVALID_STORAGE_ID; + + // special case the root + if (parent == MTP_PARENT_ROOT) { + path = storage->getPath(); + parent = 0; + } else { + int64_t dummy; + int result = mDatabase->getObjectFilePath(parent, path, dummy); + if (result != MTP_RESPONSE_OK) + return result; + } + + // read only the fields we need + mData.getUInt32(); // storage ID + MtpObjectFormat format = mData.getUInt16(); + mData.getUInt16(); // protection status + mSendObjectFileSize = mData.getUInt32(); + mData.getUInt16(); // thumb format + mData.getUInt32(); // thumb compressed size + mData.getUInt32(); // thumb pix width + mData.getUInt32(); // thumb pix height + mData.getUInt32(); // image pix width + mData.getUInt32(); // image pix height + mData.getUInt32(); // image bit depth + mData.getUInt32(); // parent + uint16_t associationType = mData.getUInt16(); + uint32_t associationDesc = mData.getUInt32(); // association desc + mData.getUInt32(); // sequence number + MtpStringBuffer name, created, modified; + mData.getString(name); // file name + mData.getString(created); // date created + mData.getString(modified); // date modified + // keywords follow + + time_t modifiedTime; + if (!parseDateTime(modified, modifiedTime)) + modifiedTime = 0; + + if (path[path.size() - 1] != '/') + path += "/"; + path += (const char *)name; + + MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path, + format, parent, storageID, mSendObjectFileSize, modifiedTime); + if (handle == kInvalidObjectHandle) { + return MTP_RESPONSE_GENERAL_ERROR; + } + + if (format == MTP_FORMAT_ASSOCIATION) { + mode_t mask = umask(0); + int ret = mkdir((const char *)path, mDirectoryPermission); + umask(mask); + if (ret && ret != -EEXIST) + return MTP_RESPONSE_GENERAL_ERROR; + chown((const char *)path, getuid(), mFileGroup); + } else { + mSendObjectFilePath = path; + // save the handle for the SendObject call, which should follow + mSendObjectHandle = handle; + mSendObjectFormat = format; + } + + mResponse.setParameter(1, storageID); + mResponse.setParameter(2, (parent == 0 ? 0xFFFFFFFF: parent)); + mResponse.setParameter(3, handle); + + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doSendObject() { + MtpResponseCode result = MTP_RESPONSE_OK; + mode_t mask; + int ret; + + if (mSendObjectHandle == kInvalidObjectHandle) { + LOGE("Expected SendObjectInfo before SendObject"); + result = MTP_RESPONSE_NO_VALID_OBJECT_INFO; + goto done; + } + + // read the header + ret = mData.readDataHeader(mFD); + // FIXME - check for errors here. + + // reset so we don't attempt to send this back + mData.reset(); + + mtp_file_range mfr; + mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC); + if (mfr.fd < 0) { + result = MTP_RESPONSE_GENERAL_ERROR; + goto done; + } + fchown(mfr.fd, getuid(), mFileGroup); + // set permissions + mask = umask(0); + fchmod(mfr.fd, mFilePermission); + umask(mask); + + mfr.offset = 0; + mfr.length = mSendObjectFileSize; + + // transfer the file + ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr); + close(mfr.fd); + + LOGV("MTP_RECEIVE_FILE returned %d", ret); + + if (ret < 0) { + unlink(mSendObjectFilePath); + if (errno == ECANCELED) + result = MTP_RESPONSE_TRANSACTION_CANCELLED; + else + result = MTP_RESPONSE_GENERAL_ERROR; + } + +done: + mDatabase->endSendObject(mSendObjectFilePath, mSendObjectHandle, mSendObjectFormat, + result == MTP_RESPONSE_OK); + mSendObjectHandle = kInvalidObjectHandle; + mSendObjectFormat = 0; + return result; +} + +MtpResponseCode MtpServer::doDeleteObject() { + MtpObjectHandle handle = mRequest.getParameter(1); + MtpObjectFormat format = mRequest.getParameter(1); + // FIXME - support deleting all objects if handle is 0xFFFFFFFF + // FIXME - implement deleting objects by format + // FIXME - handle non-empty directories + + MtpString filePath; + int64_t fileLength; + int result = mDatabase->getObjectFilePath(handle, filePath, fileLength); + if (result == MTP_RESPONSE_OK) { + LOGV("deleting %s", (const char *)filePath); + // one of these should work + rmdir((const char *)filePath); + unlink((const char *)filePath); + return mDatabase->deleteFile(handle); + } else { + return result; + } +} + +MtpResponseCode MtpServer::doGetObjectPropDesc() { + MtpObjectProperty propCode = mRequest.getParameter(1); + MtpObjectFormat format = mRequest.getParameter(2); + MtpProperty* property = getObjectProperty(propCode); + if (!property) + return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED; + + property->write(mData); + return MTP_RESPONSE_OK; +} + +} // namespace android diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h new file mode 100644 index 0000000..19ccf24 --- /dev/null +++ b/media/mtp/MtpServer.h @@ -0,0 +1,111 @@ +/* + * 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_SERVER_H +#define _MTP_SERVER_H + +#include "MtpRequestPacket.h" +#include "MtpDataPacket.h" +#include "MtpResponsePacket.h" +#include "MtpEventPacket.h" +#include "mtp.h" + +#include "MtpUtils.h" + +namespace android { + +class MtpDatabase; +class MtpProperty; +class MtpStorage; + +class MtpServer { + +private: + // file descriptor for MTP kernel driver + int mFD; + + MtpDatabase* mDatabase; + + // group to own new files and folders + int mFileGroup; + // permissions for new files and directories + int mFilePermission; + int mDirectoryPermission; + + // current session ID + MtpSessionID mSessionID; + // true if we have an open session and mSessionID is valid + bool mSessionOpen; + + MtpRequestPacket mRequest; + MtpDataPacket mData; + MtpResponsePacket mResponse; + MtpEventPacket mEvent; + + MtpStorageList mStorages; + + MtpPropertyList mObjectProperties; + MtpPropertyList mDeviceProperties; + + // handle for new object, set by SendObjectInfo and used by SendObject + MtpObjectHandle mSendObjectHandle; + MtpObjectFormat mSendObjectFormat; + MtpString mSendObjectFilePath; + size_t mSendObjectFileSize; + +public: + MtpServer(int fd, MtpDatabase* database, + int fileGroup, int filePerm, int directoryPerm); + virtual ~MtpServer(); + + void addStorage(const char* filePath); + inline void addStorage(MtpStorage* storage) { mStorages.push(storage); } + MtpStorage* getStorage(MtpStorageID id); + void run(); + + MtpProperty* getObjectProperty(MtpPropertyCode propCode); + MtpProperty* getDeviceProperty(MtpPropertyCode propCode); + + void sendObjectAdded(MtpObjectHandle handle); + void sendObjectRemoved(MtpObjectHandle handle); + +private: + void initObjectProperties(); + + bool handleRequest(); + + MtpResponseCode doGetDeviceInfo(); + MtpResponseCode doOpenSession(); + MtpResponseCode doCloseSession(); + MtpResponseCode doGetStorageIDs(); + MtpResponseCode doGetStorageInfo(); + MtpResponseCode doGetObjectPropsSupported(); + MtpResponseCode doGetObjectHandles(); + MtpResponseCode doGetNumObjects(); + MtpResponseCode doGetObjectReferences(); + MtpResponseCode doSetObjectReferences(); + MtpResponseCode doGetObjectPropValue(); + MtpResponseCode doGetObjectInfo(); + MtpResponseCode doGetObject(); + MtpResponseCode doSendObjectInfo(); + MtpResponseCode doSendObject(); + MtpResponseCode doDeleteObject(); + MtpResponseCode doGetObjectPropDesc(); +}; + +}; // namespace android + +#endif // _MTP_SERVER_H diff --git a/media/mtp/MtpStorage.cpp b/media/mtp/MtpStorage.cpp new file mode 100644 index 0000000..eccf186 --- /dev/null +++ b/media/mtp/MtpStorage.cpp @@ -0,0 +1,80 @@ +/* + * 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 "MtpStorage" + +#include "MtpDebug.h" +#include "MtpDatabase.h" +#include "MtpStorage.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <unistd.h> +#include <dirent.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <limits.h> + +namespace android { + +MtpStorage::MtpStorage(MtpStorageID id, const char* filePath, MtpDatabase* db) + : mStorageID(id), + mFilePath(filePath), + mDatabase(db), + mMaxCapacity(0) +{ + LOGD("MtpStorage id: %d path: %s\n", id, filePath); +} + +MtpStorage::~MtpStorage() { +} + +int MtpStorage::getType() const { + return MTP_STORAGE_FIXED_RAM; +} + +int MtpStorage::getFileSystemType() const { + return MTP_STORAGE_FILESYSTEM_HIERARCHICAL; +} + +int MtpStorage::getAccessCapability() const { + return MTP_STORAGE_READ_WRITE; +} + +uint64_t MtpStorage::getMaxCapacity() { + if (mMaxCapacity == 0) { + struct statfs stat; + if (statfs(mFilePath, &stat)) + return -1; + mMaxCapacity = (uint64_t)stat.f_blocks * (uint64_t)stat.f_bsize; + } + return mMaxCapacity; +} + +uint64_t MtpStorage::getFreeSpace() { + struct statfs stat; + if (statfs(mFilePath, &stat)) + return -1; + return (uint64_t)stat.f_bavail * (uint64_t)stat.f_bsize; +} + +const char* MtpStorage::getDescription() const { + return "Device Storage"; +} + +} // namespace android diff --git a/media/mtp/MtpStorage.h b/media/mtp/MtpStorage.h new file mode 100644 index 0000000..b13b926 --- /dev/null +++ b/media/mtp/MtpStorage.h @@ -0,0 +1,50 @@ +/* + * 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_STORAGE_H +#define _MTP_STORAGE_H + +#include "mtp.h" + +namespace android { + +class MtpDatabase; + +class MtpStorage { + +private: + MtpStorageID mStorageID; + const char* mFilePath; + MtpDatabase* mDatabase; + uint64_t mMaxCapacity; + +public: + MtpStorage(MtpStorageID id, const char* filePath, MtpDatabase* db); + virtual ~MtpStorage(); + + inline MtpStorageID getStorageID() const { return mStorageID; } + int getType() const; + int getFileSystemType() const; + int getAccessCapability() const; + uint64_t getMaxCapacity(); + uint64_t getFreeSpace(); + const char* getDescription() const; + inline const char* getPath() const { return mFilePath; } +}; + +}; // namespace android + +#endif // _MTP_STORAGE_H diff --git a/media/mtp/MtpStorageInfo.cpp b/media/mtp/MtpStorageInfo.cpp new file mode 100644 index 0000000..ca64ac0 --- /dev/null +++ b/media/mtp/MtpStorageInfo.cpp @@ -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. + */ + +#define LOG_TAG "MtpStorageInfo" + +#include "MtpDebug.h" +#include "MtpDataPacket.h" +#include "MtpStorageInfo.h" +#include "MtpStringBuffer.h" + +namespace android { + +MtpStorageInfo::MtpStorageInfo(MtpStorageID id) + : mStorageID(id), + mStorageType(0), + mFileSystemType(0), + mAccessCapability(0), + mMaxCapacity(0), + mFreeSpaceBytes(0), + mFreeSpaceObjects(0), + mStorageDescription(NULL), + mVolumeIdentifier(NULL) +{ +} + +MtpStorageInfo::~MtpStorageInfo() { + if (mStorageDescription) + free(mStorageDescription); + if (mVolumeIdentifier) + free(mVolumeIdentifier); +} + +void MtpStorageInfo::read(MtpDataPacket& packet) { + MtpStringBuffer string; + + // read the device info + mStorageType = packet.getUInt16(); + mFileSystemType = packet.getUInt16(); + mAccessCapability = packet.getUInt16(); + mMaxCapacity = packet.getUInt64(); + mFreeSpaceBytes = packet.getUInt64(); + mFreeSpaceObjects = packet.getUInt32(); + + packet.getString(string); + mStorageDescription = strdup((const char *)string); + packet.getString(string); + mVolumeIdentifier = strdup((const char *)string); +} + +void MtpStorageInfo::print() { + LOGD("Storage Info %08X:\n\tmStorageType: %d\n\tmFileSystemType: %d\n\tmAccessCapability: %d\n", + mStorageID, mStorageType, mFileSystemType, mAccessCapability); + LOGD("\tmMaxCapacity: %lld\n\tmFreeSpaceBytes: %lld\n\tmFreeSpaceObjects: %d\n", + mMaxCapacity, mFreeSpaceBytes, mFreeSpaceObjects); + LOGD("\tmStorageDescription: %s\n\tmVolumeIdentifier: %s\n", + mStorageDescription, mVolumeIdentifier); +} + +} // namespace android diff --git a/media/mtp/MtpStorageInfo.h b/media/mtp/MtpStorageInfo.h new file mode 100644 index 0000000..2cb626e --- /dev/null +++ b/media/mtp/MtpStorageInfo.h @@ -0,0 +1,49 @@ +/* + * 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_STORAGE_INFO_H +#define _MTP_STORAGE_INFO_H + +#include "MtpTypes.h" + +namespace android { + +class MtpDataPacket; + +class MtpStorageInfo { +public: + MtpStorageID mStorageID; + uint16_t mStorageType; + uint16_t mFileSystemType; + uint16_t mAccessCapability; + uint64_t mMaxCapacity; + uint64_t mFreeSpaceBytes; + uint32_t mFreeSpaceObjects; + char* mStorageDescription; + char* mVolumeIdentifier; + +public: + MtpStorageInfo(MtpStorageID id); + virtual ~MtpStorageInfo(); + + void read(MtpDataPacket& packet); + + void print(); +}; + +}; // namespace android + +#endif // _MTP_STORAGE_INFO_H diff --git a/media/mtp/MtpStringBuffer.cpp b/media/mtp/MtpStringBuffer.cpp new file mode 100644 index 0000000..8bf6731 --- /dev/null +++ b/media/mtp/MtpStringBuffer.cpp @@ -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. + */ + +#define LOG_TAG "MtpStringBuffer" + +#include <string.h> + +#include "MtpDataPacket.h" +#include "MtpStringBuffer.h" + +namespace android { + +MtpStringBuffer::MtpStringBuffer() + : mCharCount(0), + mByteCount(1) +{ + mBuffer[0] = 0; +} + +MtpStringBuffer::MtpStringBuffer(const char* src) + : mCharCount(0), + mByteCount(1) +{ + set(src); +} + +MtpStringBuffer::MtpStringBuffer(const MtpStringBuffer& src) + : mCharCount(src.mCharCount), + mByteCount(src.mByteCount) +{ + memcpy(mBuffer, src.mBuffer, mByteCount); +} + + +MtpStringBuffer::~MtpStringBuffer() { +} + +void MtpStringBuffer::set(const char* src) { + int length = strlen(src); + if (length >= sizeof(mBuffer)) + length = sizeof(mBuffer) - 1; + memcpy(mBuffer, src, length); + + // count the characters + int count = 0; + char ch; + while ((ch = *src++) != 0) { + if ((ch & 0x80) == 0) { + // single byte character + } else if ((ch & 0xE0) == 0xC0) { + // two byte character + if (! *src++) { + // last character was truncated, so ignore last byte + length--; + break; + } + } else if ((ch & 0xF0) == 0xE0) { + // 3 byte char + if (! *src++) { + // last character was truncated, so ignore last byte + length--; + break; + } + if (! *src++) { + // last character was truncated, so ignore last two bytes + length -= 2; + break; + } + } + count++; + } + + mByteCount = length + 1; + mBuffer[length] = 0; + mCharCount = count; +} + +void MtpStringBuffer::readFromPacket(MtpDataPacket* packet) { + int count = packet->getUInt8(); + uint8_t* dest = mBuffer; + for (int i = 0; i < count; i++) { + uint16_t ch = packet->getUInt16(); + if (ch >= 0x0800) { + *dest++ = (uint8_t)(0xE0 | (ch >> 12)); + *dest++ = (uint8_t)(0x80 | ((ch >> 6) & 0x3F)); + *dest++ = (uint8_t)(0x80 | (ch & 0x3F)); + } else if (ch >= 0x80) { + *dest++ = (uint8_t)(0xC0 | (ch >> 6)); + *dest++ = (uint8_t)(0x80 | (ch & 0x3F)); + } else { + *dest++ = ch; + } + } + *dest++ = 0; + mCharCount = count; + mByteCount = dest - mBuffer; +} + +void MtpStringBuffer::writeToPacket(MtpDataPacket* packet) const { + int count = mCharCount; + const uint8_t* src = mBuffer; + packet->putUInt8(count > 0 ? count + 1 : 0); + + // expand utf8 to 16 bit chars + for (int i = 0; i < count; i++) { + uint16_t ch; + uint16_t ch1 = *src++; + if ((ch1 & 0x80) == 0) { + // single byte character + ch = ch1; + } else if ((ch1 & 0xE0) == 0xC0) { + // two byte character + uint16_t ch2 = *src++; + ch = ((ch1 & 0x1F) << 6) | (ch2 & 0x3F); + } else { + // three byte character + uint16_t ch2 = *src++; + uint16_t ch3 = *src++; + ch = ((ch1 & 0x0F) << 12) | ((ch2 & 0x3F) << 6) | (ch3 & 0x3F); + } + packet->putUInt16(ch); + } + // only terminate with zero if string is not empty + if (count > 0) + packet->putUInt16(0); +} + +} // namespace android diff --git a/media/mtp/MtpStringBuffer.h b/media/mtp/MtpStringBuffer.h new file mode 100644 index 0000000..4641c3f --- /dev/null +++ b/media/mtp/MtpStringBuffer.h @@ -0,0 +1,54 @@ +/* + * 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_STRING_BUFFER_H +#define _MTP_STRING_BUFFER_H + +#include <stdint.h> + +namespace android { + +class MtpDataPacket; + +// Represents a utf8 string, with a maximum of 255 characters +class MtpStringBuffer { + +private: + // maximum 3 bytes/character, with 1 extra for zero termination + uint8_t mBuffer[255 * 3 + 1]; + int mCharCount; + int mByteCount; + +public: + MtpStringBuffer(); + MtpStringBuffer(const char* src); + MtpStringBuffer(const MtpStringBuffer& src); + virtual ~MtpStringBuffer(); + + void set(const char* src); + + void readFromPacket(MtpDataPacket* packet); + void writeToPacket(MtpDataPacket* packet) const; + + inline int getCharCount() const { return mCharCount; } + inline int getByteCount() const { return mByteCount; } + + inline operator const char*() const { return (const char *)mBuffer; } +}; + +}; // namespace android + +#endif // _MTP_STRING_BUFFER_H diff --git a/media/mtp/MtpTypes.h b/media/mtp/MtpTypes.h new file mode 100644 index 0000000..7e3c009 --- /dev/null +++ b/media/mtp/MtpTypes.h @@ -0,0 +1,92 @@ +/* + * 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_TYPES_H +#define _MTP_TYPES_H + +#include <stdint.h> +#include "utils/String8.h" +#include "utils/Vector.h" + +namespace android { + +typedef int32_t int128_t[4]; +typedef uint32_t uint128_t[4]; + +typedef uint16_t MtpOperationCode; +typedef uint16_t MtpResponseCode; +typedef uint16_t MtpEventCode; +typedef uint32_t MtpSessionID; +typedef uint32_t MtpStorageID; +typedef uint32_t MtpTransactionID; +typedef uint16_t MtpPropertyCode; +typedef uint16_t MtpDataType; +typedef uint16_t MtpObjectFormat; +typedef MtpPropertyCode MtpDeviceProperty; +typedef MtpPropertyCode MtpObjectProperty; + +// object handles are unique across all storage but only within a single session. +// object handles cannot be reused after an object is deleted. +// values 0x00000000 and 0xFFFFFFFF are reserved for special purposes. +typedef uint32_t MtpObjectHandle; + +union MtpPropertyValue { + int8_t i8; + uint8_t u8; + int16_t i16; + uint16_t u16; + int32_t i32; + uint32_t u32; + int64_t i64; + uint64_t u64; + int128_t i128; + uint128_t u128; + char* str; +}; + +// Special values +#define MTP_PARENT_ROOT 0xFFFFFFFF // parent is root of the storage +#define kInvalidObjectHandle 0xFFFFFFFF + +class MtpStorage; +class MtpDevice; +class MtpProperty; + +typedef Vector<MtpStorage *> MtpStorageList; +typedef Vector<MtpDevice*> MtpDeviceList; +typedef Vector<MtpProperty*> MtpPropertyList; + +typedef Vector<uint8_t> UInt8List; +typedef Vector<uint16_t> UInt16List; +typedef Vector<uint32_t> UInt32List; +typedef Vector<uint64_t> UInt64List; +typedef Vector<int8_t> Int8List; +typedef Vector<int16_t> Int16List; +typedef Vector<int32_t> Int32List; +typedef Vector<int64_t> Int64List; + +typedef UInt16List MtpObjectPropertyList; +typedef UInt16List MtpDevicePropertyList; +typedef UInt16List MtpObjectFormatList; +typedef UInt32List MtpObjectHandleList; +typedef UInt16List MtpObjectPropertyList; +typedef UInt32List MtpStorageIDList; + +typedef String8 MtpString; + +}; // namespace android + +#endif // _MTP_TYPES_H diff --git a/media/mtp/MtpUtils.cpp b/media/mtp/MtpUtils.cpp new file mode 100644 index 0000000..ab01ef5 --- /dev/null +++ b/media/mtp/MtpUtils.cpp @@ -0,0 +1,78 @@ +/* + * 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 "MtpUtils" + +#include <stdio.h> +#include <time.h> + +#include <cutils/tztime.h> +#include "MtpUtils.h" + +namespace android { + +/* +DateTime strings follow a compatible subset of the definition found in ISO 8601, and +take the form of a Unicode string formatted as: "YYYYMMDDThhmmss.s". In this +representation, YYYY shall be replaced by the year, MM replaced by the month (01-12), +DD replaced by the day (01-31), T is a constant character 'T' delimiting time from date, +hh is replaced by the hour (00-23), mm is replaced by the minute (00-59), and ss by the +second (00-59). The ".s" is optional, and represents tenths of a second. +*/ + +bool parseDateTime(const char* dateTime, time_t& outSeconds) { + int year, month, day, hour, minute, second; + struct tm tm; + + if (sscanf(dateTime, "%04d%02d%02dT%02d%02d%02d", + &year, &month, &day, &hour, &minute, &second) != 6) + return false; + const char* tail = dateTime + 15; + // skip optional tenth of second + if (tail[0] == '.' && tail[1]) + tail += 2; + //FIXME - support +/-hhmm + bool useUTC = (tail[0] == 'Z'); + + // hack to compute timezone + time_t dummy; + localtime_r(&dummy, &tm); + + tm.tm_sec = second; + tm.tm_min = minute; + tm.tm_hour = hour; + tm.tm_mday = day; + tm.tm_mon = month; + tm.tm_year = year - 1900; + tm.tm_wday = 0; + tm.tm_isdst = -1; + if (useUTC) + outSeconds = mktime(&tm); + else + outSeconds = mktime_tz(&tm, tm.tm_zone); + + return true; +} + +void formatDateTime(time_t seconds, char* buffer, int bufferLength) { + struct tm tm; + + localtime_r(&seconds, &tm); + snprintf(buffer, bufferLength, "%04d%02d%02dT%02d%02d%02d", + tm.tm_year + 1900, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); +} + +} // namespace android diff --git a/media/mtp/MtpUtils.h b/media/mtp/MtpUtils.h new file mode 100644 index 0000000..61f9055 --- /dev/null +++ b/media/mtp/MtpUtils.h @@ -0,0 +1,29 @@ +/* + * 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_UTILS_H +#define _MTP_UTILS_H + +#include <stdint.h> + +namespace android { + +bool parseDateTime(const char* dateTime, time_t& outSeconds); +void formatDateTime(time_t seconds, char* buffer, int bufferLength); + +}; // namespace android + +#endif // _MTP_UTILS_H diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h new file mode 100644 index 0000000..b7afa66 --- /dev/null +++ b/media/mtp/mtp.h @@ -0,0 +1,475 @@ +/* + * 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_H +#define _MTP_H + +#include <stdint.h> +#include <stdlib.h> + +#define MTP_STANDARD_VERSION 100 + +// Container Types +#define MTP_CONTAINER_TYPE_UNDEFINED 0 +#define MTP_CONTAINER_TYPE_COMMAND 1 +#define MTP_CONTAINER_TYPE_DATA 2 +#define MTP_CONTAINER_TYPE_RESPONSE 3 +#define MTP_CONTAINER_TYPE_EVENT 4 + +// Container Offsets +#define MTP_CONTAINER_LENGTH_OFFSET 0 +#define MTP_CONTAINER_TYPE_OFFSET 4 +#define MTP_CONTAINER_CODE_OFFSET 6 +#define MTP_CONTAINER_TRANSACTION_ID_OFFSET 8 +#define MTP_CONTAINER_PARAMETER_OFFSET 12 +#define MTP_CONTAINER_HEADER_SIZE 12 + +// MTP Types +#define MTP_TYPE_UNDEFINED 0x0000 // Undefined +#define MTP_TYPE_INT8 0x0001 // Signed 8-bit integer +#define MTP_TYPE_UINT8 0x0002 // Unsigned 8-bit integer +#define MTP_TYPE_INT16 0x0003 // Signed 16-bit integer +#define MTP_TYPE_UINT16 0x0004 // Unsigned 16-bit integer +#define MTP_TYPE_INT32 0x0005 // Signed 32-bit integer +#define MTP_TYPE_UINT32 0x0006 // Unsigned 32-bit integer +#define MTP_TYPE_INT64 0x0007 // Signed 64-bit integer +#define MTP_TYPE_UINT64 0x0008 // Unsigned 64-bit integer +#define MTP_TYPE_INT128 0x0009 // Signed 128-bit integer +#define MTP_TYPE_UINT128 0x000A // Unsigned 128-bit integer +#define MTP_TYPE_AINT8 0x4001 // Array of signed 8-bit integers +#define MTP_TYPE_AUINT8 0x4002 // Array of unsigned 8-bit integers +#define MTP_TYPE_AINT16 0x4003 // Array of signed 16-bit integers +#define MTP_TYPE_AUINT16 0x4004 // Array of unsigned 16-bit integers +#define MTP_TYPE_AINT32 0x4005 // Array of signed 32-bit integers +#define MTP_TYPE_AUINT32 0x4006 // Array of unsigned 32-bit integers +#define MTP_TYPE_AINT64 0x4007 // Array of signed 64-bit integers +#define MTP_TYPE_AUINT64 0x4008 // Array of unsigned 64-bit integers +#define MTP_TYPE_AINT128 0x4009 // Array of signed 128-bit integers +#define MTP_TYPE_AUINT128 0x400A // Array of unsigned 128-bit integers +#define MTP_TYPE_STR 0xFFFF // Variable-length Unicode string + +// MTP Format Codes +#define MTP_FORMAT_UNDEFINED 0x3000 // Undefined object +#define MTP_FORMAT_ASSOCIATION 0x3001 // Association (for example, a folder) +#define MTP_FORMAT_SCRIPT 0x3002 // Device model-specific script +#define MTP_FORMAT_EXECUTABLE 0x3003 // Device model-specific binary executable +#define MTP_FORMAT_TEXT 0x3004 // Text file +#define MTP_FORMAT_HTML 0x3005 // Hypertext Markup Language file (text) +#define MTP_FORMAT_DPOF 0x3006 // Digital Print Order Format file (text) +#define MTP_FORMAT_AIFF 0x3007 // Audio clip +#define MTP_FORMAT_WAV 0x3008 // Audio clip +#define MTP_FORMAT_MP3 0x3009 // Audio clip +#define MTP_FORMAT_AVI 0x300A // Video clip +#define MTP_FORMAT_MPEG 0x300B // Video clip +#define MTP_FORMAT_ASF 0x300C // Microsoft Advanced Streaming Format (video) +#define MTP_FORMAT_DEFINED 0x3800 // Unknown image object +#define MTP_FORMAT_EXIF_JPEG 0x3801 // Exchangeable File Format, JEIDA standard +#define MTP_FORMAT_TIFF_EP 0x3802 // Tag Image File Format for Electronic Photography +#define MTP_FORMAT_FLASHPIX 0x3803 // Structured Storage Image Format +#define MTP_FORMAT_BMP 0x3804 // Microsoft Windows Bitmap file +#define MTP_FORMAT_CIFF 0x3805 // Canon Camera Image File Format +#define MTP_FORMAT_GIF 0x3807 // Graphics Interchange Format +#define MTP_FORMAT_JFIF 0x3808 // JPEG File Interchange Format +#define MTP_FORMAT_CD 0x3809 // PhotoCD Image Pac +#define MTP_FORMAT_PICT 0x380A // Quickdraw Image Format +#define MTP_FORMAT_PNG 0x380B // Portable Network Graphics +#define MTP_FORMAT_TIFF 0x380D // Tag Image File Format +#define MTP_FORMAT_TIFF_IT 0x380E // Tag Image File Format for Information Technology (graphic arts) +#define MTP_FORMAT_JP2 0x380F // JPEG2000 Baseline File Format +#define MTP_FORMAT_JPX 0x3810 // JPEG2000 Extended File Format +#define MTP_FORMAT_UNDEFINED_FIRMWARE 0xB802 +#define MTP_FORMAT_WINDOWS_IMAGE_FORMAT 0xB881 +#define MTP_FORMAT_UNDEFINED_AUDIO 0xB900 +#define MTP_FORMAT_WMA 0xB901 +#define MTP_FORMAT_OGG 0xB902 +#define MTP_FORMAT_AAC 0xB903 +#define MTP_FORMAT_AUDIBLE 0xB904 +#define MTP_FORMAT_FLAC 0xB906 +#define MTP_FORMAT_UNDEFINED_VIDEO 0xB980 +#define MTP_FORMAT_WMV 0xB981 +#define MTP_FORMAT_MP4_CONTAINER 0xB982 // ISO 14496-1 +#define MTP_FORMAT_MP2 0xB983 +#define MTP_FORMAT_3GP_CONTAINER 0xB984 // 3GPP file format. Details: http://www.3gpp.org/ftp/Specs/html-info/26244.htm (page title - \u201cTransparent end-to-end packet switched streaming service, 3GPP file format\u201d). +#define MTP_FORMAT_UNDEFINED_COLLECTION 0xBA00 +#define MTP_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM 0xBA01 +#define MTP_FORMAT_ABSTRACT_IMAGE_ALBUM 0xBA02 +#define MTP_FORMAT_ABSTRACT_AUDIO_ALBUM 0xBA03 +#define MTP_FORMAT_ABSTRACT_VIDEO_ALBUM 0xBA04 +#define MTP_FORMAT_ABSTRACT_AV_PLAYLIST 0xBA05 +#define MTP_FORMAT_ABSTRACT_CONTACT_GROUP 0xBA06 +#define MTP_FORMAT_ABSTRACT_MESSAGE_FOLDER 0xBA07 +#define MTP_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION 0xBA08 +#define MTP_FORMAT_ABSTRACT_AUDIO_PLAYLIST 0xBA09 +#define MTP_FORMAT_ABSTRACT_VIDEO_PLAYLIST 0xBA0A +#define MTP_FORMAT_ABSTRACT_MEDIACAST 0xBA0B // For use with mediacasts; references multimedia enclosures of RSS feeds or episodic content +#define MTP_FORMAT_WPL_PLAYLIST 0xBA10 +#define MTP_FORMAT_M3U_PLAYLIST 0xBA11 +#define MTP_FORMAT_MPL_PLAYLIST 0xBA12 +#define MTP_FORMAT_ASX_PLAYLIST 0xBA13 +#define MTP_FORMAT_PLS_PLAYLIST 0xBA14 +#define MTP_FORMAT_UNDEFINED_DOCUMENT 0xBA80 +#define MTP_FORMAT_ABSTRACT_DOCUMENT 0xBA81 +#define MTP_FORMAT_XML_DOCUMENT 0xBA82 +#define MTP_FORMAT_MS_WORD_DOCUMENT 0xBA83 +#define MTP_FORMAT_MHT_COMPILED_HTML_DOCUMENT 0xBA84 +#define MTP_FORMAT_MS_EXCEL_SPREADSHEET 0xBA85 +#define MTP_FORMAT_MS_POWERPOINT_PRESENTATION 0xBA86 +#define MTP_FORMAT_UNDEFINED_MESSAGE 0xBB00 +#define MTP_FORMAT_ABSTRACT_MESSSAGE 0xBB01 +#define MTP_FORMAT_UNDEFINED_CONTACT 0xBB80 +#define MTP_FORMAT_ABSTRACT_CONTACT 0xBB81 +#define MTP_FORMAT_VCARD_2 0xBB82 + +// MTP Object Property Codes +#define MTP_PROPERTY_STORAGE_ID 0xDC01 +#define MTP_PROPERTY_OBJECT_FORMAT 0xDC02 +#define MTP_PROPERTY_PROTECTION_STATUS 0xDC03 +#define MTP_PROPERTY_OBJECT_SIZE 0xDC04 +#define MTP_PROPERTY_ASSOCIATION_TYPE 0xDC05 +#define MTP_PROPERTY_ASSOCIATION_DESC 0xDC06 +#define MTP_PROPERTY_OBJECT_FILE_NAME 0xDC07 +#define MTP_PROPERTY_DATE_CREATED 0xDC08 +#define MTP_PROPERTY_DATE_MODIFIED 0xDC09 +#define MTP_PROPERTY_KEYWORDS 0xDC0A +#define MTP_PROPERTY_PARENT_OBJECT 0xDC0B +#define MTP_PROPERTY_ALLOWED_FOLDER_CONTENTS 0xDC0C +#define MTP_PROPERTY_HIDDEN 0xDC0D +#define MTP_PROPERTY_SYSTEM_OBJECT 0xDC0E +#define MTP_PROPERTY_PERSISTENT_UID 0xDC41 +#define MTP_PROPERTY_SYNC_ID 0xDC42 +#define MTP_PROPERTY_PROPERTY_BAG 0xDC43 +#define MTP_PROPERTY_NAME 0xDC44 +#define MTP_PROPERTY_CREATED_BY 0xDC45 +#define MTP_PROPERTY_ARTIST 0xDC46 +#define MTP_PROPERTY_DATE_AUTHORED 0xDC47 +#define MTP_PROPERTY_DESCRIPTION 0xDC48 +#define MTP_PROPERTY_URL_REFERENCE 0xDC49 +#define MTP_PROPERTY_LANGUAGE_LOCALE 0xDC4A +#define MTP_PROPERTY_COPYRIGHT_INFORMATION 0xDC4B +#define MTP_PROPERTY_SOURCE 0xDC4C +#define MTP_PROPERTY_ORIGIN_LOCATION 0xDC4D +#define MTP_PROPERTY_DATE_ADDED 0xDC4E +#define MTP_PROPERTY_NON_CONSUMABLE 0xDC4F +#define MTP_PROPERTY_CORRUPT_UNPLAYABLE 0xDC50 +#define MTP_PROPERTY_PRODUCER_SERIAL_NUMBER 0xDC51 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_FORMAT 0xDC81 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_SIZE 0xDC82 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_HEIGHT 0xDC83 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_WIDTH 0xDC84 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DURATION 0xDC85 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DATA 0xDC86 +#define MTP_PROPERTY_WIDTH 0xDC87 +#define MTP_PROPERTY_HEIGHT 0xDC88 +#define MTP_PROPERTY_DURATION 0xDC89 +#define MTP_PROPERTY_RATING 0xDC8A +#define MTP_PROPERTY_TRACK 0xDC8B +#define MTP_PROPERTY_GENRE 0xDC8C +#define MTP_PROPERTY_CREDITS 0xDC8D +#define MTP_PROPERTY_LYRICS 0xDC8E +#define MTP_PROPERTY_SUBSCRIPTION_CONTENT_ID 0xDC8F +#define MTP_PROPERTY_PRODUCED_BY 0xDC90 +#define MTP_PROPERTY_USE_COUNT 0xDC91 +#define MTP_PROPERTY_SKIP_COUNT 0xDC92 +#define MTP_PROPERTY_LAST_ACCESSED 0xDC93 +#define MTP_PROPERTY_PARENTAL_RATING 0xDC94 +#define MTP_PROPERTY_META_GENRE 0xDC95 +#define MTP_PROPERTY_COMPOSER 0xDC96 +#define MTP_PROPERTY_EFFECTIVE_RATING 0xDC97 +#define MTP_PROPERTY_SUBTITLE 0xDC98 +#define MTP_PROPERTY_ORIGINAL_RELEASE_DATE 0xDC99 +#define MTP_PROPERTY_ALBUM_NAME 0xDC9A +#define MTP_PROPERTY_ALBUM_ARTIST 0xDC9B +#define MTP_PROPERTY_MOOD 0xDC9C +#define MTP_PROPERTY_DRM_STATUS 0xDC9D +#define MTP_PROPERTY_SUB_DESCRIPTION 0xDC9E +#define MTP_PROPERTY_IS_CROPPED 0xDCD1 +#define MTP_PROPERTY_IS_COLOUR_CORRECTED 0xDCD2 +#define MTP_PROPERTY_IMAGE_BIT_DEPTH 0xDCD3 +#define MTP_PROPERTY_F_NUMBER 0xDCD4 +#define MTP_PROPERTY_EXPOSURE_TIME 0xDCD5 +#define MTP_PROPERTY_EXPOSURE_INDEX 0xDCD6 +#define MTP_PROPERTY_TOTAL_BITRATE 0xDE91 +#define MTP_PROPERTY_BITRATE_TYPE 0xDE92 +#define MTP_PROPERTY_SAMPLE_RATE 0xDE93 +#define MTP_PROPERTY_NUMBER_OF_CHANNELS 0xDE94 +#define MTP_PROPERTY_AUDIO_BIT_DEPTH 0xDE95 +#define MTP_PROPERTY_SCAN_TYPE 0xDE97 +#define MTP_PROPERTY_AUDIO_WAVE_CODEC 0xDE99 +#define MTP_PROPERTY_AUDIO_BITRATE 0xDE9A +#define MTP_PROPERTY_VIDEO_FOURCC_CODEC 0xDE9B +#define MTP_PROPERTY_VIDEO_BITRATE 0xDE9C +#define MTP_PROPERTY_FRAMES_PER_THOUSAND_SECONDS 0xDE9D +#define MTP_PROPERTY_KEYFRAME_DISTANCE 0xDE9E +#define MTP_PROPERTY_BUFFER_SIZE 0xDE9F +#define MTP_PROPERTY_ENCODING_QUALITY 0xDEA0 +#define MTP_PROPERTY_ENCODING_PROFILE 0xDEA1 +#define MTP_PROPERTY_DISPLAY_NAME 0xDCE0 +#define MTP_PROPERTY_BODY_TEXT 0xDCE1 +#define MTP_PROPERTY_SUBJECT 0xDCE2 +#define MTP_PROPERTY_PRIORITY 0xDCE3 +#define MTP_PROPERTY_GIVEN_NAME 0xDD00 +#define MTP_PROPERTY_MIDDLE_NAMES 0xDD01 +#define MTP_PROPERTY_FAMILY_NAME 0xDD02 +#define MTP_PROPERTY_PREFIX 0xDD03 +#define MTP_PROPERTY_SUFFIX 0xDD04 +#define MTP_PROPERTY_PHONETIC_GIVEN_NAME 0xDD05 +#define MTP_PROPERTY_PHONETIC_FAMILY_NAME 0xDD06 +#define MTP_PROPERTY_EMAIL_PRIMARY 0xDD07 +#define MTP_PROPERTY_EMAIL_PERSONAL_1 0xDD08 +#define MTP_PROPERTY_EMAIL_PERSONAL_2 0xDD09 +#define MTP_PROPERTY_EMAIL_BUSINESS_1 0xDD0A +#define MTP_PROPERTY_EMAIL_BUSINESS_2 0xDD0B +#define MTP_PROPERTY_EMAIL_OTHERS 0xDD0C +#define MTP_PROPERTY_PHONE_NUMBER_PRIMARY 0xDD0D +#define MTP_PROPERTY_PHONE_NUMBER_PERSONAL 0xDD0E +#define MTP_PROPERTY_PHONE_NUMBER_PERSONAL_2 0xDD0F +#define MTP_PROPERTY_PHONE_NUMBER_BUSINESS 0xDD10 +#define MTP_PROPERTY_PHONE_NUMBER_BUSINESS_2 0xDD11 +#define MTP_PROPERTY_PHONE_NUMBER_MOBILE 0xDD12 +#define MTP_PROPERTY_PHONE_NUMBER_MOBILE_2 0xDD13 +#define MTP_PROPERTY_FAX_NUMBER_PRIMARY 0xDD14 +#define MTP_PROPERTY_FAX_NUMBER_PERSONAL 0xDD15 +#define MTP_PROPERTY_FAX_NUMBER_BUSINESS 0xDD16 +#define MTP_PROPERTY_PAGER_NUMBER 0xDD17 +#define MTP_PROPERTY_PHONE_NUMBER_OTHERS 0xDD18 +#define MTP_PROPERTY_PRIMARY_WEB_ADDRESS 0xDD19 +#define MTP_PROPERTY_PERSONAL_WEB_ADDRESS 0xDD1A +#define MTP_PROPERTY_BUSINESS_WEB_ADDRESS 0xDD1B +#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS 0xDD1C +#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_2 0xDD1D +#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_3 0xDD1E +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_FULL 0xDD1F +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_1 0xDD20 +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_2 0xDD21 +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_CITY 0xDD22 +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_REGION 0xDD23 +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_POSTAL_CODE 0xDD24 +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_COUNTRY 0xDD25 +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_FULL 0xDD26 +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_1 0xDD27 +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_2 0xDD28 +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_CITY 0xDD29 +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_REGION 0xDD2A +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_POSTAL_CODE 0xDD2B +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_COUNTRY 0xDD2C +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_FULL 0xDD2D +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_1 0xDD2E +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_2 0xDD2F +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_CITY 0xDD30 +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_REGION 0xDD31 +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_POSTAL_CODE 0xDD32 +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_COUNTRY 0xDD33 +#define MTP_PROPERTY_ORGANIZATION_NAME 0xDD34 +#define MTP_PROPERTY_PHONETIC_ORGANIZATION_NAME 0xDD35 +#define MTP_PROPERTY_ROLE 0xDD36 +#define MTP_PROPERTY_BIRTHDATE 0xDD37 +#define MTP_PROPERTY_MESSAGE_TO 0xDD40 +#define MTP_PROPERTY_MESSAGE_CC 0xDD41 +#define MTP_PROPERTY_MESSAGE_BCC 0xDD42 +#define MTP_PROPERTY_MESSAGE_READ 0xDD43 +#define MTP_PROPERTY_MESSAGE_RECEIVED_TIME 0xDD44 +#define MTP_PROPERTY_MESSAGE_SENDER 0xDD45 +#define MTP_PROPERTY_ACTIVITY_BEGIN_TIME 0xDD50 +#define MTP_PROPERTY_ACTIVITY_END_TIME 0xDD51 +#define MTP_PROPERTY_ACTIVITY_LOCATION 0xDD52 +#define MTP_PROPERTY_ACTIVITY_REQUIRED_ATTENDEES 0xDD54 +#define MTP_PROPERTY_ACTIVITY_OPTIONAL_ATTENDEES 0xDD55 +#define MTP_PROPERTY_ACTIVITY_RESOURCES 0xDD56 +#define MTP_PROPERTY_ACTIVITY_ACCEPTED 0xDD57 +#define MTP_PROPERTY_ACTIVITY_TENTATIVE 0xDD58 +#define MTP_PROPERTY_ACTIVITY_DECLINED 0xDD59 +#define MTP_PROPERTY_ACTIVITY_REMAINDER_TIME 0xDD5A +#define MTP_PROPERTY_ACTIVITY_OWNER 0xDD5B +#define MTP_PROPERTY_ACTIVITY_STATUS 0xDD5C +#define MTP_PROPERTY_OWNER 0xDD5D +#define MTP_PROPERTY_EDITOR 0xDD5E +#define MTP_PROPERTY_WEBMASTER 0xDD5F +#define MTP_PROPERTY_URL_SOURCE 0xDD60 +#define MTP_PROPERTY_URL_DESTINATION 0xDD61 +#define MTP_PROPERTY_TIME_BOOKMARK 0xDD62 +#define MTP_PROPERTY_OBJECT_BOOKMARK 0xDD63 +#define MTP_PROPERTY_BYTE_BOOKMARK 0xDD64 +#define MTP_PROPERTY_LAST_BUILD_DATE 0xDD70 +#define MTP_PROPERTY_TIME_TO_LIVE 0xDD71 +#define MTP_PROPERTY_MEDIA_GUID 0xDD72 + +// MTP Device Property Codes +#define MTP_DEVICE_PROPERTY_UNDEFINED 0x5000 +#define MTP_DEVICE_PROPERTY_BATTERY_LEVEL 0x5001 +#define MTP_DEVICE_PROPERTY_FUNCTIONAL_MODE 0x5002 +#define MTP_DEVICE_PROPERTY_IMAGE_SIZE 0x5003 +#define MTP_DEVICE_PROPERTY_COMPRESSION_SETTING 0x5004 +#define MTP_DEVICE_PROPERTY_WHITE_BALANCE 0x5005 +#define MTP_DEVICE_PROPERTY_RGB_GAIN 0x5006 +#define MTP_DEVICE_PROPERTY_F_NUMBER 0x5007 +#define MTP_DEVICE_PROPERTY_FOCAL_LENGTH 0x5008 +#define MTP_DEVICE_PROPERTY_FOCUS_DISTANCE 0x5009 +#define MTP_DEVICE_PROPERTY_FOCUS_MODE 0x500A +#define MTP_DEVICE_PROPERTY_EXPOSURE_METERING_MODE 0x500B +#define MTP_DEVICE_PROPERTY_FLASH_MODE 0x500C +#define MTP_DEVICE_PROPERTY_EXPOSURE_TIME 0x500D +#define MTP_DEVICE_PROPERTY_EXPOSURE_PROGRAM_MODE 0x500E +#define MTP_DEVICE_PROPERTY_EXPOSURE_INDEX 0x500F +#define MTP_DEVICE_PROPERTY_EXPOSURE_BIAS_COMPENSATION 0x5010 +#define MTP_DEVICE_PROPERTY_DATETIME 0x5011 +#define MTP_DEVICE_PROPERTY_CAPTURE_DELAY 0x5012 +#define MTP_DEVICE_PROPERTY_STILL_CAPTURE_MODE 0x5013 +#define MTP_DEVICE_PROPERTY_CONTRAST 0x5014 +#define MTP_DEVICE_PROPERTY_SHARPNESS 0x5015 +#define MTP_DEVICE_PROPERTY_DIGITAL_ZOOM 0x5016 +#define MTP_DEVICE_PROPERTY_EFFECT_MODE 0x5017 +#define MTP_DEVICE_PROPERTY_BURST_NUMBER 0x5018 +#define MTP_DEVICE_PROPERTY_BURST_INTERVAL 0x5019 +#define MTP_DEVICE_PROPERTY_TIMELAPSE_NUMBER 0x501A +#define MTP_DEVICE_PROPERTY_TIMELAPSE_INTERVAL 0x501B +#define MTP_DEVICE_PROPERTY_FOCUS_METERING_MODE 0x501C +#define MTP_DEVICE_PROPERTY_UPLOAD_URL 0x501D +#define MTP_DEVICE_PROPERTY_ARTIST 0x501E +#define MTP_DEVICE_PROPERTY_COPYRIGHT_INFO 0x501F +#define MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER 0xD401 +#define MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME 0xD402 +#define MTP_DEVICE_PROPERTY_VOLUME 0xD403 +#define MTP_DEVICE_PROPERTY_SUPPORTED_FORMATS_ORDERED 0xD404 +#define MTP_DEVICE_PROPERTY_DEVICE_ICON 0xD405 +#define MTP_DEVICE_PROPERTY_PLAYBACK_RATE 0xD410 +#define MTP_DEVICE_PROPERTY_PLAYBACK_OBJECT 0xD411 +#define MTP_DEVICE_PROPERTY_PLAYBACK_CONTAINER_INDEX 0xD412 +#define MTP_DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO 0xD406 +#define MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE 0xD407 + +// MTP Operation Codes +#define MTP_OPERATION_GET_DEVICE_INFO 0x1001 +#define MTP_OPERATION_OPEN_SESSION 0x1002 +#define MTP_OPERATION_CLOSE_SESSION 0x1003 +#define MTP_OPERATION_GET_STORAGE_IDS 0x1004 +#define MTP_OPERATION_GET_STORAGE_INFO 0x1005 +#define MTP_OPERATION_GET_NUM_OBJECTS 0x1006 +#define MTP_OPERATION_GET_OBJECT_HANDLES 0x1007 +#define MTP_OPERATION_GET_OBJECT_INFO 0x1008 +#define MTP_OPERATION_GET_OBJECT 0x1009 +#define MTP_OPERATION_GET_THUMB 0x100A +#define MTP_OPERATION_DELETE_OBJECT 0x100B +#define MTP_OPERATION_SEND_OBJECT_INFO 0x100C +#define MTP_OPERATION_SEND_OBJECT 0x100D +#define MTP_OPERATION_INITIATE_CAPTURE 0x100E +#define MTP_OPERATION_FORMAT_STORE 0x100F +#define MTP_OPERATION_RESET_DEVICE 0x1010 +#define MTP_OPERATION_SELF_TEST 0x1011 +#define MTP_OPERATION_SET_OBJECT_PROTECTION 0x1012 +#define MTP_OPERATION_POWER_DOWN 0x1013 +#define MTP_OPERATION_GET_DEVICE_PROP_DESC 0x1014 +#define MTP_OPERATION_GET_DEVICE_PROP_VALUE 0x1015 +#define MTP_OPERATION_SET_DEVICE_PROP_VALUE 0x1016 +#define MTP_OPERATION_RESET_DEVICE_PROP_VALUE 0x1017 +#define MTP_OPERATION_TERMINATE_OPEN_CAPTURE 0x1018 +#define MTP_OPERATION_MOVE_OBJECT 0x1019 +#define MTP_OPERATION_COPY_OBJECT 0x101A +#define MTP_OPERATION_GET_PARTIAL_OBJECT 0x101B +#define MTP_OPERATION_INITIATE_OPEN_CAPTURE 0x101C +#define MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED 0x9801 +#define MTP_OPERATION_GET_OBJECT_PROP_DESC 0x9802 +#define MTP_OPERATION_GET_OBJECT_PROP_VALUE 0x9803 +#define MTP_OPERATION_SET_OBJECT_PROP_VALUE 0x9804 +#define MTP_OPERATION_GET_OBJECT_REFERENCES 0x9810 +#define MTP_OPERATION_SET_OBJECT_REFERENCES 0x9811 +#define MTP_OPERATION_SKIP 0x9820 + +// MTP Response Codes +#define MTP_RESPONSE_UNDEFINED 0x2000 +#define MTP_RESPONSE_OK 0x2001 +#define MTP_RESPONSE_GENERAL_ERROR 0x2002 +#define MTP_RESPONSE_SESSION_NOT_OPEN 0x2003 +#define MTP_RESPONSE_INVALID_TRANSACTION_ID 0x2004 +#define MTP_RESPONSE_OPERATION_NOT_SUPPORTED 0x2005 +#define MTP_RESPONSE_PARAMETER_NOT_SUPPORTED 0x2006 +#define MTP_RESPONSE_INCOMPLETE_TRANSFER 0x2007 +#define MTP_RESPONSE_INVALID_STORAGE_ID 0x2008 +#define MTP_RESPONSE_INVALID_OBJECT_HANDLE 0x2009 +#define MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED 0x200A +#define MTP_RESPONSE_INVALID_OBJECT_FORMAT_CODE 0x200B +#define MTP_RESPONSE_STORAGE_FULL 0x200C +#define MTP_RESPONSE_OBJECT_WRITE_PROTECTED 0x200D +#define MTP_RESPONSE_STORE_READ_ONLY 0x200E +#define MTP_RESPONSE_ACCESS_DENIED 0x200F +#define MTP_RESPONSE_NO_THUMBNAIL_PRESENT 0x2010 +#define MTP_RESPONSE_SELF_TEST_FAILED 0x2011 +#define MTP_RESPONSE_PARTIAL_DELETION 0x2012 +#define MTP_RESPONSE_STORE_NOT_AVAILABLE 0x2013 +#define MTP_RESPONSE_SPECIFICATION_BY_FORMAT_UNSUPPORTED 0x2014 +#define MTP_RESPONSE_NO_VALID_OBJECT_INFO 0x2015 +#define MTP_RESPONSE_INVALID_CODE_FORMAT 0x2016 +#define MTP_RESPONSE_UNKNOWN_VENDOR_CODE 0x2017 +#define MTP_RESPONSE_CAPTURE_ALREADY_TERMINATED 0x2018 +#define MTP_RESPONSE_DEVICE_BUSY 0x2019 +#define MTP_RESPONSE_INVALID_PARENT_OBJECT 0x201A +#define MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT 0x201B +#define MTP_RESPONSE_INVALID_DEVICE_PROP_VALUE 0x201C +#define MTP_RESPONSE_INVALID_PARAMETER 0x201D +#define MTP_RESPONSE_SESSION_ALREADY_OPEN 0x201E +#define MTP_RESPONSE_TRANSACTION_CANCELLED 0x201F +#define MTP_RESPONSE_SPECIFICATION_OF_DESTINATION_UNSUPPORTED 0x2020 +#define MTP_RESPONSE_INVALID_OBJECT_PROP_CODE 0xA801 +#define MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT 0xA802 +#define MTP_RESPONSE_INVALID_OBJECT_PROP_VALUE 0xA803 +#define MTP_RESPONSE_INVALID_OBJECT_REFERENCE 0xA804 +#define MTP_RESPONSE_GROUP_NOT_SUPPORTED 0xA805 +#define MTP_RESPONSE_INVALID_DATASET 0xA806 +#define MTP_RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED 0xA807 +#define MTP_RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED 0xA808 +#define MTP_RESPONSE_OBJECT_TOO_LARGE 0xA809 +#define MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED 0xA80A + +// MTP Event Codes +#define MTP_EVENT_UNDEFINED 0x4000 +#define MTP_EVENT_CANCEL_TRANSACTION 0x4001 +#define MTP_EVENT_OBJECT_ADDED 0x4002 +#define MTP_EVENT_OBJECT_REMOVED 0x4003 +#define MTP_EVENT_STORE_ADDED 0x4004 +#define MTP_EVENT_STORE_REMOVED 0x4005 +#define MTP_EVENT_DEVICE_PROP_CHANGED 0x4006 +#define MTP_EVENT_OBJECT_INFO_CHANGED 0x4007 +#define MTP_EVENT_DEVICE_INFO_CHANGED 0x4008 +#define MTP_EVENT_REQUEST_OBJECT_TRANSFER 0x4009 +#define MTP_EVENT_STORE_FULL 0x400A +#define MTP_EVENT_DEVICE_RESET 0x400B +#define MTP_EVENT_STORAGE_INFO_CHANGED 0x400C +#define MTP_EVENT_CAPTURE_COMPLETE 0x400D +#define MTP_EVENT_UNREPORTED_STATUS 0x400E +#define MTP_EVENT_OBJECT_PROP_CHANGED 0xC801 +#define MTP_EVENT_OBJECT_PROP_DESC_CHANGED 0xC802 +#define MTP_EVENT_OBJECT_REFERENCES_CHANGED 0xC803 + +// Storage Type +#define MTP_STORAGE_FIXED_ROM 0x0001 +#define MTP_STORAGE_REMOVABLE_ROM 0x0002 +#define MTP_STORAGE_FIXED_RAM 0x0003 +#define MTP_STORAGE_REMOVABLE_RAM 0x0004 + +// Storage File System +#define MTP_STORAGE_FILESYSTEM_FLAT 0x0001 +#define MTP_STORAGE_FILESYSTEM_HIERARCHICAL 0x0002 +#define MTP_STORAGE_FILESYSTEM_DCF 0x0003 + +// Storage Access Capability +#define MTP_STORAGE_READ_WRITE 0x0000 +#define MTP_STORAGE_READ_ONLY_WITHOUT_DELETE 0x0001 +#define MTP_STORAGE_READ_ONLY_WITH_DELETE 0x0002 + +// Association Type +#define MTP_ASSOCIATION_TYPE_UNDEFINED 0x0000 +#define MTP_ASSOCIATION_TYPE_GENERIC_FOLDER 0x0001 + +#endif // _MTP_H diff --git a/media/tests/CameraBrowser/Android.mk b/media/tests/CameraBrowser/Android.mk new file mode 100644 index 0000000..33d2976 --- /dev/null +++ b/media/tests/CameraBrowser/Android.mk @@ -0,0 +1,8 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +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..eae0b01 --- /dev/null +++ b/media/tests/CameraBrowser/AndroidManifest.xml @@ -0,0 +1,28 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.camerabrowser"> + + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + + <application android:label="@string/app_label"> + <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_CAMERA_ATTACHED" /> + <data android:scheme="content"/> + </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..c7fd830 --- /dev/null +++ b/media/tests/CameraBrowser/res/layout/object_info.xml @@ -0,0 +1,147 @@ +<?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/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> +</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/menu/object_menu.xml b/media/tests/CameraBrowser/res/menu/object_menu.xml new file mode 100644 index 0000000..a0865f0 --- /dev/null +++ b/media/tests/CameraBrowser/res/menu/object_menu.xml @@ -0,0 +1,23 @@ +<?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. +--> + +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:id="@+id/save" + android:title="@string/save_item" /> + <item android:id="@+id/delete" + android:title="@string/delete_item" /> +</menu> diff --git a/media/tests/CameraBrowser/res/values/strings.xml b/media/tests/CameraBrowser/res/values/strings.xml new file mode 100644 index 0000000..56c5111 --- /dev/null +++ b/media/tests/CameraBrowser/res/values/strings.xml @@ -0,0 +1,44 @@ +<?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="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> + + <!-- menu items --> + <string name="save_item">Save</string> + <string name="delete_item">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> + +</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..c04873a --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java @@ -0,0 +1,103 @@ +/* + * 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.Intent; +import android.database.ContentObserver; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Mtp; +import android.util.Log; +import android.view.View; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; + + /** + * A list view displaying all connected cameras. + */ +public class CameraBrowser extends ListActivity { + + private static final String TAG = "CameraBrowser"; + + private ListAdapter mAdapter; + private ContentResolver mResolver; + private DeviceObserver mDeviceObserver; + private Cursor mCursor; + + private class DeviceObserver extends ContentObserver { + DeviceObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + Log.d(TAG, "DeviceObserver.onChange"); + if (mCursor != null) { + mCursor.requery(); + } + } + } + + private static final String[] DEVICE_COLUMNS = + new String[] { Mtp.Device._ID, Mtp.Device.MANUFACTURER, Mtp.Device.MODEL }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mResolver = getContentResolver(); + mDeviceObserver = new DeviceObserver(new Handler()); + } + + @Override + protected void onResume() { + super.onResume(); + + Cursor c = getContentResolver().query(Mtp.Device.CONTENT_URI, + DEVICE_COLUMNS, null, null, null); + Log.d(TAG, "query returned " + c); + startManagingCursor(c); + mCursor = c; + + // Map Cursor columns to views defined in simple_list_item_2.xml + mAdapter = new SimpleCursorAdapter(this, + android.R.layout.simple_list_item_2, c, + new String[] { Mtp.Device.MANUFACTURER, Mtp.Device.MODEL }, + new int[] { android.R.id.text1, android.R.id.text2 }); + setListAdapter(mAdapter); + + // register for changes to the device list + mResolver.registerContentObserver(Mtp.Device.CONTENT_URI, true, mDeviceObserver); + } + + @Override + protected void onPause() { + super.onPause(); + mResolver.unregisterContentObserver(mDeviceObserver); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Intent intent = new Intent(this, StorageBrowser.class); + intent.putExtra("device", (int)mAdapter.getItemId(position)); + startActivity(intent); + } +} 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..a002028 --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java @@ -0,0 +1,146 @@ +/* + * 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.net.Uri; +import android.os.Bundle; +import android.provider.Mtp; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.ResourceCursorAdapter; +import android.widget.TextView; + + /** + * 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 Cursor mCursor; + private ObjectCursorAdapter mAdapter; + private int mDeviceID; + private int mStorageID; + private int mObjectID; + + private static final String[] OBJECT_COLUMNS = + new String[] { Mtp.Object._ID, Mtp.Object.NAME, Mtp.Object.FORMAT, Mtp.Object.THUMB }; + + static final int ID_COLUMN = 0; + static final int NAME_COLUMN = 1; + static final int FORMAT_COLUMN = 2; + static final int THUMB_COLUMN = 3; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + protected void onResume() { + super.onResume(); + + mDeviceID = getIntent().getIntExtra("device", 0); + mStorageID = getIntent().getIntExtra("storage", 0); + mObjectID = getIntent().getIntExtra("object", 0); + if (mDeviceID != 0 && mStorageID != 0) { + Cursor c; + Uri uri; + if (mObjectID == 0) { + uri = Mtp.Object.getContentUriForStorageChildren(mDeviceID, mStorageID); + } else { + uri = Mtp.Object.getContentUriForObjectChildren(mDeviceID, mObjectID); + } + Log.d(TAG, "query " + uri); + c = getContentResolver().query(uri, OBJECT_COLUMNS, null, null, null); + startManagingCursor(c); + mCursor = c; + + // Map Cursor columns to views defined in simple_list_item_1.xml + mAdapter = new ObjectCursorAdapter(this, c); + setListAdapter(mAdapter); + } + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + int rowID = (int)mAdapter.getItemId(position); + Cursor c = getContentResolver().query( + Mtp.Object.getContentUri(mDeviceID, rowID), + OBJECT_COLUMNS, null, null, null); + Log.d(TAG, "query returned " + c + " count: " + c.getCount()); + long format = 0; + if (c != null && c.getCount() == 1) { + c.moveToFirst(); + long rowId = c.getLong(ID_COLUMN); + String name = c.getString(NAME_COLUMN); + format = c.getLong(FORMAT_COLUMN); + Log.d(TAG, "rowId: " + rowId + " name: " + name + " format: " + format); + } + if (format == Mtp.Object.FORMAT_JFIF) { + Intent intent = new Intent(this, ObjectViewer.class); + intent.putExtra("device", mDeviceID); + intent.putExtra("storage", mStorageID); + intent.putExtra("object",rowID); + startActivity(intent); + } else { + Intent intent = new Intent(this, ObjectBrowser.class); + intent.putExtra("device", mDeviceID); + intent.putExtra("storage", mStorageID); + intent.putExtra("object", rowID); + startActivity(intent); + } + } + + private class ObjectCursorAdapter extends ResourceCursorAdapter { + + public ObjectCursorAdapter(Context context, Cursor c) { + super(context, R.layout.object_list, c); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + ImageView thumbView = (ImageView)view.findViewById(R.id.thumbnail); + TextView nameView = (TextView)view.findViewById(R.id.name); + + // get the thumbnail + byte[] thumbnail = cursor.getBlob(THUMB_COLUMN); + if (thumbnail != null) { + Bitmap bitmap = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length); + if (bitmap != null) { + thumbView.setImageBitmap(bitmap); + } + } + + // get the name + String name = cursor.getString(NAME_COLUMN); + if (name == null) { + name = ""; + } + nameView.setText(name); + } + } +} 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..aa49cd8 --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java @@ -0,0 +1,252 @@ +/* + * 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.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.FileUtils; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.provider.Mtp; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.Calendar; +import java.util.Date; + +/** + * A view to display the properties of an object. + */ +public class ObjectViewer extends Activity { + + private static final String TAG = "ObjectViewer"; + + private int mDeviceID; + private int mStorageID; + private int mObjectID; + + private static final String[] OBJECT_COLUMNS = + new String[] { Mtp.Object._ID, + Mtp.Object.NAME, + Mtp.Object.SIZE, + Mtp.Object.THUMB_WIDTH, + Mtp.Object.THUMB_HEIGHT, + Mtp.Object.THUMB_SIZE, + Mtp.Object.IMAGE_WIDTH, + Mtp.Object.IMAGE_HEIGHT, + Mtp.Object.IMAGE_DEPTH, + Mtp.Object.SEQUENCE_NUMBER, + Mtp.Object.DATE_CREATED, + Mtp.Object.DATE_MODIFIED, + Mtp.Object.KEYWORDS, + Mtp.Object.THUMB, + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.object_info); + } + + @Override + protected void onResume() { + super.onResume(); + + mDeviceID = getIntent().getIntExtra("device", 0); + mStorageID = getIntent().getIntExtra("storage", 0); + mObjectID = getIntent().getIntExtra("object", 0); + + if (mDeviceID != 0 && mObjectID != 0) { + Cursor c = getContentResolver().query( + Mtp.Object.getContentUri(mDeviceID, mObjectID), + OBJECT_COLUMNS, null, null, null); + c.moveToFirst(); + TextView view = (TextView)findViewById(R.id.name); + view.setText(c.getString(1)); + view = (TextView)findViewById(R.id.size); + view.setText(Long.toString(c.getLong(2))); + view = (TextView)findViewById(R.id.thumb_width); + view.setText(Long.toString(c.getLong(3))); + view = (TextView)findViewById(R.id.thumb_height); + view.setText(Long.toString(c.getLong(4))); + view = (TextView)findViewById(R.id.thumb_size); + view.setText(Long.toString(c.getLong(5))); + view = (TextView)findViewById(R.id.width); + view.setText(Long.toString(c.getLong(6))); + view = (TextView)findViewById(R.id.height); + view.setText(Long.toString(c.getLong(7))); + view = (TextView)findViewById(R.id.depth); + view.setText(Long.toString(c.getLong(8))); + view = (TextView)findViewById(R.id.sequence); + view.setText(Long.toString(c.getLong(9))); + view = (TextView)findViewById(R.id.created); + Date date = new Date(c.getLong(10) * 1000); + view.setText(date.toString()); + view = (TextView)findViewById(R.id.modified); + date = new Date(c.getLong(11) * 1000); + view.setText(date.toString()); + view = (TextView)findViewById(R.id.keywords); + view.setText(c.getString(12)); + byte[] thumbnail = c.getBlob(13); + if (thumbnail != null) { + ImageView thumbView = (ImageView)findViewById(R.id.thumbnail); + Bitmap bitmap = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length); + if (bitmap != null) { + thumbView.setImageBitmap(bitmap); + } + } + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.object_menu, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem item = menu.findItem(R.id.save); + item.setEnabled(true); + item = menu.findItem(R.id.delete); + item.setEnabled(true); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.save: + save(); + return true; + case R.id.delete: + delete(); + return true; + } + return false; + } + + private static String getTimestamp() { + Calendar c = Calendar.getInstance(); + c.setTimeInMillis(System.currentTimeMillis()); + return String.format("%tY-%tm-%td-%tH-%tM-%tS", c, c, c, c, c, c); + } + + private void save() { + boolean success = false; + Uri uri = Mtp.Object.getContentUri(mDeviceID, mObjectID); + File destFile = null; + ParcelFileDescriptor pfd = null; + FileInputStream fis = null; + FileOutputStream fos = null; + + try { + pfd = getContentResolver().openFileDescriptor(uri, "r"); + Log.d(TAG, "save got pfd " + pfd); + if (pfd != null) { + fis = new FileInputStream(pfd.getFileDescriptor()); + Log.d(TAG, "save got fis " + fis); + File destDir = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DCIM); + destDir.mkdirs(); + destFile = new File(destDir, "CameraBrowser-" + getTimestamp() + ".jpeg"); + + + Log.d(TAG, "save got destFile " + destFile); + + if (destFile.exists()) { + destFile.delete(); + } + fos = new FileOutputStream(destFile); + + byte[] buffer = new byte[65536]; + int bytesRead; + while ((bytesRead = fis.read(buffer)) >= 0) { + fos.write(buffer, 0, bytesRead); + } + + // temporary workaround until we straighten out permissions in /data/media + // 1015 is AID_SDCARD_RW + FileUtils.setPermissions(destDir.getPath(), 0775, Process.myUid(), 1015); + FileUtils.setPermissions(destFile.getPath(), 0664, Process.myUid(), 1015); + + success = true; + } + } catch (Exception e) { + Log.e(TAG, "Exception in ObjectView.save", e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (Exception e) { + } + } + if (fos != null) { + try { + fos.close(); + } catch (Exception e) { + } + } + if (pfd != null) { + try { + pfd.close(); + } catch (Exception e) { + } + } + } + + if (success) { + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + intent.setData(Uri.fromFile(destFile)); + sendBroadcast(intent); + Toast.makeText(this, R.string.object_saved_message, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, R.string.save_failed_message, Toast.LENGTH_SHORT).show(); + } + } + + private void delete() { + Uri uri = Mtp.Object.getContentUri(mDeviceID, mObjectID); + + Log.d(TAG, "deleting " + uri); + + int result = getContentResolver().delete(uri, null, null); + if (result > 0) { + 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(); + } + } +} 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..6ddf4e7 --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java @@ -0,0 +1,76 @@ +/* + * 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.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Mtp; +import android.util.Log; +import android.view.View; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; + +/** + * A list view displaying all storage units on a device. + */ +public class StorageBrowser extends ListActivity { + + private static final String TAG = "StorageBrowser"; + + private ListAdapter mAdapter; + private int mDeviceID; + + private static final String[] STORAGE_COLUMNS = + new String[] { Mtp.Storage._ID, Mtp.Storage.DESCRIPTION }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + protected void onResume() { + super.onResume(); + + mDeviceID = getIntent().getIntExtra("device", 0); + if (mDeviceID != 0) { + Cursor c = getContentResolver().query(Mtp.Storage.getContentUri(mDeviceID), + STORAGE_COLUMNS, null, null, null); + Log.d(TAG, "query returned " + c); + startManagingCursor(c); + + // Map Cursor columns to views defined in simple_list_item_1.xml + mAdapter = new SimpleCursorAdapter(this, + android.R.layout.simple_list_item_1, c, + new String[] { Mtp.Storage.DESCRIPTION }, + new int[] { android.R.id.text1, android.R.id.text2 }); + setListAdapter(mAdapter); + } + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Intent intent = new Intent(this, ObjectBrowser.class); + intent.putExtra("device", mDeviceID); + intent.putExtra("storage", (int)mAdapter.getItemId(position)); + 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..c05b239 --- /dev/null +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.java @@ -0,0 +1,47 @@ +/* + * 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.Usb; +import android.net.Uri; +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 (Usb.ACTION_USB_CAMERA_ATTACHED.equals(intent.getAction())) { + Uri uri = intent.getData(); + intent = new Intent(context, StorageBrowser.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + // TODO - add a wrapper to Mtp.Device for this + int id = Integer.parseInt(uri.getPathSegments().get(1)); + intent.putExtra("device", id); + context.startActivity(intent); + } catch (NumberFormatException e) { + Log.e(TAG, "bad device Uri " + uri); + } + } + } +} diff --git a/media/tests/mtp/Android.mk b/media/tests/mtp/Android.mk new file mode 100644 index 0000000..a9074ed --- /dev/null +++ b/media/tests/mtp/Android.mk @@ -0,0 +1,61 @@ +LOCAL_PATH:= $(call my-dir) + +ifneq ($(TARGET_SIMULATOR),true) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + mtp.cpp \ + MtpFile.cpp \ + +LOCAL_C_INCLUDES += \ + frameworks/base/media/mtp \ + +LOCAL_CFLAGS := -DMTP_HOST + +LOCAL_MODULE := mtp + +LOCAL_STATIC_LIBRARIES := libmtp libusbhost libutils libcutils + +include $(BUILD_EXECUTABLE) + +endif + +ifeq ($(HOST_OS),linux) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + mtp.cpp \ + MtpFile.cpp \ + ../../../libs/utils/RefBase.cpp \ + ../../../libs/utils/SharedBuffer.cpp \ + ../../../libs/utils/Threads.cpp \ + ../../../libs/utils/VectorImpl.cpp \ + +LOCAL_C_INCLUDES += \ + frameworks/base/media/mtp \ + +LOCAL_CFLAGS := -DMTP_HOST -g -O0 + +have_readline := $(wildcard /usr/include/readline/readline.h) +have_history := $(wildcard /usr/lib/libhistory*) +ifneq ($(strip $(have_readline)),) +LOCAL_CFLAGS += -DHAVE_READLINE=1 +endif + +LOCAL_LDLIBS += -lpthread +ifneq ($(strip $(have_readline)),) +LOCAL_LDLIBS += -lreadline -lncurses +endif +ifneq ($(strip $(have_history)),) +LOCAL_LDLIBS += -lhistory +endif + +LOCAL_MODULE := mtp + +LOCAL_STATIC_LIBRARIES := libmtp libusbhost libcutils + +include $(BUILD_HOST_EXECUTABLE) + +endif diff --git a/media/tests/mtp/MtpFile.cpp b/media/tests/mtp/MtpFile.cpp new file mode 100644 index 0000000..00d328e --- /dev/null +++ b/media/tests/mtp/MtpFile.cpp @@ -0,0 +1,187 @@ +/* + * 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. + */ + +#include "MtpClient.h" +#include "MtpDevice.h" +#include "MtpDeviceInfo.h" +#include "MtpObjectInfo.h" +#include "MtpStorage.h" +#include "MtpUtils.h" + +#include "MtpFile.h" + +namespace android { + +MtpClient* MtpFile::sClient = NULL; + +MtpFile::MtpFile(MtpDevice* device) + : mDevice(device), + mStorage(0), + mHandle(0) +{ +} + +MtpFile::MtpFile(MtpDevice* device, MtpStorageID storage) + : mDevice(device), + mStorage(storage), + mHandle(0) +{ +} + +MtpFile::MtpFile(MtpDevice* device, MtpStorageID storage, MtpObjectHandle handle) + : mDevice(device), + mStorage(storage), + mHandle(handle) +{ +} + +MtpFile::MtpFile(MtpFile* file) + : mDevice(file->mDevice), + mStorage(file->mStorage), + mHandle(file->mHandle) +{ +} + +MtpFile::~MtpFile() { +} + +void MtpFile::print() { + if (mHandle) { + + } else if (mStorage) { + printf("%x\n", mStorage); + } else { + int id = mDevice->getID(); + MtpDeviceInfo* info = mDevice->getDeviceInfo(); + if (info) + printf("%d\t%s %s %s\n", id, info->mManufacturer, info->mModel, info->mSerial); + else + printf("%d\t(no device info available)\n", id); + delete info; + } +} + +MtpObjectInfo* MtpFile::getObjectInfo() { + return mDevice->getObjectInfo(mHandle); +} + +void MtpFile::list() { + if (mStorage) { + MtpObjectHandleList* handles = mDevice->getObjectHandles(mStorage, 0, + (mHandle ? mHandle : -1)); + if (handles) { + for (int i = 0; i < handles->size(); i++) { + MtpObjectHandle handle = (*handles)[i]; + MtpObjectInfo* info = mDevice->getObjectInfo(handle); + if (info) { + char modified[100]; + struct tm tm; + + gmtime_r(&info->mDateModified, &tm); + strftime(modified, sizeof(modified), "%a %b %e %H:%M:%S GMT %Y", &tm); + printf("%s Handle: %d Format: %04X Size: %d Modified: %s\n", + info->mName, handle, info->mFormat, info->mCompressedSize, modified); + delete info; + } + } + delete handles; + } + } else { + // list storage units for device + MtpStorageIDList* storageList = mDevice->getStorageIDs(); + for (int i = 0; i < storageList->size(); i++) { + MtpStorageID storageID = (*storageList)[i]; + printf("%x\n", storageID); + } + } +} + +void MtpFile::init(MtpClient* client) { + sClient = client; +} + +MtpFile* MtpFile::parsePath(MtpFile* base, char* path) { + MtpDevice* device = NULL; + MtpStorageID storage = 0; + MtpObjectHandle handle = 0; + + if (path[0] != '/' && base) { + device = base->mDevice; + storage = base->mStorage; + handle = base->mHandle; + } + + // parse an absolute path + if (path[0] == '/') + path++; + char* tok = strtok(path, "/"); + while (tok) { + if (storage) { + // find child of current handle + MtpObjectHandleList* handles = device->getObjectHandles(storage, 0, + (handle ? handle : -1)); + MtpObjectHandle childHandle = 0; + + if (handles) { + for (int i = 0; i < handles->size() && !childHandle; i++) { + MtpObjectHandle handle = (*handles)[i]; + MtpObjectInfo* info = device->getObjectInfo(handle); + if (info && !strcmp(tok, info->mName)) + childHandle = handle; + delete info; + } + delete handles; + } + if (childHandle) + handle = childHandle; + else + return NULL; + } else if (device) { + unsigned int id; + // find storage for the device + if (sscanf(tok, "%x", &id) == 1) { + MtpStorageIDList* storageList = device->getStorageIDs(); + bool found = false; + for (int i = 0; i < storageList->size(); i++) { + if ((*storageList)[i] == id) { + found = true; + break; + } + } + if (found) + storage = id; + else + return NULL; + } + } else { + // find device + unsigned int id; + if (sscanf(tok, "%d", &id) == 1) + device = sClient->getDevice(id); + if (!device) + return NULL; + } + + tok = strtok(NULL, "/"); + } + + if (device) + return new MtpFile(device, storage, handle); + else + return NULL; +} + +} diff --git a/media/tests/mtp/MtpFile.h b/media/tests/mtp/MtpFile.h new file mode 100644 index 0000000..ab8762b --- /dev/null +++ b/media/tests/mtp/MtpFile.h @@ -0,0 +1,57 @@ +/* + * 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_FILE_H +#define _MTP_FILE_H + +#include "MtpTypes.h" + +namespace android { + +class MtpClient; +class MtpDevice; +class MtpObjectInfo; + +// File-like abstraction for the interactive shell. +// This can be used to represent an MTP device, storage unit or object +// (either file or association). +class MtpFile { +private: + MtpDevice* mDevice; + MtpStorageID mStorage; + MtpObjectHandle mHandle; + static MtpClient* sClient; + +public: + MtpFile(MtpDevice* device); + MtpFile(MtpDevice* device, MtpStorageID storage); + MtpFile(MtpDevice* device, MtpStorageID storage, MtpObjectHandle handle); + MtpFile(MtpFile* file); + virtual ~MtpFile(); + + MtpObjectInfo* getObjectInfo(); + void print(); + void list(); + + inline MtpDevice* getDevice() const { return mDevice; } + + static void init(MtpClient* client); + static MtpFile* parsePath(MtpFile* base, char* path); +}; + +} + +#endif // _MTP_DIRECTORY_H diff --git a/media/tests/mtp/mtp.cpp b/media/tests/mtp/mtp.cpp new file mode 100644 index 0000000..2c5e23b --- /dev/null +++ b/media/tests/mtp/mtp.cpp @@ -0,0 +1,370 @@ +/* + * 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. + */ + +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#if HAVE_READLINE +#include <readline/readline.h> +#include <readline/history.h> +#endif + +#include "MtpClient.h" +#include "MtpDevice.h" +#include "MtpObjectInfo.h" + +#include "MtpFile.h" + +#define PROMPT "mtp> " + +using namespace android; + +static MtpClient* sClient = NULL; + +// current working directory information for interactive shell +static MtpFile* sCurrentDirectory = NULL; + +static MtpFile* parse_path(char* path) { + return MtpFile::parsePath(sCurrentDirectory, path); +} + +class MyClient : public MtpClient { +private: + virtual void deviceAdded(MtpDevice *device) { + } + + virtual void deviceRemoved(MtpDevice *device) { + } + +public: +}; + +static void init() { + sClient = new MyClient; + sClient->start(); + MtpFile::init(sClient); +} + +static int set_cwd(int argc, char* argv[]) { + if (argc != 1) { + fprintf(stderr, "cd should have one argument\n"); + return -1; + } + if (!strcmp(argv[0], "/")) { + delete sCurrentDirectory; + sCurrentDirectory = NULL; + } + else { + MtpFile* file = parse_path(argv[0]); + if (file) { + delete sCurrentDirectory; + sCurrentDirectory = file; + } else { + fprintf(stderr, "could not find %s\n", argv[0]); + return -1; + } + } + return 0; +} + +static void list_devices() { + // TODO - need to make sure the list will not change while iterating + MtpDeviceList& devices = sClient->getDeviceList(); + for (int i = 0; i < devices.size(); i++) { + MtpDevice* device = devices[i]; + MtpFile* file = new MtpFile(device); + file->print(); + delete file; + } +} + +static int list(int argc, char* argv[]) { + if (argc == 0) { + // list cwd + if (sCurrentDirectory) { + sCurrentDirectory->list(); + } else { + list_devices(); + } + } + + for (int i = 0; i < argc; i++) { + char* path = argv[i]; + if (!strcmp(path, "/")) { + list_devices(); + } else { + MtpFile* file = parse_path(path); + if (!file) { + fprintf(stderr, "could not find %s\n", path); + return -1; + } + file->list(); + } + } + + return 0; +} + +static int get_file(int argc, char* argv[]) { + int ret = -1; + int srcFD = -1; + int destFD = -1; + MtpFile* srcFile = NULL; + MtpObjectInfo* info = NULL; + char* dest; + + if (argc < 1) { + fprintf(stderr, "not enough arguments\n"); + return -1; + } else if (argc > 2) { + fprintf(stderr, "too many arguments\n"); + return -1; + } + + // find source object + char* src = argv[0]; + srcFile = parse_path(src); + if (!srcFile) { + fprintf(stderr, "could not find %s\n", src); + return -1; + } + info = srcFile->getObjectInfo(); + if (!info) { + fprintf(stderr, "could not find object info for %s\n", src); + goto fail; + } + if (info->mFormat == MTP_FORMAT_ASSOCIATION) { + fprintf(stderr, "copying directories not implemented yet\n"); + goto fail; + } + + dest = (argc > 1 ? argv[1] : info->mName); + destFD = open(dest, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (destFD < 0) { + fprintf(stderr, "could not create %s\n", dest); + goto fail; + } + srcFD = srcFile->getDevice()->readObject(info->mHandle, info->mCompressedSize); + if (srcFD < 0) + goto fail; + + char buffer[65536]; + while (1) { + int count = read(srcFD, buffer, sizeof(buffer)); + if (count <= 0) + break; + write(destFD, buffer, count); + } + // FIXME - error checking and reporting + ret = 0; + +fail: + delete srcFile; + delete info; + if (srcFD >= 0) + close(srcFD); + if (destFD >= 0) + close(destFD); + return ret; +} + +static int put_file(int argc, char* argv[]) { + int ret = -1; + int srcFD = -1; + MtpFile* destFile = NULL; + MtpObjectInfo* srcInfo = NULL; + MtpObjectInfo* destInfo = NULL; + MtpObjectHandle handle; + struct stat statbuf; + const char* lastSlash; + + if (argc < 1) { + fprintf(stderr, "not enough arguments\n"); + return -1; + } else if (argc > 2) { + fprintf(stderr, "too many arguments\n"); + return -1; + } + const char* src = argv[0]; + srcFD = open(src, O_RDONLY); + if (srcFD < 0) { + fprintf(stderr, "could not open %s\n", src); + goto fail; + } + if (argc == 2) { + char* dest = argv[1]; + destFile = parse_path(dest); + if (!destFile) { + fprintf(stderr, "could not find %s\n", dest); + goto fail; + } + } else { + if (!sCurrentDirectory) { + fprintf(stderr, "current working directory not set\n"); + goto fail; + } + destFile = new MtpFile(sCurrentDirectory); + } + + destInfo = destFile->getObjectInfo(); + if (!destInfo) { + fprintf(stderr, "could not find object info destination directory\n"); + goto fail; + } + if (destInfo->mFormat != MTP_FORMAT_ASSOCIATION) { + fprintf(stderr, "destination not a directory\n"); + goto fail; + } + + if (fstat(srcFD, &statbuf)) + goto fail; + + srcInfo = new MtpObjectInfo(0); + srcInfo->mStorageID = destInfo->mStorageID; + srcInfo->mFormat = MTP_FORMAT_EXIF_JPEG; // FIXME + srcInfo->mCompressedSize = statbuf.st_size; + srcInfo->mParent = destInfo->mHandle; + lastSlash = strrchr(src, '/'); + srcInfo->mName = strdup(lastSlash ? lastSlash + 1 : src); + srcInfo->mDateModified = statbuf.st_mtime; + handle = destFile->getDevice()->sendObjectInfo(srcInfo); + if (handle <= 0) { + printf("sendObjectInfo returned %04X\n", handle); + goto fail; + } + if (destFile->getDevice()->sendObject(srcInfo, srcFD)) + ret = 0; + +fail: + delete destFile; + delete srcInfo; + delete destInfo; + if (srcFD >= 0) + close(srcFD); + printf("returning %d\n", ret); + return ret; +} + +typedef int (* command_func)(int argc, char* argv[]); + +struct command_table_entry { + const char* name; + command_func func; +}; + +const command_table_entry command_list[] = { + { "cd", set_cwd }, + { "ls", list }, + { "get", get_file }, + { "put", put_file }, + { NULL, NULL }, +}; + + +static int do_command(int argc, char* argv[]) { + const command_table_entry* command = command_list; + const char* name = *argv++; + argc--; + + while (command->name) { + if (!strcmp(command->name, name)) + return command->func(argc, argv); + else + command++; + } + fprintf(stderr, "unknown command %s\n", name); + return -1; +} + +static int shell() { + int argc; + int result = 0; +#define MAX_ARGS 100 + char* argv[MAX_ARGS]; + +#if HAVE_READLINE + using_history(); +#endif + + while (1) { +#if HAVE_READLINE + char* line = readline(PROMPT); + if (!line) { + printf("\n"); + exit(0); + } +#else + char buffer[1000]; + printf("%s", PROMPT); + char* line = NULL; + size_t length = 0; + + buffer[0] = 0; + fgets(buffer, sizeof(buffer), stdin); + int count = strlen(buffer); + if (count > 0 && buffer[0] == EOF) { + printf("\n"); + exit(0); + } + if (count > 0 && line[count - 1] == '\n') + line[count - 1] == 0; +#endif + char* tok = strtok(line, " \t\n\r"); + if (!tok) + continue; + if (!strcmp(tok, "quit") || !strcmp(tok, "exit")) { + exit(0); + } +#if HAVE_READLINE + add_history(line); +#endif + argc = 0; + while (tok) { + if (argc + 1 == MAX_ARGS) { + fprintf(stderr, "too many arguments\n"); + result = -1; + goto bottom_of_loop; + } + + argv[argc++] = strdup(tok); + tok = strtok(NULL, " \t\n\r"); + } + + result = do_command(argc, argv); + +bottom_of_loop: + for (int i = 0; i < argc; i++) + free(argv[i]); + free(line); + } + + return result; +} + +int main(int argc, char* argv[]) { + init(); + + if (argc == 1) + return shell(); + else + return do_command(argc - 1, argv + 1); +} diff --git a/media/tests/players/invoke_mock_media_player.cpp b/media/tests/players/invoke_mock_media_player.cpp index b3cc8b6..53308be 100644 --- a/media/tests/players/invoke_mock_media_player.cpp +++ b/media/tests/players/invoke_mock_media_player.cpp @@ -26,6 +26,7 @@ using android::INVALID_OPERATION; using android::ISurface; +using android::Surface; using android::MediaPlayerBase; using android::OK; using android::Parcel; @@ -67,7 +68,8 @@ class Player: public MediaPlayerBase } virtual status_t setDataSource(int fd, int64_t offset, int64_t length) {return OK;} - virtual status_t setVideoSurface(const sp<ISurface>& surface) {return OK;} + virtual status_t setVideoISurface(const sp<ISurface>& surface) {return OK;} + virtual status_t setVideoSurface(const sp<Surface>& surface) {return OK;} virtual status_t prepare() {return OK;} virtual status_t prepareAsync() {return OK;} virtual status_t start() {return OK;} |