summaryrefslogtreecommitdiffstats
path: root/media/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'media/java/android')
-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.java12
-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
10 files changed, 996 insertions, 31 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..7f91c22 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
@@ -625,6 +626,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 +1231,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);
+}