775de951a8
sqlite3 is overkill for what we are doing here, and more fragile. BUG: 3512856 Change-Id: I83d86127949d894a3887db3456d91b013060e852 Signed-off-by: Mike Lockwood <lockwood@android.com>
805 lines
30 KiB
Java
805 lines
30 KiB
Java
/*
|
|
* 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.content.SharedPreferences;
|
|
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;
|
|
|
|
// SharedPreferences for writable MTP device properties
|
|
private SharedPreferences mDeviceProperties;
|
|
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 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);
|
|
initDeviceProperties(context);
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
try {
|
|
native_finalize();
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
private void initDeviceProperties(Context context) {
|
|
final String devicePropertiesName = "device-properties";
|
|
mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
|
|
File databaseFile = context.getDatabasePath(devicePropertiesName);
|
|
|
|
if (databaseFile.exists()) {
|
|
// for backward compatibility - read device properties from sqlite database
|
|
// and migrate them to shared prefs
|
|
SQLiteDatabase db = null;
|
|
Cursor c = null;
|
|
try {
|
|
db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
|
|
if (db != null) {
|
|
c = db.query("properties", new String[] { "_id", "code", "value" },
|
|
null, null, null, null, null);
|
|
if (c != null) {
|
|
SharedPreferences.Editor e = mDeviceProperties.edit();
|
|
while (c.moveToNext()) {
|
|
String name = c.getString(1);
|
|
String value = c.getString(2);
|
|
e.putString(name, value);
|
|
}
|
|
e.commit();
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "failed to migrate device properties", e);
|
|
} finally {
|
|
if (c != null) c.close();
|
|
if (db != null) db.close();
|
|
}
|
|
databaseFile.delete();
|
|
}
|
|
}
|
|
|
|
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,
|
|
MtpConstants.FORMAT_FLAC,
|
|
};
|
|
}
|
|
|
|
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 shared preferences
|
|
String value = mDeviceProperties.getString(Integer.toString(property), "");
|
|
int length = value.length();
|
|
if (length > 255) {
|
|
length = 255;
|
|
}
|
|
value.getChars(0, length, outStringValue, 0);
|
|
outStringValue[length] = 0;
|
|
return MtpConstants.RESPONSE_OK;
|
|
|
|
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 shared prefs
|
|
SharedPreferences.Editor e = mDeviceProperties.edit();
|
|
e.putString(Integer.toString(property), stringValue);
|
|
return (e.commit() ? MtpConstants.RESPONSE_OK
|
|
: 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();
|
|
}
|