Loading media/java/android/media/MtpConstants.java +24 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,30 @@ package android.media; */ public final class MtpConstants { // MTP Data Types public static final int TYPE_UNDEFINED = 0x0000; public static final int TYPE_INT8 = 0x0001; public static final int TYPE_UINT8 = 0x0002; public static final int TYPE_INT16 = 0x0003; public static final int TYPE_UINT16 = 0x0004; public static final int TYPE_INT32 = 0x0005; public static final int TYPE_UINT32 = 0x0006; public static final int TYPE_INT64 = 0x0007; public static final int TYPE_UINT64 = 0x0008; public static final int TYPE_INT128 = 0x0009; public static final int TYPE_UINT128 = 0x000A; public static final int TYPE_AINT8 = 0x4001; public static final int TYPE_AUINT8 = 0x4002; public static final int TYPE_AINT16 = 0x4003; public static final int TYPE_AUINT16 = 0x4004; public static final int TYPE_AINT32 = 0x4005; public static final int TYPE_AUINT32 = 0x4006; public static final int TYPE_AINT64 = 0x4007; public static final int TYPE_AUINT64 = 0x4008; public static final int TYPE_AINT128 = 0x4009; public static final int TYPE_AUINT128 = 0x400A; public static final int TYPE_STR = 0xFFFF; // MTP Response Codes public static final int RESPONSE_UNDEFINED = 0x2000; public static final int RESPONSE_OK = 0x2001; Loading media/java/android/media/MtpDatabase.java +255 −139 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.provider.MediaStore.Files; import android.provider.MediaStore.Images; import android.provider.MediaStore.MediaColumns; import android.provider.Mtp; import android.text.format.Time; import android.util.Log; import java.io.File; Loading Loading @@ -428,6 +429,26 @@ public class MtpDatabase { } } private String queryAudio(int id, String column) { Cursor c = null; try { c = mMediaProvider.query(Audio.Media.getContentUri(mVolumeName), new String [] { Files.FileColumns._ID, column }, ID_WHERE, new String[] { Integer.toString(id) }, null); if (c != null && c.moveToNext()) { return c.getString(1); } else { return ""; } } catch (Exception e) { return null; } finally { if (c != null) { c.close(); } } } private String queryGenre(int id) { Cursor c = null; try { Loading @@ -450,7 +471,7 @@ public class MtpDatabase { } } private boolean queryInt(int id, String column, long[] outValue) { private Long queryLong(int id, String column) { Cursor c = null; try { // for now we are only reading properties from the "objects" table Loading @@ -458,17 +479,15 @@ public class MtpDatabase { new String [] { Files.FileColumns._ID, column }, ID_WHERE, new String[] { Integer.toString(id) }, null); if (c != null && c.moveToNext()) { outValue[0] = c.getLong(1); return true; return new Long(c.getLong(1)); } return false; } catch (Exception e) { return false; } finally { if (c != null) { c.close(); } } return null; } private String nameFromPath(String path) { Loading @@ -485,98 +504,165 @@ public class MtpDatabase { return path.substring(start, end); } private int renameFile(int handle, String newName) { Cursor c = null; // first compute current path String path = null; String[] whereArgs = new String[] { Integer.toString(handle) }; try { c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null); if (c != null && c.moveToNext()) { path = externalToMediaPath(c.getString(1)); } } catch (RemoteException e) { Log.e(TAG, "RemoteException in getObjectFilePath", e); return MtpConstants.RESPONSE_GENERAL_ERROR; } finally { if (c != null) { c.close(); private String formatDateTime(long seconds) { Time time = new Time(Time.TIMEZONE_UTC); time.set(seconds * 1000); String result = time.format("%Y-%m-%dT%H:%M:%SZ"); Log.d(TAG, "formatDateTime returning " + result); return result; } private MtpPropertyList getObjectPropertyList(int handle, int format, int property, int groupCode, int depth) { // FIXME - implement group support // For now we only support a single property at a time if (groupCode != 0) { return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED); } if (path == null) { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; if (depth > 1) { return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED); } // now rename the file. make sure this succeeds before updating database File oldFile = new File(path); int lastSlash = path.lastIndexOf('/'); if (lastSlash <= 1) { return MtpConstants.RESPONSE_GENERAL_ERROR; } String newPath = path.substring(0, lastSlash + 1) + newName; File newFile = new File(newPath); boolean success = oldFile.renameTo(newFile); Log.d(TAG, "renaming "+ path + " to " + newPath + (success ? " succeeded" : " failed")); if (!success) { return MtpConstants.RESPONSE_GENERAL_ERROR; String column = null; int type = MtpConstants.TYPE_UNDEFINED; switch (property) { case MtpConstants.PROPERTY_STORAGE_ID: // no query needed until we support multiple storage units // for now it is always mStorageID type = MtpConstants.TYPE_UINT32; break; case MtpConstants.PROPERTY_OBJECT_FORMAT: column = Files.FileColumns.FORMAT; type = MtpConstants.TYPE_UINT16; break; case MtpConstants.PROPERTY_PROTECTION_STATUS: // protection status is always 0 type = MtpConstants.TYPE_UINT16; break; case MtpConstants.PROPERTY_OBJECT_SIZE: column = Files.FileColumns.SIZE; type = MtpConstants.TYPE_UINT64; break; case MtpConstants.PROPERTY_OBJECT_FILE_NAME: column = Files.FileColumns.DATA; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_NAME: column = MediaColumns.TITLE; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_DATE_MODIFIED: column = Files.FileColumns.DATE_MODIFIED; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_DATE_ADDED: column = Files.FileColumns.DATE_ADDED; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE: column = Audio.AudioColumns.YEAR; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_PARENT_OBJECT: column = Files.FileColumns.PARENT; type = MtpConstants.TYPE_UINT32; break; case MtpConstants.PROPERTY_PERSISTENT_UID: // PUID is concatenation of storageID and object handle type = MtpConstants.TYPE_UINT128; break; case MtpConstants.PROPERTY_DURATION: column = Audio.AudioColumns.DURATION; type = MtpConstants.TYPE_UINT32; break; case MtpConstants.PROPERTY_TRACK: column = Audio.AudioColumns.TRACK; type = MtpConstants.TYPE_UINT16; break; case MtpConstants.PROPERTY_DISPLAY_NAME: column = MediaColumns.DISPLAY_NAME; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ARTIST: type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ALBUM_NAME: type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ALBUM_ARTIST: column = Audio.AudioColumns.ALBUM_ARTIST; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_GENRE: // genre requires a special query type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_COMPOSER: column = Audio.AudioColumns.COMPOSER; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_DESCRIPTION: column = Images.ImageColumns.DESCRIPTION; type = MtpConstants.TYPE_STR; break; default: return new MtpPropertyList(0, MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED); } // finally update database ContentValues values = new ContentValues(); values.put(Files.FileColumns.DATA, newPath); int updated = 0; Cursor c = null; try { // note - we are relying on a special case in MediaProvider.update() to update // the paths for all children in the case where this is a directory. updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs); } catch (RemoteException e) { Log.e(TAG, "RemoteException in mMediaProvider.update", e); if (column != null) { c = mMediaProvider.query(mObjectsUri, new String [] { Files.FileColumns._ID, column }, // depth 0: single record, depth 1: immediate children (depth == 0 ? ID_WHERE : PARENT_WHERE), new String[] { Integer.toString(handle) }, null); if (c == null) { return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } if (updated == 0) { Log.e(TAG, "Unable to update path for " + path + " to " + newPath); // this shouldn't happen, but if it does we need to rename the file to its original name newFile.renameTo(oldFile); return MtpConstants.RESPONSE_GENERAL_ERROR; } else if (depth == 1) { c = mMediaProvider.query(mObjectsUri, new String [] { Files.FileColumns._ID }, PARENT_WHERE, new String[] { Integer.toString(handle) }, null); if (c == null) { return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } return MtpConstants.RESPONSE_OK; } private int getObjectProperty(int handle, int property, long[] outIntValue, char[] outStringValue) { Log.d(TAG, "getObjectProperty: " + property); String column = null; boolean isString = false; int count = (c == null ? 1 : c.getCount()); MtpPropertyList result = new MtpPropertyList(count, MtpConstants.RESPONSE_OK); for (int index = 0; index < count; index++) { if (c != null) { c.moveToNext(); } if (depth == 1) { handle = (int)c.getLong(0); } switch (property) { // handle special cases here case MtpConstants.PROPERTY_STORAGE_ID: outIntValue[0] = mStorageID; return MtpConstants.RESPONSE_OK; case MtpConstants.PROPERTY_OBJECT_FORMAT: column = Files.FileColumns.FORMAT; result.setProperty(index, handle, property, MtpConstants.TYPE_UINT32, mStorageID); break; case MtpConstants.PROPERTY_PROTECTION_STATUS: // protection status is always 0 outIntValue[0] = 0; return MtpConstants.RESPONSE_OK; case MtpConstants.PROPERTY_OBJECT_SIZE: column = Files.FileColumns.SIZE; result.setProperty(index, handle, property, MtpConstants.TYPE_UINT16, 0); break; case MtpConstants.PROPERTY_OBJECT_FILE_NAME: // special case - need to extract file name from full path String value = queryString(handle, Files.FileColumns.DATA); String value = c.getString(1); if (value != null) { value = nameFromPath(value); value.getChars(0, value.length(), outStringValue, 0); outStringValue[value.length()] = 0; return MtpConstants.RESPONSE_OK; result.setProperty(index, handle, property, nameFromPath(value)); } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } break; case MtpConstants.PROPERTY_NAME: // first try title String name = queryString(handle, MediaColumns.TITLE); String name = c.getString(1); // then try name if (name == null) { name = queryString(handle, Audio.PlaylistsColumns.NAME); Loading @@ -589,95 +675,125 @@ public class MtpDatabase { } } if (name != null) { name.getChars(0, name.length(), outStringValue, 0); outStringValue[name.length()] = 0; return MtpConstants.RESPONSE_OK; result.setProperty(index, handle, property, name); } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } case MtpConstants.PROPERTY_DATE_MODIFIED: column = Files.FileColumns.DATE_MODIFIED; break; case MtpConstants.PROPERTY_DATE_MODIFIED: case MtpConstants.PROPERTY_DATE_ADDED: column = Files.FileColumns.DATE_ADDED; // convert from seconds to DateTime result.setProperty(index, handle, property, formatDateTime(c.getInt(1))); break; case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE: column = Audio.AudioColumns.YEAR; break; case MtpConstants.PROPERTY_PARENT_OBJECT: column = Files.FileColumns.PARENT; // release date is stored internally as just the year int year = c.getInt(1); String dateTime = Integer.toString(year) + "0101T000000"; result.setProperty(index, handle, property, dateTime); break; case MtpConstants.PROPERTY_PERSISTENT_UID: // PUID is concatenation of storageID and object handle long puid = mStorageID; puid <<= 32; puid += handle; outIntValue[0] = puid; return MtpConstants.RESPONSE_OK; case MtpConstants.PROPERTY_DURATION: column = Audio.AudioColumns.DURATION; result.setProperty(index, handle, property, MtpConstants.TYPE_UINT128, puid); break; case MtpConstants.PROPERTY_TRACK: if (queryInt(handle, Audio.AudioColumns.TRACK, outIntValue)) { // track is stored in lower 3 decimal digits outIntValue[0] %= 1000; return MtpConstants.RESPONSE_OK; } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; } case MtpConstants.PROPERTY_DISPLAY_NAME: column = MediaColumns.DISPLAY_NAME; isString = true; result.setProperty(index, handle, property, MtpConstants.TYPE_UINT16, c.getInt(1) % 1000); break; case MtpConstants.PROPERTY_ARTIST: column = Audio.AudioColumns.ARTIST; isString = true; result.setProperty(index, handle, property, queryAudio(handle, Audio.AudioColumns.ARTIST)); break; case MtpConstants.PROPERTY_ALBUM_NAME: column = Audio.AudioColumns.ALBUM; isString = true; break; case MtpConstants.PROPERTY_ALBUM_ARTIST: column = Audio.AudioColumns.ALBUM_ARTIST; isString = true; result.setProperty(index, handle, property, queryAudio(handle, Audio.AudioColumns.ALBUM)); break; case MtpConstants.PROPERTY_GENRE: String genre = queryGenre(handle); if (genre != null) { genre.getChars(0, genre.length(), outStringValue, 0); outStringValue[genre.length()] = 0; return MtpConstants.RESPONSE_OK; result.setProperty(index, handle, property, genre); } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } case MtpConstants.PROPERTY_COMPOSER: column = Audio.AudioColumns.COMPOSER; isString = true; break; case MtpConstants.PROPERTY_DESCRIPTION: column = Images.ImageColumns.DESCRIPTION; isString = true; break; default: return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED; if (type == MtpConstants.TYPE_STR) { result.setProperty(index, handle, property, c.getString(1)); } else { result.setProperty(index, handle, property, type, c.getLong(1)); } } } if (isString) { String value = queryString(handle, column); if (value != null) { value.getChars(0, value.length(), outStringValue, 0); outStringValue[value.length()] = 0; return MtpConstants.RESPONSE_OK; return result; } catch (RemoteException e) { return new MtpPropertyList(0, MtpConstants.RESPONSE_GENERAL_ERROR); } finally { if (c != null) { c.close(); } } else { if (queryInt(handle, column, outIntValue)) { return MtpConstants.RESPONSE_OK; } // impossible to get here, so no return statement } // query failed if we get here private int renameFile(int handle, String newName) { Cursor c = null; // first compute current path String path = null; String[] whereArgs = new String[] { Integer.toString(handle) }; try { c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null); if (c != null && c.moveToNext()) { path = externalToMediaPath(c.getString(1)); } } catch (RemoteException e) { Log.e(TAG, "RemoteException in getObjectFilePath", e); return MtpConstants.RESPONSE_GENERAL_ERROR; } finally { if (c != null) { c.close(); } } if (path == null) { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; } // now rename the file. make sure this succeeds before updating database File oldFile = new File(path); int lastSlash = path.lastIndexOf('/'); if (lastSlash <= 1) { return MtpConstants.RESPONSE_GENERAL_ERROR; } String newPath = path.substring(0, lastSlash + 1) + newName; File newFile = new File(newPath); boolean success = oldFile.renameTo(newFile); Log.d(TAG, "renaming "+ path + " to " + newPath + (success ? " succeeded" : " failed")); if (!success) { return MtpConstants.RESPONSE_GENERAL_ERROR; } // finally update database ContentValues values = new ContentValues(); values.put(Files.FileColumns.DATA, newPath); int updated = 0; try { // note - we are relying on a special case in MediaProvider.update() to update // the paths for all children in the case where this is a directory. updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs); } catch (RemoteException e) { Log.e(TAG, "RemoteException in mMediaProvider.update", e); } if (updated == 0) { Log.e(TAG, "Unable to update path for " + path + " to " + newPath); // this shouldn't happen, but if it does we need to rename the file to its original name newFile.renameTo(oldFile); return MtpConstants.RESPONSE_GENERAL_ERROR; } return MtpConstants.RESPONSE_OK; } private int setObjectProperty(int handle, int property, long intValue, String stringValue) { Log.d(TAG, "setObjectProperty: " + property); Loading media/java/android/media/MtpPropertyList.java 0 → 100644 +76 −0 Original line number Diff line number Diff line /* * 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; /** * Encapsulates the ObjectPropList dataset used by the GetObjectPropList command. * The fields of this class are read by JNI code in android_media_MtpDatabase.cpp * * {@hide} */ public class MtpPropertyList { // number of results returned public final int mCount; // result code for GetObjectPropList public int mResult; // list of object handles (first field in quadruplet) public final int[] mObjectHandles; // list of object propery codes (second field in quadruplet) public final int[] mPropertyCodes; // list of data type codes (third field in quadruplet) public final int[] mDataTypes; // list of long int property values (fourth field in quadruplet, when value is integer type) public long[] mLongValues; // list of long int property values (fourth field in quadruplet, when value is string type) public String[] mStringValues; // constructor only called from MtpDatabase public MtpPropertyList(int count, int result) { mCount = count; mResult = result; mObjectHandles = new int[count]; mPropertyCodes = new int[count]; mDataTypes = new int[count]; // mLongValues and mStringValues are created lazily since both might not be necessary } public void setProperty(int index, int handle, int property, int type, long value) { if (mLongValues == null) { mLongValues = new long[mCount]; } mObjectHandles[index] = handle; mPropertyCodes[index] = property; mDataTypes[index] = type; mLongValues[index] = value; } public void setProperty(int index, int handle, int property, String value) { if (mStringValues == null) { mStringValues = new String[mCount]; } mObjectHandles[index] = handle; mPropertyCodes[index] = property; mDataTypes[index] = MtpConstants.TYPE_STR; mStringValues[index] = value; } public void setResult(int result) { mResult = result; } } Loading
media/java/android/media/MtpConstants.java +24 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,30 @@ package android.media; */ public final class MtpConstants { // MTP Data Types public static final int TYPE_UNDEFINED = 0x0000; public static final int TYPE_INT8 = 0x0001; public static final int TYPE_UINT8 = 0x0002; public static final int TYPE_INT16 = 0x0003; public static final int TYPE_UINT16 = 0x0004; public static final int TYPE_INT32 = 0x0005; public static final int TYPE_UINT32 = 0x0006; public static final int TYPE_INT64 = 0x0007; public static final int TYPE_UINT64 = 0x0008; public static final int TYPE_INT128 = 0x0009; public static final int TYPE_UINT128 = 0x000A; public static final int TYPE_AINT8 = 0x4001; public static final int TYPE_AUINT8 = 0x4002; public static final int TYPE_AINT16 = 0x4003; public static final int TYPE_AUINT16 = 0x4004; public static final int TYPE_AINT32 = 0x4005; public static final int TYPE_AUINT32 = 0x4006; public static final int TYPE_AINT64 = 0x4007; public static final int TYPE_AUINT64 = 0x4008; public static final int TYPE_AINT128 = 0x4009; public static final int TYPE_AUINT128 = 0x400A; public static final int TYPE_STR = 0xFFFF; // MTP Response Codes public static final int RESPONSE_UNDEFINED = 0x2000; public static final int RESPONSE_OK = 0x2001; Loading
media/java/android/media/MtpDatabase.java +255 −139 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.provider.MediaStore.Files; import android.provider.MediaStore.Images; import android.provider.MediaStore.MediaColumns; import android.provider.Mtp; import android.text.format.Time; import android.util.Log; import java.io.File; Loading Loading @@ -428,6 +429,26 @@ public class MtpDatabase { } } private String queryAudio(int id, String column) { Cursor c = null; try { c = mMediaProvider.query(Audio.Media.getContentUri(mVolumeName), new String [] { Files.FileColumns._ID, column }, ID_WHERE, new String[] { Integer.toString(id) }, null); if (c != null && c.moveToNext()) { return c.getString(1); } else { return ""; } } catch (Exception e) { return null; } finally { if (c != null) { c.close(); } } } private String queryGenre(int id) { Cursor c = null; try { Loading @@ -450,7 +471,7 @@ public class MtpDatabase { } } private boolean queryInt(int id, String column, long[] outValue) { private Long queryLong(int id, String column) { Cursor c = null; try { // for now we are only reading properties from the "objects" table Loading @@ -458,17 +479,15 @@ public class MtpDatabase { new String [] { Files.FileColumns._ID, column }, ID_WHERE, new String[] { Integer.toString(id) }, null); if (c != null && c.moveToNext()) { outValue[0] = c.getLong(1); return true; return new Long(c.getLong(1)); } return false; } catch (Exception e) { return false; } finally { if (c != null) { c.close(); } } return null; } private String nameFromPath(String path) { Loading @@ -485,98 +504,165 @@ public class MtpDatabase { return path.substring(start, end); } private int renameFile(int handle, String newName) { Cursor c = null; // first compute current path String path = null; String[] whereArgs = new String[] { Integer.toString(handle) }; try { c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null); if (c != null && c.moveToNext()) { path = externalToMediaPath(c.getString(1)); } } catch (RemoteException e) { Log.e(TAG, "RemoteException in getObjectFilePath", e); return MtpConstants.RESPONSE_GENERAL_ERROR; } finally { if (c != null) { c.close(); private String formatDateTime(long seconds) { Time time = new Time(Time.TIMEZONE_UTC); time.set(seconds * 1000); String result = time.format("%Y-%m-%dT%H:%M:%SZ"); Log.d(TAG, "formatDateTime returning " + result); return result; } private MtpPropertyList getObjectPropertyList(int handle, int format, int property, int groupCode, int depth) { // FIXME - implement group support // For now we only support a single property at a time if (groupCode != 0) { return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED); } if (path == null) { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; if (depth > 1) { return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED); } // now rename the file. make sure this succeeds before updating database File oldFile = new File(path); int lastSlash = path.lastIndexOf('/'); if (lastSlash <= 1) { return MtpConstants.RESPONSE_GENERAL_ERROR; } String newPath = path.substring(0, lastSlash + 1) + newName; File newFile = new File(newPath); boolean success = oldFile.renameTo(newFile); Log.d(TAG, "renaming "+ path + " to " + newPath + (success ? " succeeded" : " failed")); if (!success) { return MtpConstants.RESPONSE_GENERAL_ERROR; String column = null; int type = MtpConstants.TYPE_UNDEFINED; switch (property) { case MtpConstants.PROPERTY_STORAGE_ID: // no query needed until we support multiple storage units // for now it is always mStorageID type = MtpConstants.TYPE_UINT32; break; case MtpConstants.PROPERTY_OBJECT_FORMAT: column = Files.FileColumns.FORMAT; type = MtpConstants.TYPE_UINT16; break; case MtpConstants.PROPERTY_PROTECTION_STATUS: // protection status is always 0 type = MtpConstants.TYPE_UINT16; break; case MtpConstants.PROPERTY_OBJECT_SIZE: column = Files.FileColumns.SIZE; type = MtpConstants.TYPE_UINT64; break; case MtpConstants.PROPERTY_OBJECT_FILE_NAME: column = Files.FileColumns.DATA; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_NAME: column = MediaColumns.TITLE; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_DATE_MODIFIED: column = Files.FileColumns.DATE_MODIFIED; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_DATE_ADDED: column = Files.FileColumns.DATE_ADDED; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE: column = Audio.AudioColumns.YEAR; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_PARENT_OBJECT: column = Files.FileColumns.PARENT; type = MtpConstants.TYPE_UINT32; break; case MtpConstants.PROPERTY_PERSISTENT_UID: // PUID is concatenation of storageID and object handle type = MtpConstants.TYPE_UINT128; break; case MtpConstants.PROPERTY_DURATION: column = Audio.AudioColumns.DURATION; type = MtpConstants.TYPE_UINT32; break; case MtpConstants.PROPERTY_TRACK: column = Audio.AudioColumns.TRACK; type = MtpConstants.TYPE_UINT16; break; case MtpConstants.PROPERTY_DISPLAY_NAME: column = MediaColumns.DISPLAY_NAME; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ARTIST: type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ALBUM_NAME: type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ALBUM_ARTIST: column = Audio.AudioColumns.ALBUM_ARTIST; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_GENRE: // genre requires a special query type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_COMPOSER: column = Audio.AudioColumns.COMPOSER; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_DESCRIPTION: column = Images.ImageColumns.DESCRIPTION; type = MtpConstants.TYPE_STR; break; default: return new MtpPropertyList(0, MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED); } // finally update database ContentValues values = new ContentValues(); values.put(Files.FileColumns.DATA, newPath); int updated = 0; Cursor c = null; try { // note - we are relying on a special case in MediaProvider.update() to update // the paths for all children in the case where this is a directory. updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs); } catch (RemoteException e) { Log.e(TAG, "RemoteException in mMediaProvider.update", e); if (column != null) { c = mMediaProvider.query(mObjectsUri, new String [] { Files.FileColumns._ID, column }, // depth 0: single record, depth 1: immediate children (depth == 0 ? ID_WHERE : PARENT_WHERE), new String[] { Integer.toString(handle) }, null); if (c == null) { return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } if (updated == 0) { Log.e(TAG, "Unable to update path for " + path + " to " + newPath); // this shouldn't happen, but if it does we need to rename the file to its original name newFile.renameTo(oldFile); return MtpConstants.RESPONSE_GENERAL_ERROR; } else if (depth == 1) { c = mMediaProvider.query(mObjectsUri, new String [] { Files.FileColumns._ID }, PARENT_WHERE, new String[] { Integer.toString(handle) }, null); if (c == null) { return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } return MtpConstants.RESPONSE_OK; } private int getObjectProperty(int handle, int property, long[] outIntValue, char[] outStringValue) { Log.d(TAG, "getObjectProperty: " + property); String column = null; boolean isString = false; int count = (c == null ? 1 : c.getCount()); MtpPropertyList result = new MtpPropertyList(count, MtpConstants.RESPONSE_OK); for (int index = 0; index < count; index++) { if (c != null) { c.moveToNext(); } if (depth == 1) { handle = (int)c.getLong(0); } switch (property) { // handle special cases here case MtpConstants.PROPERTY_STORAGE_ID: outIntValue[0] = mStorageID; return MtpConstants.RESPONSE_OK; case MtpConstants.PROPERTY_OBJECT_FORMAT: column = Files.FileColumns.FORMAT; result.setProperty(index, handle, property, MtpConstants.TYPE_UINT32, mStorageID); break; case MtpConstants.PROPERTY_PROTECTION_STATUS: // protection status is always 0 outIntValue[0] = 0; return MtpConstants.RESPONSE_OK; case MtpConstants.PROPERTY_OBJECT_SIZE: column = Files.FileColumns.SIZE; result.setProperty(index, handle, property, MtpConstants.TYPE_UINT16, 0); break; case MtpConstants.PROPERTY_OBJECT_FILE_NAME: // special case - need to extract file name from full path String value = queryString(handle, Files.FileColumns.DATA); String value = c.getString(1); if (value != null) { value = nameFromPath(value); value.getChars(0, value.length(), outStringValue, 0); outStringValue[value.length()] = 0; return MtpConstants.RESPONSE_OK; result.setProperty(index, handle, property, nameFromPath(value)); } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } break; case MtpConstants.PROPERTY_NAME: // first try title String name = queryString(handle, MediaColumns.TITLE); String name = c.getString(1); // then try name if (name == null) { name = queryString(handle, Audio.PlaylistsColumns.NAME); Loading @@ -589,95 +675,125 @@ public class MtpDatabase { } } if (name != null) { name.getChars(0, name.length(), outStringValue, 0); outStringValue[name.length()] = 0; return MtpConstants.RESPONSE_OK; result.setProperty(index, handle, property, name); } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } case MtpConstants.PROPERTY_DATE_MODIFIED: column = Files.FileColumns.DATE_MODIFIED; break; case MtpConstants.PROPERTY_DATE_MODIFIED: case MtpConstants.PROPERTY_DATE_ADDED: column = Files.FileColumns.DATE_ADDED; // convert from seconds to DateTime result.setProperty(index, handle, property, formatDateTime(c.getInt(1))); break; case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE: column = Audio.AudioColumns.YEAR; break; case MtpConstants.PROPERTY_PARENT_OBJECT: column = Files.FileColumns.PARENT; // release date is stored internally as just the year int year = c.getInt(1); String dateTime = Integer.toString(year) + "0101T000000"; result.setProperty(index, handle, property, dateTime); break; case MtpConstants.PROPERTY_PERSISTENT_UID: // PUID is concatenation of storageID and object handle long puid = mStorageID; puid <<= 32; puid += handle; outIntValue[0] = puid; return MtpConstants.RESPONSE_OK; case MtpConstants.PROPERTY_DURATION: column = Audio.AudioColumns.DURATION; result.setProperty(index, handle, property, MtpConstants.TYPE_UINT128, puid); break; case MtpConstants.PROPERTY_TRACK: if (queryInt(handle, Audio.AudioColumns.TRACK, outIntValue)) { // track is stored in lower 3 decimal digits outIntValue[0] %= 1000; return MtpConstants.RESPONSE_OK; } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; } case MtpConstants.PROPERTY_DISPLAY_NAME: column = MediaColumns.DISPLAY_NAME; isString = true; result.setProperty(index, handle, property, MtpConstants.TYPE_UINT16, c.getInt(1) % 1000); break; case MtpConstants.PROPERTY_ARTIST: column = Audio.AudioColumns.ARTIST; isString = true; result.setProperty(index, handle, property, queryAudio(handle, Audio.AudioColumns.ARTIST)); break; case MtpConstants.PROPERTY_ALBUM_NAME: column = Audio.AudioColumns.ALBUM; isString = true; break; case MtpConstants.PROPERTY_ALBUM_ARTIST: column = Audio.AudioColumns.ALBUM_ARTIST; isString = true; result.setProperty(index, handle, property, queryAudio(handle, Audio.AudioColumns.ALBUM)); break; case MtpConstants.PROPERTY_GENRE: String genre = queryGenre(handle); if (genre != null) { genre.getChars(0, genre.length(), outStringValue, 0); outStringValue[genre.length()] = 0; return MtpConstants.RESPONSE_OK; result.setProperty(index, handle, property, genre); } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } case MtpConstants.PROPERTY_COMPOSER: column = Audio.AudioColumns.COMPOSER; isString = true; break; case MtpConstants.PROPERTY_DESCRIPTION: column = Images.ImageColumns.DESCRIPTION; isString = true; break; default: return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED; if (type == MtpConstants.TYPE_STR) { result.setProperty(index, handle, property, c.getString(1)); } else { result.setProperty(index, handle, property, type, c.getLong(1)); } } } if (isString) { String value = queryString(handle, column); if (value != null) { value.getChars(0, value.length(), outStringValue, 0); outStringValue[value.length()] = 0; return MtpConstants.RESPONSE_OK; return result; } catch (RemoteException e) { return new MtpPropertyList(0, MtpConstants.RESPONSE_GENERAL_ERROR); } finally { if (c != null) { c.close(); } } else { if (queryInt(handle, column, outIntValue)) { return MtpConstants.RESPONSE_OK; } // impossible to get here, so no return statement } // query failed if we get here private int renameFile(int handle, String newName) { Cursor c = null; // first compute current path String path = null; String[] whereArgs = new String[] { Integer.toString(handle) }; try { c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null); if (c != null && c.moveToNext()) { path = externalToMediaPath(c.getString(1)); } } catch (RemoteException e) { Log.e(TAG, "RemoteException in getObjectFilePath", e); return MtpConstants.RESPONSE_GENERAL_ERROR; } finally { if (c != null) { c.close(); } } if (path == null) { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; } // now rename the file. make sure this succeeds before updating database File oldFile = new File(path); int lastSlash = path.lastIndexOf('/'); if (lastSlash <= 1) { return MtpConstants.RESPONSE_GENERAL_ERROR; } String newPath = path.substring(0, lastSlash + 1) + newName; File newFile = new File(newPath); boolean success = oldFile.renameTo(newFile); Log.d(TAG, "renaming "+ path + " to " + newPath + (success ? " succeeded" : " failed")); if (!success) { return MtpConstants.RESPONSE_GENERAL_ERROR; } // finally update database ContentValues values = new ContentValues(); values.put(Files.FileColumns.DATA, newPath); int updated = 0; try { // note - we are relying on a special case in MediaProvider.update() to update // the paths for all children in the case where this is a directory. updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs); } catch (RemoteException e) { Log.e(TAG, "RemoteException in mMediaProvider.update", e); } if (updated == 0) { Log.e(TAG, "Unable to update path for " + path + " to " + newPath); // this shouldn't happen, but if it does we need to rename the file to its original name newFile.renameTo(oldFile); return MtpConstants.RESPONSE_GENERAL_ERROR; } return MtpConstants.RESPONSE_OK; } private int setObjectProperty(int handle, int property, long intValue, String stringValue) { Log.d(TAG, "setObjectProperty: " + property); Loading
media/java/android/media/MtpPropertyList.java 0 → 100644 +76 −0 Original line number Diff line number Diff line /* * 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; /** * Encapsulates the ObjectPropList dataset used by the GetObjectPropList command. * The fields of this class are read by JNI code in android_media_MtpDatabase.cpp * * {@hide} */ public class MtpPropertyList { // number of results returned public final int mCount; // result code for GetObjectPropList public int mResult; // list of object handles (first field in quadruplet) public final int[] mObjectHandles; // list of object propery codes (second field in quadruplet) public final int[] mPropertyCodes; // list of data type codes (third field in quadruplet) public final int[] mDataTypes; // list of long int property values (fourth field in quadruplet, when value is integer type) public long[] mLongValues; // list of long int property values (fourth field in quadruplet, when value is string type) public String[] mStringValues; // constructor only called from MtpDatabase public MtpPropertyList(int count, int result) { mCount = count; mResult = result; mObjectHandles = new int[count]; mPropertyCodes = new int[count]; mDataTypes = new int[count]; // mLongValues and mStringValues are created lazily since both might not be necessary } public void setProperty(int index, int handle, int property, int type, long value) { if (mLongValues == null) { mLongValues = new long[mCount]; } mObjectHandles[index] = handle; mPropertyCodes[index] = property; mDataTypes[index] = type; mLongValues[index] = value; } public void setProperty(int index, int handle, int property, String value) { if (mStringValues == null) { mStringValues = new String[mCount]; } mObjectHandles[index] = handle; mPropertyCodes[index] = property; mDataTypes[index] = MtpConstants.TYPE_STR; mStringValues[index] = value; } public void setResult(int result) { mResult = result; } }