| /* |
| * 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.mtp; |
| |
| import android.content.Context; |
| import android.content.ContentValues; |
| import android.content.IContentProvider; |
| import android.content.Intent; |
| import android.database.Cursor; |
| import android.database.sqlite.SQLiteDatabase; |
| import android.media.MediaScanner; |
| import android.net.Uri; |
| import android.os.Environment; |
| import android.os.RemoteException; |
| import android.provider.MediaStore; |
| import android.provider.MediaStore.Audio; |
| import android.provider.MediaStore.Files; |
| import android.provider.MediaStore.Images; |
| import android.provider.MediaStore.MediaColumns; |
| import android.util.Log; |
| import android.view.Display; |
| import android.view.WindowManager; |
| |
| import java.io.File; |
| import java.util.HashMap; |
| |
| /** |
| * {@hide} |
| */ |
| public class MtpDatabase { |
| |
| private static final String TAG = "MtpDatabase"; |
| |
| private final Context mContext; |
| private final IContentProvider mMediaProvider; |
| private final String mVolumeName; |
| private final Uri mObjectsUri; |
| private final String mMediaStoragePath; |
| |
| // cached property groups for single properties |
| private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty |
| = new HashMap<Integer, MtpPropertyGroup>(); |
| |
| // cached property groups for all properties for a given format |
| private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat |
| = new HashMap<Integer, MtpPropertyGroup>(); |
| |
| // 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; |
| |
| private static final String[] ID_PROJECTION = new String[] { |
| Files.FileColumns._ID, // 0 |
| }; |
| private static final String[] PATH_PROJECTION = new String[] { |
| Files.FileColumns._ID, // 0 |
| Files.FileColumns.DATA, // 1 |
| }; |
| private static final String[] PATH_SIZE_FORMAT_PROJECTION = new String[] { |
| Files.FileColumns._ID, // 0 |
| Files.FileColumns.DATA, // 1 |
| Files.FileColumns.SIZE, // 2 |
| Files.FileColumns.FORMAT, // 3 |
| }; |
| private static final String[] OBJECT_INFO_PROJECTION = new String[] { |
| Files.FileColumns._ID, // 0 |
| Files.FileColumns.DATA, // 1 |
| Files.FileColumns.FORMAT, // 2 |
| Files.FileColumns.PARENT, // 3 |
| Files.FileColumns.SIZE, // 4 |
| Files.FileColumns.DATE_MODIFIED, // 5 |
| }; |
| private static final String ID_WHERE = Files.FileColumns._ID + "=?"; |
| private static final String PATH_WHERE = Files.FileColumns.DATA + "=?"; |
| private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?"; |
| private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " |
| + Files.FileColumns.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 { |
| System.loadLibrary("media_jni"); |
| } |
| |
| public MtpDatabase(Context context, String volumeName, String storagePath) { |
| native_setup(); |
| |
| mContext = context; |
| mMediaProvider = context.getContentResolver().acquireProvider("media"); |
| mVolumeName = volumeName; |
| mMediaStoragePath = storagePath; |
| mObjectsUri = Files.getMtpObjectsUri(volumeName); |
| mMediaScanner = new MediaScanner(context); |
| openDevicePropertiesDatabase(context); |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| try { |
| native_finalize(); |
| } finally { |
| super.finalize(); |
| } |
| } |
| |
| 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) { |
| // first make sure the object does not exist |
| if (path != null) { |
| Cursor c = null; |
| try { |
| c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE, |
| new String[] { path }, null); |
| if (c != null && c.getCount() > 0) { |
| Log.w(TAG, "file already exists in beginSendObject: " + path); |
| return -1; |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException in beginSendObject", e); |
| } finally { |
| if (c != null) { |
| c.close(); |
| } |
| } |
| } |
| |
| mDatabaseModified = true; |
| ContentValues values = new ContentValues(); |
| values.put(Files.FileColumns.DATA, path); |
| values.put(Files.FileColumns.FORMAT, format); |
| values.put(Files.FileColumns.PARENT, parent); |
| // storage is ignored for now |
| values.put(Files.FileColumns.SIZE, size); |
| values.put(Files.FileColumns.DATE_MODIFIED, modified); |
| |
| try { |
| Uri uri = mMediaProvider.insert(mObjectsUri, values); |
| if (uri != null) { |
| return Integer.parseInt(uri.getPathSegments().get(2)); |
| } else { |
| return -1; |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException in beginSendObject", e); |
| return -1; |
| } |
| } |
| |
| private void endSendObject(String path, int handle, int format, boolean succeeded) { |
| if (succeeded) { |
| // handle abstract playlists separately |
| // they do not exist in the file system so don't use the media scanner here |
| if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) { |
| // extract name from path |
| String name = path; |
| int lastSlash = name.lastIndexOf('/'); |
| if (lastSlash >= 0) { |
| name = name.substring(lastSlash + 1); |
| } |
| // strip trailing ".pla" from the name |
| if (name.endsWith(".pla")) { |
| name = name.substring(0, name.length() - 4); |
| } |
| |
| ContentValues values = new ContentValues(1); |
| values.put(Audio.Playlists.DATA, path); |
| values.put(Audio.Playlists.NAME, name); |
| values.put(Files.FileColumns.FORMAT, format); |
| values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000); |
| 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 { |
| mMediaScanner.scanMtpFile(path, mVolumeName, handle, format); |
| } |
| } else { |
| deleteFile(handle); |
| } |
| } |
| |
| private int[] getObjectList(int storageID, int format, int parent) { |
| // we can ignore storageID until we support multiple storages |
| Cursor c = null; |
| try { |
| if (format != 0) { |
| c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, |
| PARENT_FORMAT_WHERE, |
| new String[] { Integer.toString(parent), Integer.toString(format) }, |
| null); |
| } else { |
| c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, |
| PARENT_WHERE, new String[] { Integer.toString(parent) }, null); |
| } |
| if (c == null) { |
| return 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 getNumObjects(int storageID, int format, int parent) { |
| // we can ignore storageID until we support multiple storages |
| Cursor c = null; |
| try { |
| if (format != 0) { |
| c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, |
| PARENT_FORMAT_WHERE, |
| new String[] { Integer.toString(parent), Integer.toString(format) }, |
| null); |
| } else { |
| c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, |
| PARENT_WHERE, new String[] { Integer.toString(parent) }, null); |
| } |
| if (c != null) { |
| return c.getCount(); |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException in getNumObjects", e); |
| } finally { |
| if (c != null) { |
| c.close(); |
| } |
| } |
| return -1; |
| } |
| |
| private int[] getSupportedPlaybackFormats() { |
| return new int[] { |
| // allow transfering arbitrary files |
| MtpConstants.FORMAT_UNDEFINED, |
| |
| MtpConstants.FORMAT_ASSOCIATION, |
| MtpConstants.FORMAT_TEXT, |
| MtpConstants.FORMAT_HTML, |
| MtpConstants.FORMAT_WAV, |
| MtpConstants.FORMAT_MP3, |
| MtpConstants.FORMAT_MPEG, |
| MtpConstants.FORMAT_EXIF_JPEG, |
| MtpConstants.FORMAT_TIFF_EP, |
| MtpConstants.FORMAT_GIF, |
| MtpConstants.FORMAT_JFIF, |
| MtpConstants.FORMAT_PNG, |
| MtpConstants.FORMAT_TIFF, |
| MtpConstants.FORMAT_WMA, |
| MtpConstants.FORMAT_OGG, |
| MtpConstants.FORMAT_AAC, |
| MtpConstants.FORMAT_MP4_CONTAINER, |
| MtpConstants.FORMAT_MP2, |
| MtpConstants.FORMAT_3GP_CONTAINER, |
| MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST, |
| MtpConstants.FORMAT_WPL_PLAYLIST, |
| MtpConstants.FORMAT_M3U_PLAYLIST, |
| MtpConstants.FORMAT_PLS_PLAYLIST, |
| MtpConstants.FORMAT_XML_DOCUMENT, |
| }; |
| } |
| |
| private int[] getSupportedCaptureFormats() { |
| // no capture formats yet |
| return null; |
| } |
| |
| static final int[] FILE_PROPERTIES = { |
| // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES |
| // and IMAGE_PROPERTIES below |
| MtpConstants.PROPERTY_STORAGE_ID, |
| MtpConstants.PROPERTY_OBJECT_FORMAT, |
| MtpConstants.PROPERTY_PROTECTION_STATUS, |
| MtpConstants.PROPERTY_OBJECT_SIZE, |
| MtpConstants.PROPERTY_OBJECT_FILE_NAME, |
| MtpConstants.PROPERTY_DATE_MODIFIED, |
| MtpConstants.PROPERTY_PARENT_OBJECT, |
| MtpConstants.PROPERTY_PERSISTENT_UID, |
| MtpConstants.PROPERTY_NAME, |
| MtpConstants.PROPERTY_DATE_ADDED, |
| }; |
| |
| static final int[] AUDIO_PROPERTIES = { |
| // NOTE must match FILE_PROPERTIES above |
| MtpConstants.PROPERTY_STORAGE_ID, |
| MtpConstants.PROPERTY_OBJECT_FORMAT, |
| MtpConstants.PROPERTY_PROTECTION_STATUS, |
| MtpConstants.PROPERTY_OBJECT_SIZE, |
| MtpConstants.PROPERTY_OBJECT_FILE_NAME, |
| MtpConstants.PROPERTY_DATE_MODIFIED, |
| MtpConstants.PROPERTY_PARENT_OBJECT, |
| MtpConstants.PROPERTY_PERSISTENT_UID, |
| MtpConstants.PROPERTY_NAME, |
| MtpConstants.PROPERTY_DISPLAY_NAME, |
| MtpConstants.PROPERTY_DATE_ADDED, |
| |
| // audio specific properties |
| MtpConstants.PROPERTY_ARTIST, |
| MtpConstants.PROPERTY_ALBUM_NAME, |
| MtpConstants.PROPERTY_ALBUM_ARTIST, |
| MtpConstants.PROPERTY_TRACK, |
| MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE, |
| MtpConstants.PROPERTY_DURATION, |
| MtpConstants.PROPERTY_GENRE, |
| MtpConstants.PROPERTY_COMPOSER, |
| }; |
| |
| static final int[] VIDEO_PROPERTIES = { |
| // NOTE must match FILE_PROPERTIES above |
| MtpConstants.PROPERTY_STORAGE_ID, |
| MtpConstants.PROPERTY_OBJECT_FORMAT, |
| MtpConstants.PROPERTY_PROTECTION_STATUS, |
| MtpConstants.PROPERTY_OBJECT_SIZE, |
| MtpConstants.PROPERTY_OBJECT_FILE_NAME, |
| MtpConstants.PROPERTY_DATE_MODIFIED, |
| MtpConstants.PROPERTY_PARENT_OBJECT, |
| MtpConstants.PROPERTY_PERSISTENT_UID, |
| MtpConstants.PROPERTY_NAME, |
| MtpConstants.PROPERTY_DISPLAY_NAME, |
| MtpConstants.PROPERTY_DATE_ADDED, |
| |
| // video specific properties |
| MtpConstants.PROPERTY_ARTIST, |
| MtpConstants.PROPERTY_ALBUM_NAME, |
| MtpConstants.PROPERTY_DURATION, |
| MtpConstants.PROPERTY_DESCRIPTION, |
| }; |
| |
| static final int[] IMAGE_PROPERTIES = { |
| // NOTE must match FILE_PROPERTIES above |
| MtpConstants.PROPERTY_STORAGE_ID, |
| MtpConstants.PROPERTY_OBJECT_FORMAT, |
| MtpConstants.PROPERTY_PROTECTION_STATUS, |
| MtpConstants.PROPERTY_OBJECT_SIZE, |
| MtpConstants.PROPERTY_OBJECT_FILE_NAME, |
| MtpConstants.PROPERTY_DATE_MODIFIED, |
| MtpConstants.PROPERTY_PARENT_OBJECT, |
| MtpConstants.PROPERTY_PERSISTENT_UID, |
| MtpConstants.PROPERTY_NAME, |
| MtpConstants.PROPERTY_DISPLAY_NAME, |
| MtpConstants.PROPERTY_DATE_ADDED, |
| |
| // image specific properties |
| MtpConstants.PROPERTY_DESCRIPTION, |
| }; |
| |
| static final int[] ALL_PROPERTIES = { |
| // NOTE must match FILE_PROPERTIES above |
| MtpConstants.PROPERTY_STORAGE_ID, |
| MtpConstants.PROPERTY_OBJECT_FORMAT, |
| MtpConstants.PROPERTY_PROTECTION_STATUS, |
| MtpConstants.PROPERTY_OBJECT_SIZE, |
| MtpConstants.PROPERTY_OBJECT_FILE_NAME, |
| MtpConstants.PROPERTY_DATE_MODIFIED, |
| MtpConstants.PROPERTY_PARENT_OBJECT, |
| MtpConstants.PROPERTY_PERSISTENT_UID, |
| MtpConstants.PROPERTY_NAME, |
| MtpConstants.PROPERTY_DISPLAY_NAME, |
| MtpConstants.PROPERTY_DATE_ADDED, |
| |
| // image specific properties |
| MtpConstants.PROPERTY_DESCRIPTION, |
| |
| // audio specific properties |
| MtpConstants.PROPERTY_ARTIST, |
| MtpConstants.PROPERTY_ALBUM_NAME, |
| MtpConstants.PROPERTY_ALBUM_ARTIST, |
| MtpConstants.PROPERTY_TRACK, |
| MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE, |
| MtpConstants.PROPERTY_DURATION, |
| MtpConstants.PROPERTY_GENRE, |
| MtpConstants.PROPERTY_COMPOSER, |
| |
| // video specific properties |
| MtpConstants.PROPERTY_ARTIST, |
| MtpConstants.PROPERTY_ALBUM_NAME, |
| MtpConstants.PROPERTY_DURATION, |
| MtpConstants.PROPERTY_DESCRIPTION, |
| |
| // image specific properties |
| MtpConstants.PROPERTY_DESCRIPTION, |
| }; |
| |
| private int[] getSupportedObjectProperties(int format) { |
| switch (format) { |
| case MtpConstants.FORMAT_MP3: |
| case MtpConstants.FORMAT_WAV: |
| case MtpConstants.FORMAT_WMA: |
| case MtpConstants.FORMAT_OGG: |
| case MtpConstants.FORMAT_AAC: |
| return AUDIO_PROPERTIES; |
| case MtpConstants.FORMAT_MPEG: |
| case MtpConstants.FORMAT_3GP_CONTAINER: |
| case MtpConstants.FORMAT_WMV: |
| return VIDEO_PROPERTIES; |
| case MtpConstants.FORMAT_EXIF_JPEG: |
| case MtpConstants.FORMAT_GIF: |
| case MtpConstants.FORMAT_PNG: |
| case MtpConstants.FORMAT_BMP: |
| return IMAGE_PROPERTIES; |
| case 0: |
| return ALL_PROPERTIES; |
| default: |
| return FILE_PROPERTIES; |
| } |
| } |
| |
| private int[] getSupportedDeviceProperties() { |
| return new int[] { |
| MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER, |
| MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, |
| MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE, |
| }; |
| } |
| |
| |
| private MtpPropertyList getObjectPropertyList(long handle, int format, long property, |
| int groupCode, int depth) { |
| // FIXME - implement group support |
| if (groupCode != 0) { |
| return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED); |
| } |
| |
| MtpPropertyGroup propertyGroup; |
| if (property == 0xFFFFFFFFL) { |
| propertyGroup = mPropertyGroupsByFormat.get(format); |
| if (propertyGroup == null) { |
| int[] propertyList = getSupportedObjectProperties(format); |
| propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList); |
| mPropertyGroupsByFormat.put(new Integer(format), propertyGroup); |
| } |
| } else { |
| propertyGroup = mPropertyGroupsByProperty.get(property); |
| if (propertyGroup == null) { |
| int[] propertyList = new int[] { (int)property }; |
| propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList); |
| mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup); |
| } |
| } |
| |
| return propertyGroup.getPropertyList((int)handle, format, depth, mStorageID); |
| } |
| |
| 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 = 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); |
| if (!success) { |
| Log.w(TAG, "renaming "+ path + " to " + newPath + " failed"); |
| 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) { |
| switch (property) { |
| case MtpConstants.PROPERTY_OBJECT_FILE_NAME: |
| return renameFile(handle, stringValue); |
| |
| default: |
| return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED; |
| } |
| } |
| |
| private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) { |
| 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(); |
| } |
| } |
| |
| case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE: |
| // use screen size as max image size |
| Display display = ((WindowManager)mContext.getSystemService( |
| Context.WINDOW_SERVICE)).getDefaultDisplay(); |
| int width = display.getWidth(); |
| int height = display.getHeight(); |
| String imageSize = Integer.toString(width) + "x" + Integer.toString(height); |
| imageSize.getChars(0, imageSize.length(), outStringValue, 0); |
| outStringValue[imageSize.length()] = 0; |
| return MtpConstants.RESPONSE_OK; |
| |
| default: |
| return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED; |
| } |
| } |
| |
| private int setDeviceProperty(int property, long intValue, String 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) { |
| Cursor c = null; |
| try { |
| c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION, |
| ID_WHERE, new String[] { Integer.toString(handle) }, null); |
| if (c != null && c.moveToNext()) { |
| outStorageFormatParent[0] = mStorageID; |
| outStorageFormatParent[1] = c.getInt(2); |
| outStorageFormatParent[2] = c.getInt(3); |
| |
| // extract name from path |
| String path = c.getString(1); |
| int lastSlash = path.lastIndexOf('/'); |
| int start = (lastSlash >= 0 ? lastSlash + 1 : 0); |
| int end = path.length(); |
| if (end - start > 255) { |
| end = start + 255; |
| } |
| path.getChars(start, end, outName, 0); |
| outName[end - start] = 0; |
| |
| outSizeModified[0] = c.getLong(4); |
| outSizeModified[1] = c.getLong(5); |
| return true; |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException in getObjectInfo", e); |
| } finally { |
| if (c != null) { |
| c.close(); |
| } |
| } |
| return false; |
| } |
| |
| private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) { |
| if (handle == 0) { |
| // special case root directory |
| mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0); |
| outFilePath[mMediaStoragePath.length()] = 0; |
| outFileLengthFormat[0] = 0; |
| outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION; |
| return MtpConstants.RESPONSE_OK; |
| } |
| Cursor c = null; |
| try { |
| c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_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; |
| outFileLengthFormat[0] = c.getLong(2); |
| outFileLengthFormat[1] = c.getLong(3); |
| return MtpConstants.RESPONSE_OK; |
| } else { |
| return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException in getObjectFilePath", e); |
| return MtpConstants.RESPONSE_GENERAL_ERROR; |
| } finally { |
| if (c != null) { |
| c.close(); |
| } |
| } |
| } |
| |
| private int deleteFile(int handle) { |
| mDatabaseModified = true; |
| String path = null; |
| int format = 0; |
| |
| Cursor c = null; |
| try { |
| c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION, |
| ID_WHERE, new String[] { Integer.toString(handle) }, null); |
| if (c != null && c.moveToNext()) { |
| // don't convert to media path here, since we will be matching |
| // against paths in the database matching /data/media |
| path = c.getString(1); |
| format = c.getInt(3); |
| } else { |
| return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; |
| } |
| |
| if (path == null || format == 0) { |
| return MtpConstants.RESPONSE_GENERAL_ERROR; |
| } |
| |
| if (format == MtpConstants.FORMAT_ASSOCIATION) { |
| // recursive case - delete all children first |
| Uri uri = Files.getMtpObjectsUri(mVolumeName); |
| int count = mMediaProvider.delete(uri, "_data LIKE ?", |
| new String[] { path + "/%"}); |
| } |
| |
| Uri uri = Files.getMtpObjectsUri(mVolumeName, handle); |
| if (mMediaProvider.delete(uri, null, null) > 0) { |
| return MtpConstants.RESPONSE_OK; |
| } else { |
| return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException in deleteFile", e); |
| return MtpConstants.RESPONSE_GENERAL_ERROR; |
| } finally { |
| if (c != null) { |
| c.close(); |
| } |
| } |
| } |
| |
| private int[] getObjectReferences(int handle) { |
| Uri uri = Files.getMtpReferencesUri(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) { |
| mDatabaseModified = true; |
| Uri uri = Files.getMtpReferencesUri(mVolumeName, handle); |
| int count = references.length; |
| ContentValues[] valuesList = new ContentValues[count]; |
| for (int i = 0; i < count; i++) { |
| ContentValues values = new ContentValues(); |
| values.put(Files.FileColumns._ID, references[i]); |
| valuesList[i] = values; |
| } |
| try { |
| if (mMediaProvider.bulkInsert(uri, valuesList) > 0) { |
| return MtpConstants.RESPONSE_OK; |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException in setObjectReferences", e); |
| } |
| return MtpConstants.RESPONSE_GENERAL_ERROR; |
| } |
| |
| private void sessionStarted() { |
| mDatabaseModified = false; |
| } |
| |
| private void sessionEnded() { |
| if (mDatabaseModified) { |
| mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END)); |
| mDatabaseModified = false; |
| } |
| } |
| |
| // used by the JNI code |
| private int mNativeContext; |
| |
| private native final void native_setup(); |
| private native final void native_finalize(); |
| } |