diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 41dfc0075405c..227d94d85229e 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -273,6 +273,13 @@ public final class MediaStore { + "/object/" + objectId); } + // used for MTP GetObjectReferences and SetObjectReferences + public static final Uri getReferencesUri(String volumeName, + long objectId) { + return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + + "/object/" + objectId + "/references"); + } + /** * Fields for master table for all media files. * Table also contains MediaColumns._ID, DATA, SIZE and DATE_MODIFIED. diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java index 1b82d3974109f..37f9f2c7dd6ff 100644 --- a/media/java/android/media/MtpDatabase.java +++ b/media/java/android/media/MtpDatabase.java @@ -22,7 +22,10 @@ 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; /** @@ -120,7 +123,33 @@ public class MtpDatabase { private void endSendObject(String path, int handle, int format, boolean succeeded) { if (succeeded) { - Uri uri = mMediaScanner.scanMtpFile(path, mVolumeName, handle, format); + // 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); } @@ -338,6 +367,53 @@ public class MtpDatabase { } } + 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; diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp index 5ab0a55a2945c..d4539fe0f15ee 100644 --- a/media/jni/android_media_MtpDatabase.cpp +++ b/media/jni/android_media_MtpDatabase.cpp @@ -44,6 +44,8 @@ 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) { @@ -98,6 +100,11 @@ public: virtual MtpResponseCode deleteFile(MtpObjectHandle handle); bool getPropertyInfo(MtpObjectProperty property, int& type); + + virtual MtpObjectHandleList* getObjectReferences(MtpObjectHandle handle); + + virtual MtpResponseCode setObjectReferences(MtpObjectHandle handle, + MtpObjectHandleList* references); }; MyMtpDatabase::MyMtpDatabase(JNIEnv *env, jobject client) @@ -344,6 +351,37 @@ bool MyMtpDatabase::getPropertyInfo(MtpObjectProperty property, int& type) { 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); + 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); + return env->CallIntMethod(mDatabase, method_setObjectReferences, + (jint)handle, array); +} + // ---------------------------------------------------------------------------- static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { @@ -442,6 +480,16 @@ int register_android_media_MtpDatabase(JNIEnv *env) 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"); diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h index bbbbc38b5ca83..02bb0d9f88c0e 100644 --- a/media/mtp/MtpDatabase.h +++ b/media/mtp/MtpDatabase.h @@ -61,7 +61,14 @@ public: 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 diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp index 50a839ea17e89..5f5cadfd98ac0 100644 --- a/media/mtp/MtpServer.cpp +++ b/media/mtp/MtpServer.cpp @@ -68,8 +68,8 @@ static const MtpOperationCode kSupportedOperationCodes[] = { // 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_GET_OBJECT_REFERENCES, + MTP_OPERATION_SET_OBJECT_REFERENCES, // MTP_OPERATION_SKIP, }; @@ -111,11 +111,11 @@ static const MtpObjectFormat kSupportedPlaybackFormats[] = { MTP_FORMAT_MP2, MTP_FORMAT_3GP_CONTAINER, // MTP_FORMAT_ABSTRACT_AUDIO_ALBUM, - // MTP_FORMAT_ABSTRACT_AV_PLAYLIST, - // MTP_FORMAT_WPL_PLAYLIST, - // MTP_FORMAT_M3U_PLAYLIST, + MTP_FORMAT_ABSTRACT_AV_PLAYLIST, + MTP_FORMAT_WPL_PLAYLIST, + MTP_FORMAT_M3U_PLAYLIST, // MTP_FORMAT_MPL_PLAYLIST, - // MTP_FORMAT_PLS_PLAYLIST, + MTP_FORMAT_PLS_PLAYLIST, }; MtpServer::MtpServer(int fd, MtpDatabase* database, @@ -175,7 +175,8 @@ void MtpServer::run() { mRequest.dump(); // FIXME need to generalize this - bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO); + bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO + || operation == MTP_OPERATION_SET_OBJECT_REFERENCES); if (dataIn) { int ret = mData.read(fd); if (ret < 0) { @@ -311,6 +312,12 @@ bool MtpServer::handleRequest() { 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; @@ -477,6 +484,30 @@ MtpResponseCode MtpServer::doGetNumObjects() { } } +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); diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h index 9ed1c84486847..19ccf245f0dd9 100644 --- a/media/mtp/MtpServer.h +++ b/media/mtp/MtpServer.h @@ -95,6 +95,8 @@ private: MtpResponseCode doGetObjectPropsSupported(); MtpResponseCode doGetObjectHandles(); MtpResponseCode doGetNumObjects(); + MtpResponseCode doGetObjectReferences(); + MtpResponseCode doSetObjectReferences(); MtpResponseCode doGetObjectPropValue(); MtpResponseCode doGetObjectInfo(); MtpResponseCode doGetObject();