Merge "MTP: Use media provider database to implement MTP device support."

This commit is contained in:
Mike Lockwood
2010-07-08 14:29:59 -07:00
committed by Android (Google) Code Review
14 changed files with 823 additions and 63 deletions

View File

@@ -239,6 +239,44 @@ public final class MediaStore {
public static final String MIME_TYPE = "mime_type";
}
/**
* Media provider interface used by MTP implementation.
* @hide
*/
public static final class MtpObjects {
public static Uri getContentUri(String volumeName) {
return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
"/object");
}
public static final Uri getContentUri(String volumeName,
long objectId) {
return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
+ "/object/" + objectId);
}
/**
* Fields for master table for all media files.
* Table also contains MediaColumns._ID, DATA, SIZE and DATE_MODIFIED.
*/
public interface ObjectColumns extends MediaColumns {
/**
* The MTP format code of the file
* <P>Type: INTEGER</P>
*/
public static final String FORMAT = "format";
/**
* The index of the parent directory of the file
* <P>Type: INTEGER</P>
*/
public static final String PARENT = "parent";
}
}
/**
* This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended
* to be accessed elsewhere.

View File

@@ -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,28 @@ 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>();
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));
}
private static boolean isWMAEnabled() {
List<AudioDecoder> decoders = DecoderCapabilities.getAudioDecoders();
for (AudioDecoder decoder: decoders) {
@@ -126,17 +140,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 +162,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 +236,21 @@ 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;
}
}

View File

@@ -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.content.Context;
import android.content.IContentProvider;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.MediaStore.MtpObjects;
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;
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 + "=?";
static {
System.loadLibrary("media_jni");
}
public MtpDatabase(Context context, String volumeName) {
native_setup();
mMediaProvider = context.getContentResolver().acquireProvider("media");
mVolumeName = volumeName;
mObjectsUri = MtpObjects.getContentUri(volumeName);
}
@Override
protected void finalize() {
native_finalize();
}
// called from native code
private int getObjectHandle(String path) {
Log.d(TAG, "getObjectHandle " + path);
Cursor c = null;
try {
c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
PATH_WHERE, new String[] { path }, null);
if (c != null && c.moveToNext()) {
return c.getInt(0);
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in getObjectHandle", e);
} finally {
if (c != null) {
c.close();
}
}
return 0;
}
private int addFile(String path, int format, int parent,
int storage, long size, long modified) {
Log.d(TAG, "addFile " + path);
return 0;
}
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 getObjectProperty(int handle, int property,
long[] outIntValue, char[] outStringValue) {
Log.d(TAG, "getObjectProperty: " + property);
return 0;
}
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] = 0x00010001;
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 boolean 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 true;
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in getObjectFilePath", e);
} finally {
if (c != null) {
c.close();
}
}
return false;
}
private boolean deleteFile(int handle) {
Log.d(TAG, "deleteFile: " + handle);
Uri uri = MtpObjects.getContentUri(mVolumeName, handle);
try {
return (mMediaProvider.delete(uri, null, null) == 1);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in deleteFile", e);
return false;
}
}
// used by the JNI code
private int mNativeContext;
private native final void native_setup();
private native final void native_finalize();
}

View File

@@ -30,8 +30,8 @@ public class MtpServer {
System.loadLibrary("media_jni");
}
public MtpServer(String storagePath, String databasePath) {
native_setup(storagePath, databasePath);
public MtpServer(MtpDatabase database, String storagePath) {
native_setup(database, storagePath);
}
@Override
@@ -50,7 +50,7 @@ public class MtpServer {
// used by the JNI code
private int mNativeContext;
private native final void native_setup(String storagePath, String databasePath);
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();

View File

@@ -15,6 +15,7 @@ LOCAL_SRC_FILES:= \
android_media_AmrInputStream.cpp \
android_media_MtpClient.cpp \
android_media_MtpCursor.cpp \
android_media_MtpDatabase.cpp \
android_media_MtpServer.cpp \
LOCAL_SHARED_LIBRARIES := \

View File

@@ -766,6 +766,7 @@ 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
@@ -830,6 +831,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
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;

View File

@@ -0,0 +1,451 @@
/*
* 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_getObjectHandle;
static jmethodID method_addFile;
static jmethodID method_getObjectList;
static jmethodID method_getObjectProperty;
static jmethodID method_getObjectInfo;
static jmethodID method_getObjectFilePath;
static jmethodID method_deleteFile;
static jfieldID field_context;
MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) {
return (MtpDatabase *)env->GetIntField(database, field_context);
}
// ----------------------------------------------------------------------------
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 getObjectHandle(const char* path);
virtual MtpObjectHandle addFile(const char* path,
MtpObjectFormat format,
MtpObjectHandle parent,
MtpStorageID storage,
uint64_t size,
time_t modified);
virtual MtpObjectHandleList* getObjectList(MtpStorageID storageID,
MtpObjectFormat format,
MtpObjectHandle parent);
virtual MtpResponseCode getObjectProperty(MtpObjectHandle handle,
MtpObjectProperty property,
MtpDataPacket& packet);
virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle,
MtpDataPacket& packet);
virtual bool getObjectFilePath(MtpObjectHandle handle,
MtpString& filePath,
int64_t& fileLength);
virtual bool deleteFile(MtpObjectHandle handle);
// helper for media scanner
virtual MtpObjectHandle* getFileList(int& outCount);
virtual void beginTransaction();
virtual void commitTransaction();
virtual void rollbackTransaction();
bool getPropertyInfo(MtpObjectProperty property, int& type);
};
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::getObjectHandle(const char* path) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
return env->CallIntMethod(mDatabase, method_getObjectHandle, env->NewStringUTF(path));
}
MtpObjectHandle MyMtpDatabase::addFile(const char* path,
MtpObjectFormat format,
MtpObjectHandle parent,
MtpStorageID storage,
uint64_t size,
time_t modified) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
return env->CallIntMethod(mDatabase, method_addFile, env->NewStringUTF(path),
(jint)format, (jint)parent, (jint)storage, (jlong)size, (jlong)modified);
}
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);
LOGD("getObjectList length: %d", length);
for (int i = 0; i < length; i++) {
LOGD("push: %d", handles[i]);
list->push(handles[i]);
}
env->ReleaseIntArrayElements(array, handles, 0);
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;
}
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
return MTP_RESPONSE_OK;
}
bool MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle,
MtpString& filePath,
int64_t& fileLength) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jboolean result = env->CallBooleanMethod(mDatabase, method_getObjectFilePath,
(jint)handle, mStringBuffer, mLongBuffer);
if (!result)
return false;
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);
return true;
}
bool MyMtpDatabase::deleteFile(MtpObjectHandle handle) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
return env->CallBooleanMethod(mDatabase, method_deleteFile, (jint)handle);
}
// helper for media scanner
MtpObjectHandle* MyMtpDatabase::getFileList(int& outCount) {
// REMOVE ME
return NULL;
}
void MyMtpDatabase::beginTransaction() {
// REMOVE ME
}
void MyMtpDatabase::commitTransaction() {
// REMOVE ME
}
void MyMtpDatabase::rollbackTransaction() {
// REMOVE ME
}
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_UINT32 },
{ 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;
}
// ----------------------------------------------------------------------------
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();
}
}
// ----------------------------------------------------------------------------
static void
android_media_MtpDatabase_setup(JNIEnv *env, jobject thiz)
{
LOGD("setup\n");
MyMtpDatabase* database = new MyMtpDatabase(env, thiz);
env->SetIntField(thiz, field_context, (int)database);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
static void
android_media_MtpDatabase_finalize(JNIEnv *env, jobject thiz)
{
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__);
}
// ----------------------------------------------------------------------------
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_getObjectHandle = env->GetMethodID(clazz, "getObjectHandle", "(Ljava/lang/String;)I");
if (method_getObjectHandle == NULL) {
LOGE("Can't find getObjectHandle");
return -1;
}
method_addFile = env->GetMethodID(clazz, "addFile", "(Ljava/lang/String;IIIJJ)I");
if (method_addFile == NULL) {
LOGE("Can't find addFile");
return -1;
}
method_getObjectList = env->GetMethodID(clazz, "getObjectList", "(III)[I");
if (method_getObjectList == NULL) {
LOGE("Can't find getObjectList");
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)Z");
if (method_getObjectFilePath == NULL) {
LOGE("Can't find getObjectFilePath");
return -1;
}
method_deleteFile = env->GetMethodID(clazz, "deleteFile", "(I)Z");
if (method_deleteFile == NULL) {
LOGE("Can't find deleteFile");
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));
}

View File

@@ -30,6 +30,7 @@
#include "private/android_filesystem_config.h"
#include "MtpServer.h"
#include "MtpSqliteDatabase.h" // REMOVE
using namespace android;
@@ -37,6 +38,8 @@ using namespace android;
static jfieldID field_context;
// in android_media_MtpDatabase.cpp
extern MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database);
// ----------------------------------------------------------------------------
@@ -47,14 +50,13 @@ static bool ExceptionCheck(void* env)
class MtpThread : public Thread {
private:
MtpDatabase* mDatabase;
String8 mStoragePath;
String8 mDatabasePath;
bool mDone;
bool mScannedOnce;
public:
MtpThread(const char* storagePath, const char* databasePath)
: mStoragePath(storagePath), mDatabasePath(databasePath), mDone(false), mScannedOnce(false)
MtpThread(MtpDatabase* database, const char* storagePath)
: mDatabase(database), mStoragePath(storagePath), mDone(false)
{
}
@@ -66,12 +68,10 @@ public:
return false;
}
MtpServer* server = new MtpServer(fd, mDatabasePath, AID_SDCARD_RW, 0664, 0775);
MtpServer* server = new MtpServer(fd, mDatabase, AID_SDCARD_RW, 0664, 0775);
server->addStorage(mStoragePath);
// temporary
LOGD("MtpThread server->scanStorage");
server->scanStorage();
LOGD("MtpThread server->run");
server->run();
close(fd);
@@ -88,18 +88,17 @@ public:
};
static void
android_media_MtpServer_setup(JNIEnv *env, jobject thiz, jstring storagePath, jstring databasePath)
android_media_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jstring storagePath)
{
LOGD("setup\n");
MtpDatabase* database = getMtpDatabase(env, javaDatabase);
const char *storagePathStr = env->GetStringUTFChars(storagePath, NULL);
const char *databasePathStr = env->GetStringUTFChars(databasePath, NULL);
MtpThread* thread = new MtpThread(storagePathStr, databasePathStr);
MtpThread* thread = new MtpThread(database, storagePathStr);
env->SetIntField(thiz, field_context, (int)thread);
env->ReleaseStringUTFChars(storagePath, storagePathStr);
env->ReleaseStringUTFChars(databasePath, databasePathStr);
}
static void
@@ -131,7 +130,8 @@ android_media_MtpServer_stop(JNIEnv *env, jobject thiz)
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
{"native_setup", "(Ljava/lang/String;Ljava/lang/String;)V", (void *)android_media_MtpServer_setup},
{"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},

View File

@@ -299,17 +299,28 @@ void MtpDataPacket::putAUInt64(const uint64_t* values, int count) {
putUInt64(*values++);
}
void MtpDataPacket::putString(const MtpStringBuffer& string)
{
void MtpDataPacket::putString(const MtpStringBuffer& string) {
string.writeToPacket(this);
}
void MtpDataPacket::putString(const char* s)
{
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);
for (int i = 0; i < count; i++)
putUInt16(string[i]);
}
#ifdef MTP_DEVICE
int MtpDataPacket::read(int fd) {
// first read the header

View File

@@ -79,6 +79,7 @@ public:
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() { putUInt16(0); }
inline void putEmptyArray() { putUInt32(0); }

View File

@@ -113,11 +113,10 @@ static const MtpObjectFormat kSupportedPlaybackFormats[] = {
// MTP_FORMAT_PLS_PLAYLIST,
};
MtpServer::MtpServer(int fd, const char* databasePath,
MtpServer::MtpServer(int fd, MtpDatabase* database,
int fileGroup, int filePerm, int directoryPerm)
: mFD(fd),
mDatabasePath(databasePath),
mDatabase(NULL),
mDatabase(database),
mFileGroup(fileGroup),
mFilePermission(filePerm),
mDirectoryPermission(directoryPerm),
@@ -126,9 +125,6 @@ MtpServer::MtpServer(int fd, const char* databasePath,
mSendObjectHandle(kInvalidObjectHandle),
mSendObjectFileSize(0)
{
mDatabase = new MtpSqliteDatabase();
mDatabase->open(databasePath, true);
initObjectProperties();
}
@@ -427,6 +423,8 @@ MtpResponseCode MtpServer::doGetObjectHandles() {
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);
@@ -488,9 +486,10 @@ MtpResponseCode MtpServer::doSendObjectInfo() {
return MTP_RESPONSE_INVALID_STORAGE_ID;
// special case the root
if (parent == MTP_PARENT_ROOT)
if (parent == MTP_PARENT_ROOT) {
path = storage->getPath();
else {
parent = 0;
} else {
int64_t dummy;
if (!mDatabase->getObjectFilePath(parent, path, dummy))
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
@@ -549,7 +548,7 @@ MtpResponseCode MtpServer::doSendObjectInfo() {
}
mResponse.setParameter(1, storageID);
mResponse.setParameter(2, parent);
mResponse.setParameter(2, (parent == 0 ? 0xFFFFFFFF: parent));
mResponse.setParameter(3, handle);
return MTP_RESPONSE_OK;

View File

@@ -26,9 +26,9 @@
namespace android {
class MtpStorage;
class MtpSqliteDatabase;
class MtpDatabase;
class MtpProperty;
class MtpStorage;
class MtpServer {
@@ -36,10 +36,7 @@ private:
// file descriptor for MTP kernel driver
int mFD;
// path to our sqlite3 database
const char* mDatabasePath;
MtpSqliteDatabase* mDatabase;
MtpDatabase* mDatabase;
// group to own new files and folders
int mFileGroup;
@@ -67,7 +64,7 @@ private:
size_t mSendObjectFileSize;
public:
MtpServer(int fd, const char* databasePath,
MtpServer(int fd, MtpDatabase* database,
int fileGroup, int filePerm, int directoryPerm);
virtual ~MtpServer();

View File

@@ -414,7 +414,6 @@ MtpObjectHandle* MtpSqliteDatabase::getFileList(int& outCount) {
SqliteStatement stmt(mDatabase);
stmt.prepare("SELECT count(*) FROM files;");
MtpObjectHandleList* list = new MtpObjectHandleList();
if (stmt.step())
count = stmt.getColumnInt(0);

View File

@@ -25,6 +25,7 @@
#include <sys/ioctl.h>
#include "MtpServer.h"
#include "MtpSqliteDatabase.h"
#include "MtpStorage.h"
#include "f_mtp.h"
#include "private/android_filesystem_config.h"
@@ -77,7 +78,9 @@ int main(int argc, char* argv[]) {
enable_usb_function("usb_mass_storage", false);
enable_usb_function("mtp", true);
MtpServer server(fd, "/data/data/mtp/mtp.db", AID_SDCARD_RW, 0664, 0775);
MtpSqliteDatabase* database = new MtpSqliteDatabase();
database->open("/data/data/mtp/mtp.db", true);
MtpServer server(fd, database, AID_SDCARD_RW, 0664, 0775);
server.addStorage(storagePath);
server.scanStorage();
server.run();