summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/AudioFormat.java4
-rw-r--r--media/java/android/media/AudioManager.java18
-rw-r--r--media/java/android/media/AudioRecord.java10
-rw-r--r--media/java/android/media/MediaFile.java91
-rw-r--r--media/java/android/media/MediaRecorder.java32
-rw-r--r--media/java/android/media/MediaScanner.java15
-rw-r--r--media/java/android/media/MtpClient.java104
-rw-r--r--media/java/android/media/MtpCursor.java223
-rw-r--r--media/java/android/media/MtpDatabase.java466
-rw-r--r--media/java/android/media/MtpServer.java67
-rw-r--r--media/jni/Android.mk14
-rw-r--r--media/jni/android_media_MediaPlayer.cpp24
-rw-r--r--media/jni/android_media_MtpClient.cpp296
-rw-r--r--media/jni/android_media_MtpCursor.cpp136
-rw-r--r--media/jni/android_media_MtpDatabase.cpp626
-rw-r--r--media/jni/android_media_MtpServer.cpp220
-rw-r--r--media/libeffects/visualizer/Android.mk2
-rw-r--r--media/libmedia/AudioSystem.cpp3
-rw-r--r--media/libmedia/IMediaPlayer.cpp21
-rw-r--r--media/libmedia/IMediaRecorder.cpp8
-rw-r--r--media/libmedia/MediaScanner.cpp18
-rw-r--r--media/libmedia/mediaplayer.cpp13
-rw-r--r--media/libmedia/mediarecorder.cpp3
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.cpp10
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.h3
-rw-r--r--media/libmediaplayerservice/MediaRecorderClient.cpp3
-rw-r--r--media/libmediaplayerservice/MediaRecorderClient.h3
-rw-r--r--media/libmediaplayerservice/MidiFile.h3
-rw-r--r--media/libmediaplayerservice/StagefrightPlayer.cpp11
-rw-r--r--media/libmediaplayerservice/StagefrightPlayer.h3
-rw-r--r--media/libmediaplayerservice/StagefrightRecorder.cpp103
-rw-r--r--media/libmediaplayerservice/StagefrightRecorder.h12
-rw-r--r--media/libmediaplayerservice/TestPlayerStub.h5
-rw-r--r--media/libstagefright/Android.mk2
-rw-r--r--media/libstagefright/AwesomePlayer.cpp65
-rw-r--r--media/libstagefright/CameraSource.cpp42
-rw-r--r--media/libstagefright/CameraSourceTimeLapse.cpp350
-rw-r--r--media/libstagefright/NuHTTPDataSource.cpp2
-rw-r--r--media/libstagefright/OMXCodec.cpp23
-rw-r--r--media/libstagefright/colorconversion/Android.mk8
-rw-r--r--media/libstagefright/colorconversion/SoftwareRenderer.cpp173
-rw-r--r--media/libstagefright/httplive/M3UParser.cpp2
-rw-r--r--media/libstagefright/include/AwesomePlayer.h3
-rw-r--r--media/libstagefright/include/SoftwareRenderer.h18
-rw-r--r--media/libstagefright/omx/OMX.cpp5
-rw-r--r--media/libstagefright/rtsp/ARTSPConnection.cpp2
-rw-r--r--media/libstagefright/rtsp/ASessionDescription.cpp2
-rw-r--r--media/libstagefright/rtsp/MyHandler.h2
-rw-r--r--media/libstagefright/yuv/Android.mk13
-rw-r--r--media/libstagefright/yuv/YUVCanvas.cpp111
-rw-r--r--media/libstagefright/yuv/YUVImage.cpp413
-rw-r--r--media/mtp/Android.mk78
-rw-r--r--media/mtp/MtpClient.cpp262
-rw-r--r--media/mtp/MtpClient.h68
-rw-r--r--media/mtp/MtpCursor.cpp460
-rw-r--r--media/mtp/MtpCursor.h76
-rw-r--r--media/mtp/MtpDataPacket.cpp476
-rw-r--r--media/mtp/MtpDataPacket.h116
-rw-r--r--media/mtp/MtpDatabase.h83
-rw-r--r--media/mtp/MtpDebug.cpp387
-rw-r--r--media/mtp/MtpDebug.h37
-rw-r--r--media/mtp/MtpDevice.cpp496
-rw-r--r--media/mtp/MtpDevice.h105
-rw-r--r--media/mtp/MtpDeviceInfo.cpp97
-rw-r--r--media/mtp/MtpDeviceInfo.h54
-rw-r--r--media/mtp/MtpEventPacket.cpp67
-rw-r--r--media/mtp/MtpEventPacket.h48
-rw-r--r--media/mtp/MtpObjectInfo.cpp108
-rw-r--r--media/mtp/MtpObjectInfo.h60
-rw-r--r--media/mtp/MtpPacket.cpp154
-rw-r--r--media/mtp/MtpPacket.h69
-rw-r--r--media/mtp/MtpProperty.cpp327
-rw-r--r--media/mtp/MtpProperty.h82
-rw-r--r--media/mtp/MtpRequestPacket.cpp56
-rw-r--r--media/mtp/MtpRequestPacket.h48
-rw-r--r--media/mtp/MtpResponsePacket.cpp57
-rw-r--r--media/mtp/MtpResponsePacket.h48
-rw-r--r--media/mtp/MtpServer.cpp688
-rw-r--r--media/mtp/MtpServer.h111
-rw-r--r--media/mtp/MtpStorage.cpp80
-rw-r--r--media/mtp/MtpStorage.h50
-rw-r--r--media/mtp/MtpStorageInfo.cpp72
-rw-r--r--media/mtp/MtpStorageInfo.h49
-rw-r--r--media/mtp/MtpStringBuffer.cpp141
-rw-r--r--media/mtp/MtpStringBuffer.h54
-rw-r--r--media/mtp/MtpTypes.h92
-rw-r--r--media/mtp/MtpUtils.cpp78
-rw-r--r--media/mtp/MtpUtils.h29
-rw-r--r--media/mtp/mtp.h475
-rw-r--r--media/tests/CameraBrowser/Android.mk8
-rw-r--r--media/tests/CameraBrowser/AndroidManifest.xml28
-rw-r--r--media/tests/CameraBrowser/res/layout/object_info.xml147
-rw-r--r--media/tests/CameraBrowser/res/layout/object_list.xml33
-rw-r--r--media/tests/CameraBrowser/res/menu/object_menu.xml23
-rw-r--r--media/tests/CameraBrowser/res/values/strings.xml44
-rw-r--r--media/tests/CameraBrowser/res/values/styles.xml34
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java103
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java146
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java252
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java76
-rw-r--r--media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.java47
-rw-r--r--media/tests/mtp/Android.mk61
-rw-r--r--media/tests/mtp/MtpFile.cpp187
-rw-r--r--media/tests/mtp/MtpFile.h57
-rw-r--r--media/tests/mtp/mtp.cpp370
-rw-r--r--media/tests/players/invoke_mock_media_player.cpp4
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(&timestampUs);
+ }
+ 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;}