MTP: Implement support for getting/setting device properties
Added support for the "device friendly name" and "synchonization partner"
properties, which are required by Microsoft.
Change-Id: Ic0443333d75f7d98a2d902a790b9d505a56d4eef
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java
index 0c8326e..9016e68 100644
--- a/media/java/android/media/MtpDatabase.java
+++ b/media/java/android/media/MtpDatabase.java
@@ -21,6 +21,7 @@
import android.content.IContentProvider;
import android.content.Intent;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.MediaStore.Audio;
@@ -44,6 +45,10 @@
// true if the database has been modified in the current MTP session
private boolean mDatabaseModified;
+ // database for writable MTP device properties
+ private SQLiteDatabase mDevicePropDb;
+ private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
+
// FIXME - this should be passed in via the constructor
private final int mStorageID = 0x00010001;
@@ -69,6 +74,9 @@
private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND "
+ MtpObjects.ObjectColumns.FORMAT + "=?";
+ private static final String[] DEVICE_PROPERTY_PROJECTION = new String[] { "_id", "value" };
+ private static final String DEVICE_PROPERTY_WHERE = "code=?";
+
private final MediaScanner mMediaScanner;
static {
@@ -83,6 +91,7 @@
mVolumeName = volumeName;
mObjectsUri = MtpObjects.getContentUri(volumeName);
mMediaScanner = new MediaScanner(context);
+ openDevicePropertiesDatabase(context);
}
@Override
@@ -94,6 +103,22 @@
}
}
+ private void openDevicePropertiesDatabase(Context context) {
+ mDevicePropDb = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
+ int version = mDevicePropDb.getVersion();
+
+ // initialize if necessary
+ if (version != DEVICE_PROPERTIES_DATABASE_VERSION) {
+ mDevicePropDb.execSQL("CREATE TABLE properties (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "code INTEGER UNIQUE ON CONFLICT REPLACE," +
+ "value TEXT" +
+ ");");
+ mDevicePropDb.execSQL("CREATE INDEX property_index ON properties (code);");
+ mDevicePropDb.setVersion(DEVICE_PROPERTIES_DATABASE_VERSION);
+ }
+ }
+
private int beginSendObject(String path, int format, int parent,
int storage, long size, long modified) {
mDatabaseModified = true;
@@ -257,8 +282,10 @@
}
private int[] getSupportedDeviceProperties() {
- // no device properties yet
- return null;
+ return new int[] {
+ MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
+ MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
+ };
}
private int getObjectProperty(int handle, int property,
@@ -342,6 +369,68 @@
return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
}
+ private int setObjectProperty(int handle, int property,
+ long intValue, String stringValue) {
+ Log.d(TAG, "setObjectProperty: " + property);
+ return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+ }
+
+ private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
+ Log.d(TAG, "getDeviceProperty: " + property);
+
+ switch (property) {
+ case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
+ case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
+ // writable string properties kept in our device property database
+ Cursor c = null;
+ try {
+ c = mDevicePropDb.query("properties", DEVICE_PROPERTY_PROJECTION,
+ DEVICE_PROPERTY_WHERE, new String[] { Integer.toString(property) },
+ null, null, null);
+
+ if (c != null && c.moveToNext()) {
+ String value = c.getString(1);
+ int length = value.length();
+ if (length > 255) {
+ length = 255;
+ }
+ value.getChars(0, length, outStringValue, 0);
+ outStringValue[length] = 0;
+ } else {
+ outStringValue[0] = 0;
+ }
+ return MtpConstants.RESPONSE_OK;
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ }
+
+ return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+ }
+
+ private int setDeviceProperty(int property, long intValue, String stringValue) {
+ Log.d(TAG, "setDeviceProperty: " + property + " : " + stringValue);
+
+ switch (property) {
+ case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
+ case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
+ // writable string properties kept in our device property database
+ try {
+ ContentValues values = new ContentValues();
+ values.put("code", property);
+ values.put("value", stringValue);
+ mDevicePropDb.insert("properties", "code", values);
+ return MtpConstants.RESPONSE_OK;
+ } catch (Exception e) {
+ return MtpConstants.RESPONSE_GENERAL_ERROR;
+ }
+ }
+
+ return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+ }
+
private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
char[] outName, long[] outSizeModified) {
Log.d(TAG, "getObjectInfo: " + handle);
diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp
index 4046287..1842cb2 100644
--- a/media/jni/android_media_MtpDatabase.cpp
+++ b/media/jni/android_media_MtpDatabase.cpp
@@ -30,6 +30,7 @@
#include "MtpDatabase.h"
#include "MtpDataPacket.h"
#include "MtpProperty.h"
+#include "MtpStringBuffer.h"
#include "MtpUtils.h"
#include "mtp.h"
@@ -47,6 +48,8 @@
static jmethodID method_getSupportedDeviceProperties;
static jmethodID method_getObjectProperty;
static jmethodID method_setObjectProperty;
+static jmethodID method_getDeviceProperty;
+static jmethodID method_setDeviceProperty;
static jmethodID method_getObjectInfo;
static jmethodID method_getObjectFilePath;
static jmethodID method_deleteFile;
@@ -127,7 +130,8 @@
int64_t& fileLength);
virtual MtpResponseCode deleteFile(MtpObjectHandle handle);
- bool getPropertyInfo(MtpObjectProperty property, int& type);
+ bool getObjectPropertyInfo(MtpObjectProperty property, int& type);
+ bool getDevicePropertyInfo(MtpDeviceProperty property, int& type);
virtual MtpObjectHandleList* getObjectReferences(MtpObjectHandle handle);
@@ -323,14 +327,16 @@
MtpDataPacket& packet) {
int type;
- if (!getPropertyInfo(property, type))
- return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE;
+ if (!getObjectPropertyInfo(property, type))
+ return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
JNIEnv* env = AndroidRuntime::getJNIEnv();
jint result = env->CallIntMethod(mDatabase, method_getObjectProperty,
(jint)handle, (jint)property, mLongBuffer, mStringBuffer);
- if (result != MTP_RESPONSE_OK)
+ if (result != MTP_RESPONSE_OK) {
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
return result;
+ }
jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
jlong longValue = longValues[0];
@@ -384,8 +390,8 @@
break;
}
default:
- LOGE("unsupported object type\n");
- return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ LOGE("unsupported type in getObjectPropertyValue\n");
+ return MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
}
checkAndClearExceptionFromCallback(env, __FUNCTION__);
@@ -395,17 +401,179 @@
MtpResponseCode MyMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle,
MtpObjectProperty property,
MtpDataPacket& packet) {
- return -1;
+ int type;
+
+ if (!getObjectPropertyInfo(property, type))
+ return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jlong longValue = 0;
+ jstring stringValue = NULL;
+
+ switch (type) {
+ case MTP_TYPE_INT8:
+ longValue = packet.getInt8();
+ break;
+ case MTP_TYPE_UINT8:
+ longValue = packet.getUInt8();
+ break;
+ case MTP_TYPE_INT16:
+ longValue = packet.getInt16();
+ break;
+ case MTP_TYPE_UINT16:
+ longValue = packet.getUInt16();
+ break;
+ case MTP_TYPE_INT32:
+ longValue = packet.getInt32();
+ break;
+ case MTP_TYPE_UINT32:
+ longValue = packet.getUInt32();
+ break;
+ case MTP_TYPE_INT64:
+ longValue = packet.getInt64();
+ break;
+ case MTP_TYPE_UINT64:
+ longValue = packet.getUInt64();
+ break;
+ case MTP_TYPE_STR:
+ {
+ MtpStringBuffer buffer;
+ packet.getString(buffer);
+ stringValue = env->NewStringUTF((const char *)buffer);
+ break;
+ }
+ default:
+ LOGE("unsupported type in getObjectPropertyValue\n");
+ return MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
+ }
+
+ jint result = env->CallIntMethod(mDatabase, method_setObjectProperty,
+ (jint)handle, (jint)property, longValue, stringValue);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return result;
}
MtpResponseCode MyMtpDatabase::getDevicePropertyValue(MtpDeviceProperty property,
MtpDataPacket& packet) {
- return -1;
+
+ int type;
+
+ if (!getDevicePropertyInfo(property, type))
+ return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jint result = env->CallIntMethod(mDatabase, method_getDeviceProperty,
+ (jint)property, mLongBuffer, mStringBuffer);
+ if (result != MTP_RESPONSE_OK) {
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ 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_INT128:
+ packet.putInt128(longValue);
+ break;
+ case MTP_TYPE_UINT128:
+ packet.putInt128(longValue);
+ break;
+ case MTP_TYPE_STR:
+ {
+ jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
+ packet.putString(str);
+ env->ReleaseCharArrayElements(mStringBuffer, str, 0);
+ break;
+ }
+ default:
+ LOGE("unsupported type in getDevicePropertyValue\n");
+ return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT;
+ }
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return MTP_RESPONSE_OK;
}
MtpResponseCode MyMtpDatabase::setDevicePropertyValue(MtpDeviceProperty property,
MtpDataPacket& packet) {
- return -1;
+ int type;
+
+ if (!getDevicePropertyInfo(property, type))
+ return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jlong longValue = 0;
+ jstring stringValue = NULL;
+
+ switch (type) {
+ case MTP_TYPE_INT8:
+ longValue = packet.getInt8();
+ break;
+ case MTP_TYPE_UINT8:
+ longValue = packet.getUInt8();
+ break;
+ case MTP_TYPE_INT16:
+ longValue = packet.getInt16();
+ break;
+ case MTP_TYPE_UINT16:
+ longValue = packet.getUInt16();
+ break;
+ case MTP_TYPE_INT32:
+ longValue = packet.getInt32();
+ break;
+ case MTP_TYPE_UINT32:
+ longValue = packet.getUInt32();
+ break;
+ case MTP_TYPE_INT64:
+ longValue = packet.getInt64();
+ break;
+ case MTP_TYPE_UINT64:
+ longValue = packet.getUInt64();
+ break;
+ case MTP_TYPE_STR:
+ {
+ MtpStringBuffer buffer;
+ packet.getString(buffer);
+ stringValue = env->NewStringUTF((const char *)buffer);
+ break;
+ }
+ default:
+ LOGE("unsupported type in setDevicePropertyValue\n");
+ return MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
+ }
+
+ jint result = env->CallIntMethod(mDatabase, method_setDeviceProperty,
+ (jint)property, longValue, stringValue);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return result;
}
MtpResponseCode MyMtpDatabase::resetDeviceProperty(MtpDeviceProperty property) {
@@ -473,8 +641,10 @@
JNIEnv* env = AndroidRuntime::getJNIEnv();
jint result = env->CallIntMethod(mDatabase, method_getObjectFilePath,
(jint)handle, mStringBuffer, mLongBuffer);
- if (result != MTP_RESPONSE_OK)
+ if (result != MTP_RESPONSE_OK) {
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
return result;
+ }
jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
filePath.setTo(str, strlen16(str));
@@ -501,7 +671,7 @@
int type;
};
-static const PropertyTableEntry kPropertyTable[] = {
+static const PropertyTableEntry kObjectPropertyTable[] = {
{ MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32 },
{ MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32 },
{ MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16 },
@@ -510,9 +680,26 @@
{ 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;
+static const PropertyTableEntry kDevicePropertyTable[] = {
+ { MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER, MTP_TYPE_STR },
+ { MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, MTP_TYPE_STR },
+};
+
+bool MyMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) {
+ int count = sizeof(kObjectPropertyTable) / sizeof(kObjectPropertyTable[0]);
+ const PropertyTableEntry* entry = kObjectPropertyTable;
+ for (int i = 0; i < count; i++, entry++) {
+ if (entry->property == property) {
+ type = entry->type;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool MyMtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type) {
+ int count = sizeof(kDevicePropertyTable) / sizeof(kDevicePropertyTable[0]);
+ const PropertyTableEntry* entry = kDevicePropertyTable;
for (int i = 0; i < count; i++, entry++) {
if (entry->property == property) {
type = entry->type;
@@ -587,7 +774,16 @@
}
MtpProperty* MyMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) {
- return NULL;
+ MtpProperty* result = NULL;
+ switch (property) {
+ case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
+ case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
+ // writeable string properties
+ result = new MtpProperty(property, MTP_TYPE_STR, true);
+ break;
+ }
+
+ return result;
}
void MyMtpDatabase::sessionStarted() {
@@ -695,6 +891,21 @@
LOGE("Can't find getObjectProperty");
return -1;
}
+ method_setObjectProperty = env->GetMethodID(clazz, "setObjectProperty", "(IIJLjava/lang/String;)I");
+ if (method_setObjectProperty == NULL) {
+ LOGE("Can't find setObjectProperty");
+ return -1;
+ }
+ method_getDeviceProperty = env->GetMethodID(clazz, "getDeviceProperty", "(I[J[C)I");
+ if (method_getDeviceProperty == NULL) {
+ LOGE("Can't find getDeviceProperty");
+ return -1;
+ }
+ method_setDeviceProperty = env->GetMethodID(clazz, "setDeviceProperty", "(IJLjava/lang/String;)I");
+ if (method_setDeviceProperty == NULL) {
+ LOGE("Can't find setDeviceProperty");
+ return -1;
+ }
method_getObjectInfo = env->GetMethodID(clazz, "getObjectInfo", "(I[I[C[J)Z");
if (method_getObjectInfo == NULL) {
LOGE("Can't find getObjectInfo");
diff --git a/media/mtp/MtpDevice.cpp b/media/mtp/MtpDevice.cpp
index bd68f75..fca0142 100644
--- a/media/mtp/MtpDevice.cpp
+++ b/media/mtp/MtpDevice.cpp
@@ -342,7 +342,7 @@
MtpResponseCode ret = readResponse();
if (ret == MTP_RESPONSE_OK) {
MtpProperty* property = new MtpProperty;
- property->read(mData, true);
+ property->read(mData);
return property;
}
return NULL;
diff --git a/media/mtp/MtpProperty.cpp b/media/mtp/MtpProperty.cpp
index 932ad6a..c7a91d6 100644
--- a/media/mtp/MtpProperty.cpp
+++ b/media/mtp/MtpProperty.cpp
@@ -120,7 +120,7 @@
delete[] mEnumValues;
}
-void MtpProperty::read(MtpDataPacket& packet, bool deviceProp) {
+void MtpProperty::read(MtpDataPacket& packet) {
mCode = packet.getUInt16();
mType = packet.getUInt16();
@@ -141,7 +141,7 @@
break;
default:
readValue(packet, mDefaultValue);
- if (deviceProp)
+ if (isDeviceProperty())
readValue(packet, mCurrentValue);
}
mGroupCode = packet.getUInt32();
@@ -159,7 +159,6 @@
}
}
-// FIXME - only works for object properties
void MtpProperty::write(MtpDataPacket& packet) {
packet.putUInt16(mCode);
packet.putUInt16(mType);
diff --git a/media/mtp/MtpProperty.h b/media/mtp/MtpProperty.h
index c5b4e28..64cfb93 100644
--- a/media/mtp/MtpProperty.h
+++ b/media/mtp/MtpProperty.h
@@ -65,16 +65,22 @@
inline MtpPropertyCode getPropertyCode() const { return mCode; }
- void read(MtpDataPacket& packet, bool deviceProp);
+ void read(MtpDataPacket& packet);
void write(MtpDataPacket& packet);
void print();
+ inline bool isDeviceProperty() const {
+ return ( ((mCode & 0xF000) == 0x5000)
+ || ((mCode & 0xF800) == 0xD000));
+ }
+
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);
+ void writeArrayValues(MtpDataPacket& packet,
+ MtpPropertyValue* values, int length);
};
}; // namespace android
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index c982114..3d3bd62 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -55,7 +55,7 @@
// MTP_OPERATION_SELF_TEST,
// MTP_OPERATION_SET_OBJECT_PROTECTION,
// MTP_OPERATION_POWER_DOWN,
-// MTP_OPERATION_GET_DEVICE_PROP_DESC,
+ MTP_OPERATION_GET_DEVICE_PROP_DESC,
MTP_OPERATION_GET_DEVICE_PROP_VALUE,
MTP_OPERATION_SET_DEVICE_PROP_VALUE,
MTP_OPERATION_RESET_DEVICE_PROP_VALUE,
@@ -288,6 +288,9 @@
case MTP_OPERATION_GET_OBJECT_PROP_DESC:
response = doGetObjectPropDesc();
break;
+ case MTP_OPERATION_GET_DEVICE_PROP_DESC:
+ response = doGetDevicePropDesc();
+ break;
default:
response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
break;