blob: 13cdc693d0b46d2f3cf1c5a47b7a59eb8948ec59 [file] [log] [blame]
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Mike Lockwood0cd01362010-12-30 11:54:33 -050017package android.mtp;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040018
Mike Lockwood56c85242014-03-07 13:29:08 -080019import android.content.BroadcastReceiver;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040020import android.content.Context;
Mike Lockwoodd815f792010-07-12 08:49:01 -040021import android.content.ContentValues;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040022import android.content.IContentProvider;
Mike Lockwood2837eef2010-08-31 16:25:12 -040023import android.content.Intent;
Mike Lockwood56c85242014-03-07 13:29:08 -080024import android.content.IntentFilter;
Mike Lockwood775de952011-03-05 17:34:11 -050025import android.content.SharedPreferences;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040026import android.database.Cursor;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040027import android.database.sqlite.SQLiteDatabase;
Mike Lockwood0cd01362010-12-30 11:54:33 -050028import android.media.MediaScanner;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040029import android.net.Uri;
Mike Lockwood56c85242014-03-07 13:29:08 -080030import android.os.BatteryManager;
31import android.os.BatteryStats;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040032import android.os.RemoteException;
Mike Lockwooda3156052010-11-20 12:28:27 -050033import android.provider.MediaStore;
Mike Lockwood9a2046f2010-08-03 15:30:09 -040034import android.provider.MediaStore.Audio;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040035import android.provider.MediaStore.Files;
Mike Lockwoodae078f72010-09-26 12:35:51 -040036import android.provider.MediaStore.MediaColumns;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040037import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080038import android.view.Display;
39import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040040
Mike Lockwood5ebac832010-10-12 11:33:47 -040041import java.io.File;
Marco Nelissen5f411692014-09-26 16:03:49 -070042import java.io.IOException;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050043import java.util.HashMap;
dujin.chafe464a72011-11-22 12:13:33 +090044import java.util.Locale;
Mike Lockwood5ebac832010-10-12 11:33:47 -040045
Mike Lockwoodd21eac92010-07-03 00:44:05 -040046/**
47 * {@hide}
48 */
49public class MtpDatabase {
50
51 private static final String TAG = "MtpDatabase";
52
Mike Lockwood2837eef2010-08-31 16:25:12 -040053 private final Context mContext;
Dianne Hackborn35654b62013-01-14 17:38:02 -080054 private final String mPackageName;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040055 private final IContentProvider mMediaProvider;
56 private final String mVolumeName;
57 private final Uri mObjectsUri;
Mike Lockwood73e56d92011-12-01 16:58:41 -050058 // path to primary storage
59 private final String mMediaStoragePath;
60 // if not null, restrict all queries to these subdirectories
61 private final String[] mSubDirectories;
62 // where clause for restricting queries to files in mSubDirectories
63 private String mSubDirectoriesWhere;
64 // where arguments for restricting queries to files in mSubDirectories
65 private String[] mSubDirectoriesWhereArgs;
66
Mike Lockwoodb239b6832011-04-05 10:21:27 -040067 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040068
Mike Lockwood7d7fb632010-12-01 18:46:23 -050069 // cached property groups for single properties
70 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
71 = new HashMap<Integer, MtpPropertyGroup>();
72
73 // cached property groups for all properties for a given format
74 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
75 = new HashMap<Integer, MtpPropertyGroup>();
76
Mike Lockwood2837eef2010-08-31 16:25:12 -040077 // true if the database has been modified in the current MTP session
78 private boolean mDatabaseModified;
79
Mike Lockwood775de952011-03-05 17:34:11 -050080 // SharedPreferences for writable MTP device properties
81 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040082 private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
83
Mike Lockwoodd21eac92010-07-03 00:44:05 -040084 private static final String[] ID_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040085 Files.FileColumns._ID, // 0
Mike Lockwoodd21eac92010-07-03 00:44:05 -040086 };
Mike Lockwood6a6a3af2010-10-12 14:19:51 -040087 private static final String[] PATH_PROJECTION = new String[] {
Mike Lockwood5ebac832010-10-12 11:33:47 -040088 Files.FileColumns._ID, // 0
89 Files.FileColumns.DATA, // 1
Mike Lockwood5ebac832010-10-12 11:33:47 -040090 };
Mike Lockwoodf6f16612012-09-12 15:50:59 -070091 private static final String[] PATH_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040092 Files.FileColumns._ID, // 0
93 Files.FileColumns.DATA, // 1
Mike Lockwoodf6f16612012-09-12 15:50:59 -070094 Files.FileColumns.FORMAT, // 2
Mike Lockwoodd21eac92010-07-03 00:44:05 -040095 };
96 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040097 Files.FileColumns._ID, // 0
Mike Lockwoodb239b6832011-04-05 10:21:27 -040098 Files.FileColumns.STORAGE_ID, // 1
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040099 Files.FileColumns.FORMAT, // 2
100 Files.FileColumns.PARENT, // 3
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400101 Files.FileColumns.DATA, // 4
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700102 Files.FileColumns.DATE_ADDED, // 5
103 Files.FileColumns.DATE_MODIFIED, // 6
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400104 };
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400105 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwoodbafca212010-12-13 21:50:09 -0800106 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400107
108 private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
Mike Lockwood58e6831c2012-09-11 10:49:34 -0700109 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
110 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400111 private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400112 + Files.FileColumns.FORMAT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400113 private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
114 + Files.FileColumns.PARENT + "=?";
115 private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
116 + Files.FileColumns.PARENT + "=?";
117 private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
118 + Files.FileColumns.PARENT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400119
Mike Lockwoodd815f792010-07-12 08:49:01 -0400120 private final MediaScanner mMediaScanner;
Mike Lockwood56c85242014-03-07 13:29:08 -0800121 private MtpServer mServer;
122
123 // read from native code
124 private int mBatteryLevel;
125 private int mBatteryScale;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400126
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400127 static {
128 System.loadLibrary("media_jni");
129 }
130
Mike Lockwood56c85242014-03-07 13:29:08 -0800131 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
132 @Override
133 public void onReceive(Context context, Intent intent) {
134 String action = intent.getAction();
135 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
136 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
137 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
138 if (newLevel != mBatteryLevel) {
139 mBatteryLevel = newLevel;
140 if (mServer != null) {
141 // send device property changed event
142 mServer.sendDevicePropertyChanged(
143 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
144 }
145 }
146 }
147 }
148 };
149
Mike Lockwood73e56d92011-12-01 16:58:41 -0500150 public MtpDatabase(Context context, String volumeName, String storagePath,
151 String[] subDirectories) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400152 native_setup();
153
Mike Lockwood2837eef2010-08-31 16:25:12 -0400154 mContext = context;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800155 mPackageName = context.getPackageName();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400156 mMediaProvider = context.getContentResolver().acquireProvider("media");
157 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400158 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400159 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400160 mMediaScanner = new MediaScanner(context);
dujin.chafe464a72011-11-22 12:13:33 +0900161
Mike Lockwood73e56d92011-12-01 16:58:41 -0500162 mSubDirectories = subDirectories;
163 if (subDirectories != null) {
164 // Compute "where" string for restricting queries to subdirectories
165 StringBuilder builder = new StringBuilder();
166 builder.append("(");
167 int count = subDirectories.length;
168 for (int i = 0; i < count; i++) {
169 builder.append(Files.FileColumns.DATA + "=? OR "
170 + Files.FileColumns.DATA + " LIKE ?");
171 if (i != count - 1) {
172 builder.append(" OR ");
173 }
174 }
175 builder.append(")");
176 mSubDirectoriesWhere = builder.toString();
177
178 // Compute "where" arguments for restricting queries to subdirectories
179 mSubDirectoriesWhereArgs = new String[count * 2];
180 for (int i = 0, j = 0; i < count; i++) {
181 String path = subDirectories[i];
182 mSubDirectoriesWhereArgs[j++] = path;
183 mSubDirectoriesWhereArgs[j++] = path + "/%";
184 }
185 }
186
dujin.chafe464a72011-11-22 12:13:33 +0900187 // Set locale to MediaScanner.
188 Locale locale = context.getResources().getConfiguration().locale;
189 if (locale != null) {
190 String language = locale.getLanguage();
191 String country = locale.getCountry();
192 if (language != null) {
193 if (country != null) {
194 mMediaScanner.setLocale(language + "_" + country);
195 } else {
196 mMediaScanner.setLocale(language);
197 }
198 }
199 }
Mike Lockwood775de952011-03-05 17:34:11 -0500200 initDeviceProperties(context);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400201 }
202
Mike Lockwood56c85242014-03-07 13:29:08 -0800203 public void setServer(MtpServer server) {
204 mServer = server;
205
Marco Nelissen1632fae2014-03-27 13:25:14 -0700206 // always unregister before registering
207 try {
208 mContext.unregisterReceiver(mBatteryReceiver);
209 } catch (IllegalArgumentException e) {
210 // wasn't previously registered, ignore
211 }
212
Mike Lockwood56c85242014-03-07 13:29:08 -0800213 // register for battery notifications when we are connected
214 if (server != null) {
215 mContext.registerReceiver(mBatteryReceiver,
216 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Mike Lockwood56c85242014-03-07 13:29:08 -0800217 }
218 }
219
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400220 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400221 protected void finalize() throws Throwable {
222 try {
223 native_finalize();
Mike Lockwooddbead322010-08-30 09:27:55 -0400224 } finally {
225 super.finalize();
226 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400227 }
228
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400229 public void addStorage(MtpStorage storage) {
230 mStorageMap.put(storage.getPath(), storage);
231 }
232
233 public void removeStorage(MtpStorage storage) {
234 mStorageMap.remove(storage.getPath());
235 }
236
Mike Lockwood775de952011-03-05 17:34:11 -0500237 private void initDeviceProperties(Context context) {
238 final String devicePropertiesName = "device-properties";
239 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
240 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400241
Mike Lockwood775de952011-03-05 17:34:11 -0500242 if (databaseFile.exists()) {
243 // for backward compatibility - read device properties from sqlite database
244 // and migrate them to shared prefs
245 SQLiteDatabase db = null;
246 Cursor c = null;
247 try {
248 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
249 if (db != null) {
250 c = db.query("properties", new String[] { "_id", "code", "value" },
251 null, null, null, null, null);
252 if (c != null) {
253 SharedPreferences.Editor e = mDeviceProperties.edit();
254 while (c.moveToNext()) {
255 String name = c.getString(1);
256 String value = c.getString(2);
257 e.putString(name, value);
258 }
259 e.commit();
260 }
261 }
262 } catch (Exception e) {
263 Log.e(TAG, "failed to migrate device properties", e);
264 } finally {
265 if (c != null) c.close();
266 if (db != null) db.close();
267 }
jangwon.lee3ed02532013-07-22 19:52:48 +0900268 context.deleteDatabase(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400269 }
270 }
271
Mike Lockwood73e56d92011-12-01 16:58:41 -0500272 // check to see if the path is contained in one of our storage subdirectories
273 // returns true if we have no special subdirectories
274 private boolean inStorageSubDirectory(String path) {
275 if (mSubDirectories == null) return true;
276 if (path == null) return false;
277
278 boolean allowed = false;
279 int pathLength = path.length();
280 for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
281 String subdir = mSubDirectories[i];
282 int subdirLength = subdir.length();
283 if (subdirLength < pathLength &&
284 path.charAt(subdirLength) == '/' &&
285 path.startsWith(subdir)) {
286 allowed = true;
287 }
288 }
289 return allowed;
290 }
291
292 // check to see if the path matches one of our storage subdirectories
293 // returns true if we have no special subdirectories
294 private boolean isStorageSubDirectory(String path) {
295 if (mSubDirectories == null) return false;
296 for (int i = 0; i < mSubDirectories.length; i++) {
297 if (path.equals(mSubDirectories[i])) {
298 return true;
299 }
300 }
301 return false;
302 }
303
Marco Nelissen5f411692014-09-26 16:03:49 -0700304 // returns true if the path is in the storage root
305 private boolean inStorageRoot(String path) {
306 try {
307 File f = new File(path);
308 String canonical = f.getCanonicalPath();
Marco Nelissenc1fda122014-10-15 14:32:22 -0700309 for (String root: mStorageMap.keySet()) {
310 if (canonical.startsWith(root)) {
311 return true;
312 }
Marco Nelissen5f411692014-09-26 16:03:49 -0700313 }
314 } catch (IOException e) {
315 // ignore
316 }
317 return false;
318 }
319
Mike Lockwoodd815f792010-07-12 08:49:01 -0400320 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400321 int storageId, long size, long modified) {
Marco Nelissen5f411692014-09-26 16:03:49 -0700322 // if the path is outside of the storage root, do not allow access
323 if (!inStorageRoot(path)) {
324 Log.e(TAG, "attempt to put file outside of storage area: " + path);
325 return -1;
326 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500327 // if mSubDirectories is not null, do not allow copying files to any other locations
328 if (!inStorageSubDirectory(path)) return -1;
329
330 // make sure the object does not exist
Mike Lockwoodbafca212010-12-13 21:50:09 -0800331 if (path != null) {
332 Cursor c = null;
333 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800334 c = mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, PATH_WHERE,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800335 new String[] { path }, null, null);
Mike Lockwoodbafca212010-12-13 21:50:09 -0800336 if (c != null && c.getCount() > 0) {
337 Log.w(TAG, "file already exists in beginSendObject: " + path);
338 return -1;
339 }
340 } catch (RemoteException e) {
341 Log.e(TAG, "RemoteException in beginSendObject", e);
342 } finally {
343 if (c != null) {
344 c.close();
345 }
346 }
347 }
348
Mike Lockwood2837eef2010-08-31 16:25:12 -0400349 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400350 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400351 values.put(Files.FileColumns.DATA, path);
352 values.put(Files.FileColumns.FORMAT, format);
353 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400354 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400355 values.put(Files.FileColumns.SIZE, size);
356 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400357
358 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800359 Uri uri = mMediaProvider.insert(mPackageName, mObjectsUri, values);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400360 if (uri != null) {
361 return Integer.parseInt(uri.getPathSegments().get(2));
362 } else {
363 return -1;
364 }
365 } catch (RemoteException e) {
366 Log.e(TAG, "RemoteException in beginSendObject", e);
367 return -1;
368 }
369 }
370
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800371 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400372 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400373 // handle abstract playlists separately
374 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400375 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400376 // extract name from path
377 String name = path;
378 int lastSlash = name.lastIndexOf('/');
379 if (lastSlash >= 0) {
380 name = name.substring(lastSlash + 1);
381 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800382 // strip trailing ".pla" from the name
383 if (name.endsWith(".pla")) {
384 name = name.substring(0, name.length() - 4);
385 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400386
387 ContentValues values = new ContentValues(1);
388 values.put(Audio.Playlists.DATA, path);
389 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500390 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800391 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400392 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
393 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800394 Uri uri = mMediaProvider.insert(mPackageName,
395 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400396 } catch (RemoteException e) {
397 Log.e(TAG, "RemoteException in endSendObject", e);
398 }
399 } else {
Mike Lockwoodc37255d2010-09-10 14:47:36 -0400400 mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400401 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400402 } else {
403 deleteFile(handle);
404 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400405 }
406
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400407 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500408 String where;
409 String[] whereArgs;
410
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400411 if (storageID == 0xFFFFFFFF) {
412 // query all stores
413 if (format == 0) {
414 // query all formats
415 if (parent == 0) {
416 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500417 where = null;
418 whereArgs = null;
419 } else {
420 if (parent == 0xFFFFFFFF) {
421 // all objects in root of store
422 parent = 0;
423 }
424 where = PARENT_WHERE;
425 whereArgs = new String[] { Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400426 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400427 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400428 // query specific format
429 if (parent == 0) {
430 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500431 where = FORMAT_WHERE;
432 whereArgs = new String[] { Integer.toString(format) };
433 } else {
434 if (parent == 0xFFFFFFFF) {
435 // all objects in root of store
436 parent = 0;
437 }
438 where = FORMAT_PARENT_WHERE;
439 whereArgs = new String[] { Integer.toString(format),
440 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400441 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400442 }
443 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400444 // query specific store
445 if (format == 0) {
446 // query all formats
447 if (parent == 0) {
448 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500449 where = STORAGE_WHERE;
450 whereArgs = new String[] { Integer.toString(storageID) };
451 } else {
452 if (parent == 0xFFFFFFFF) {
453 // all objects in root of store
454 parent = 0;
455 }
456 where = STORAGE_PARENT_WHERE;
457 whereArgs = new String[] { Integer.toString(storageID),
458 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400459 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400460 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400461 // query specific format
462 if (parent == 0) {
463 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500464 where = STORAGE_FORMAT_WHERE;
465 whereArgs = new String[] { Integer.toString(storageID),
466 Integer.toString(format) };
467 } else {
468 if (parent == 0xFFFFFFFF) {
469 // all objects in root of store
470 parent = 0;
471 }
472 where = STORAGE_FORMAT_PARENT_WHERE;
473 whereArgs = new String[] { Integer.toString(storageID),
474 Integer.toString(format),
475 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400476 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400477 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400478 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500479
480 // if we are restricting queries to mSubDirectories, we need to add the restriction
481 // onto our "where" arguments
482 if (mSubDirectoriesWhere != null) {
483 if (where == null) {
484 where = mSubDirectoriesWhere;
485 whereArgs = mSubDirectoriesWhereArgs;
486 } else {
487 where = where + " AND " + mSubDirectoriesWhere;
488
489 // create new array to hold whereArgs and mSubDirectoriesWhereArgs
490 String[] newWhereArgs =
491 new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
492 int i, j;
493 for (i = 0; i < whereArgs.length; i++) {
494 newWhereArgs[i] = whereArgs[i];
495 }
496 for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
497 newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
498 }
499 whereArgs = newWhereArgs;
500 }
501 }
502
Dianne Hackborn35654b62013-01-14 17:38:02 -0800503 return mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, where,
504 whereArgs, null, null);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400505 }
506
507 private int[] getObjectList(int storageID, int format, int parent) {
508 Cursor c = null;
509 try {
510 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400511 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400512 return null;
513 }
514 int count = c.getCount();
515 if (count > 0) {
516 int[] result = new int[count];
517 for (int i = 0; i < count; i++) {
518 c.moveToNext();
519 result[i] = c.getInt(0);
520 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400521 return result;
522 }
523 } catch (RemoteException e) {
524 Log.e(TAG, "RemoteException in getObjectList", e);
525 } finally {
526 if (c != null) {
527 c.close();
528 }
529 }
530 return null;
531 }
532
Mike Lockwood7a047c82010-08-02 10:52:20 -0400533 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400534 Cursor c = null;
535 try {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400536 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400537 if (c != null) {
538 return c.getCount();
539 }
540 } catch (RemoteException e) {
541 Log.e(TAG, "RemoteException in getNumObjects", e);
542 } finally {
543 if (c != null) {
544 c.close();
545 }
546 }
547 return -1;
548 }
549
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400550 private int[] getSupportedPlaybackFormats() {
551 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400552 // allow transfering arbitrary files
553 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400554
Mike Lockwood792ec842010-09-09 15:30:10 -0400555 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400556 MtpConstants.FORMAT_TEXT,
557 MtpConstants.FORMAT_HTML,
558 MtpConstants.FORMAT_WAV,
559 MtpConstants.FORMAT_MP3,
560 MtpConstants.FORMAT_MPEG,
561 MtpConstants.FORMAT_EXIF_JPEG,
562 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800563 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400564 MtpConstants.FORMAT_GIF,
565 MtpConstants.FORMAT_JFIF,
566 MtpConstants.FORMAT_PNG,
567 MtpConstants.FORMAT_TIFF,
568 MtpConstants.FORMAT_WMA,
569 MtpConstants.FORMAT_OGG,
570 MtpConstants.FORMAT_AAC,
571 MtpConstants.FORMAT_MP4_CONTAINER,
572 MtpConstants.FORMAT_MP2,
573 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400574 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400575 MtpConstants.FORMAT_WPL_PLAYLIST,
576 MtpConstants.FORMAT_M3U_PLAYLIST,
577 MtpConstants.FORMAT_PLS_PLAYLIST,
578 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800579 MtpConstants.FORMAT_FLAC,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400580 };
581 }
582
583 private int[] getSupportedCaptureFormats() {
584 // no capture formats yet
585 return null;
586 }
587
Mike Lockwoodae078f72010-09-26 12:35:51 -0400588 static final int[] FILE_PROPERTIES = {
589 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
590 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400591 MtpConstants.PROPERTY_STORAGE_ID,
592 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400593 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400594 MtpConstants.PROPERTY_OBJECT_SIZE,
595 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400596 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400597 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400598 MtpConstants.PROPERTY_PERSISTENT_UID,
599 MtpConstants.PROPERTY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400600 MtpConstants.PROPERTY_DATE_ADDED,
601 };
602
603 static final int[] AUDIO_PROPERTIES = {
604 // NOTE must match FILE_PROPERTIES above
605 MtpConstants.PROPERTY_STORAGE_ID,
606 MtpConstants.PROPERTY_OBJECT_FORMAT,
607 MtpConstants.PROPERTY_PROTECTION_STATUS,
608 MtpConstants.PROPERTY_OBJECT_SIZE,
609 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
610 MtpConstants.PROPERTY_DATE_MODIFIED,
611 MtpConstants.PROPERTY_PARENT_OBJECT,
612 MtpConstants.PROPERTY_PERSISTENT_UID,
613 MtpConstants.PROPERTY_NAME,
614 MtpConstants.PROPERTY_DISPLAY_NAME,
615 MtpConstants.PROPERTY_DATE_ADDED,
616
617 // audio specific properties
618 MtpConstants.PROPERTY_ARTIST,
619 MtpConstants.PROPERTY_ALBUM_NAME,
620 MtpConstants.PROPERTY_ALBUM_ARTIST,
621 MtpConstants.PROPERTY_TRACK,
622 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
623 MtpConstants.PROPERTY_DURATION,
624 MtpConstants.PROPERTY_GENRE,
625 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700626 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
627 MtpConstants.PROPERTY_BITRATE_TYPE,
628 MtpConstants.PROPERTY_AUDIO_BITRATE,
629 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
630 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400631 };
632
633 static final int[] VIDEO_PROPERTIES = {
634 // NOTE must match FILE_PROPERTIES above
635 MtpConstants.PROPERTY_STORAGE_ID,
636 MtpConstants.PROPERTY_OBJECT_FORMAT,
637 MtpConstants.PROPERTY_PROTECTION_STATUS,
638 MtpConstants.PROPERTY_OBJECT_SIZE,
639 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
640 MtpConstants.PROPERTY_DATE_MODIFIED,
641 MtpConstants.PROPERTY_PARENT_OBJECT,
642 MtpConstants.PROPERTY_PERSISTENT_UID,
643 MtpConstants.PROPERTY_NAME,
644 MtpConstants.PROPERTY_DISPLAY_NAME,
645 MtpConstants.PROPERTY_DATE_ADDED,
646
647 // video specific properties
648 MtpConstants.PROPERTY_ARTIST,
649 MtpConstants.PROPERTY_ALBUM_NAME,
650 MtpConstants.PROPERTY_DURATION,
651 MtpConstants.PROPERTY_DESCRIPTION,
652 };
653
654 static final int[] IMAGE_PROPERTIES = {
655 // NOTE must match FILE_PROPERTIES above
656 MtpConstants.PROPERTY_STORAGE_ID,
657 MtpConstants.PROPERTY_OBJECT_FORMAT,
658 MtpConstants.PROPERTY_PROTECTION_STATUS,
659 MtpConstants.PROPERTY_OBJECT_SIZE,
660 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
661 MtpConstants.PROPERTY_DATE_MODIFIED,
662 MtpConstants.PROPERTY_PARENT_OBJECT,
663 MtpConstants.PROPERTY_PERSISTENT_UID,
664 MtpConstants.PROPERTY_NAME,
665 MtpConstants.PROPERTY_DISPLAY_NAME,
666 MtpConstants.PROPERTY_DATE_ADDED,
667
668 // image specific properties
669 MtpConstants.PROPERTY_DESCRIPTION,
670 };
671
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500672 static final int[] ALL_PROPERTIES = {
673 // NOTE must match FILE_PROPERTIES above
674 MtpConstants.PROPERTY_STORAGE_ID,
675 MtpConstants.PROPERTY_OBJECT_FORMAT,
676 MtpConstants.PROPERTY_PROTECTION_STATUS,
677 MtpConstants.PROPERTY_OBJECT_SIZE,
678 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
679 MtpConstants.PROPERTY_DATE_MODIFIED,
680 MtpConstants.PROPERTY_PARENT_OBJECT,
681 MtpConstants.PROPERTY_PERSISTENT_UID,
682 MtpConstants.PROPERTY_NAME,
683 MtpConstants.PROPERTY_DISPLAY_NAME,
684 MtpConstants.PROPERTY_DATE_ADDED,
685
686 // image specific properties
687 MtpConstants.PROPERTY_DESCRIPTION,
688
689 // audio specific properties
690 MtpConstants.PROPERTY_ARTIST,
691 MtpConstants.PROPERTY_ALBUM_NAME,
692 MtpConstants.PROPERTY_ALBUM_ARTIST,
693 MtpConstants.PROPERTY_TRACK,
694 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
695 MtpConstants.PROPERTY_DURATION,
696 MtpConstants.PROPERTY_GENRE,
697 MtpConstants.PROPERTY_COMPOSER,
698
699 // video specific properties
700 MtpConstants.PROPERTY_ARTIST,
701 MtpConstants.PROPERTY_ALBUM_NAME,
702 MtpConstants.PROPERTY_DURATION,
703 MtpConstants.PROPERTY_DESCRIPTION,
704
705 // image specific properties
706 MtpConstants.PROPERTY_DESCRIPTION,
707 };
708
Mike Lockwoodae078f72010-09-26 12:35:51 -0400709 private int[] getSupportedObjectProperties(int format) {
710 switch (format) {
711 case MtpConstants.FORMAT_MP3:
712 case MtpConstants.FORMAT_WAV:
713 case MtpConstants.FORMAT_WMA:
714 case MtpConstants.FORMAT_OGG:
715 case MtpConstants.FORMAT_AAC:
716 return AUDIO_PROPERTIES;
717 case MtpConstants.FORMAT_MPEG:
718 case MtpConstants.FORMAT_3GP_CONTAINER:
719 case MtpConstants.FORMAT_WMV:
720 return VIDEO_PROPERTIES;
721 case MtpConstants.FORMAT_EXIF_JPEG:
722 case MtpConstants.FORMAT_GIF:
723 case MtpConstants.FORMAT_PNG:
724 case MtpConstants.FORMAT_BMP:
725 return IMAGE_PROPERTIES;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500726 case 0:
727 return ALL_PROPERTIES;
Mike Lockwoodae078f72010-09-26 12:35:51 -0400728 default:
729 return FILE_PROPERTIES;
730 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400731 }
732
733 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400734 return new int[] {
735 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
736 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800737 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood56c85242014-03-07 13:29:08 -0800738 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400739 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400740 }
741
Mike Lockwoodae078f72010-09-26 12:35:51 -0400742
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500743 private MtpPropertyList getObjectPropertyList(long handle, int format, long property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400744 int groupCode, int depth) {
745 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400746 if (groupCode != 0) {
747 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
748 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400749
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500750 MtpPropertyGroup propertyGroup;
751 if (property == 0xFFFFFFFFL) {
752 propertyGroup = mPropertyGroupsByFormat.get(format);
753 if (propertyGroup == null) {
754 int[] propertyList = getSupportedObjectProperties(format);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800755 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
756 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500757 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400758 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500759 } else {
760 propertyGroup = mPropertyGroupsByProperty.get(property);
761 if (propertyGroup == null) {
762 int[] propertyList = new int[] { (int)property };
Dianne Hackborn35654b62013-01-14 17:38:02 -0800763 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
764 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500765 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400766 }
767 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500768
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400769 return propertyGroup.getPropertyList((int)handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400770 }
771
Mike Lockwood5ebac832010-10-12 11:33:47 -0400772 private int renameFile(int handle, String newName) {
773 Cursor c = null;
774
775 // first compute current path
776 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400777 String[] whereArgs = new String[] { Integer.toString(handle) };
778 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800779 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_PROJECTION, ID_WHERE,
780 whereArgs, null, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400781 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500782 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400783 }
784 } catch (RemoteException e) {
785 Log.e(TAG, "RemoteException in getObjectFilePath", e);
786 return MtpConstants.RESPONSE_GENERAL_ERROR;
787 } finally {
788 if (c != null) {
789 c.close();
790 }
791 }
792 if (path == null) {
793 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
794 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400795
Mike Lockwood73e56d92011-12-01 16:58:41 -0500796 // do not allow renaming any of the special subdirectories
797 if (isStorageSubDirectory(path)) {
798 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
799 }
800
Mike Lockwood5ebac832010-10-12 11:33:47 -0400801 // now rename the file. make sure this succeeds before updating database
802 File oldFile = new File(path);
803 int lastSlash = path.lastIndexOf('/');
804 if (lastSlash <= 1) {
805 return MtpConstants.RESPONSE_GENERAL_ERROR;
806 }
807 String newPath = path.substring(0, lastSlash + 1) + newName;
808 File newFile = new File(newPath);
809 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400810 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800811 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400812 return MtpConstants.RESPONSE_GENERAL_ERROR;
813 }
814
815 // finally update database
816 ContentValues values = new ContentValues();
817 values.put(Files.FileColumns.DATA, newPath);
818 int updated = 0;
819 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400820 // note - we are relying on a special case in MediaProvider.update() to update
821 // the paths for all children in the case where this is a directory.
Dianne Hackborn35654b62013-01-14 17:38:02 -0800822 updated = mMediaProvider.update(mPackageName, mObjectsUri, values, ID_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400823 } catch (RemoteException e) {
824 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
825 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400826 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400827 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
828 // this shouldn't happen, but if it does we need to rename the file to its original name
829 newFile.renameTo(oldFile);
830 return MtpConstants.RESPONSE_GENERAL_ERROR;
831 }
832
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800833 // check if nomedia status changed
834 if (newFile.isDirectory()) {
835 // for directories, check if renamed from something hidden to something non-hidden
836 if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
837 // directory was unhidden
838 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800839 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, newPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800840 } catch (RemoteException e) {
841 Log.e(TAG, "failed to unhide/rescan for " + newPath);
842 }
843 }
844 } else {
845 // for files, check if renamed from .nomedia to something else
846 if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
847 && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
848 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800849 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800850 } catch (RemoteException e) {
851 Log.e(TAG, "failed to unhide/rescan for " + newPath);
852 }
853 }
854 }
855
Mike Lockwood5ebac832010-10-12 11:33:47 -0400856 return MtpConstants.RESPONSE_OK;
857 }
858
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400859 private int setObjectProperty(int handle, int property,
860 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400861 switch (property) {
862 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
863 return renameFile(handle, stringValue);
864
865 default:
866 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
867 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400868 }
869
870 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400871 switch (property) {
872 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
873 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500874 // writable string properties kept in shared preferences
875 String value = mDeviceProperties.getString(Integer.toString(property), "");
876 int length = value.length();
877 if (length > 255) {
878 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400879 }
Mike Lockwood775de952011-03-05 17:34:11 -0500880 value.getChars(0, length, outStringValue, 0);
881 outStringValue[length] = 0;
882 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400883
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800884 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
885 // use screen size as max image size
886 Display display = ((WindowManager)mContext.getSystemService(
887 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700888 int width = display.getMaximumSizeDimension();
889 int height = display.getMaximumSizeDimension();
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800890 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
891 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
892 outStringValue[imageSize.length()] = 0;
893 return MtpConstants.RESPONSE_OK;
894
Mike Lockwood56c85242014-03-07 13:29:08 -0800895 // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
896
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800897 default:
898 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
899 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400900 }
901
902 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400903 switch (property) {
904 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
905 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500906 // writable string properties kept in shared prefs
907 SharedPreferences.Editor e = mDeviceProperties.edit();
908 e.putString(Integer.toString(property), stringValue);
909 return (e.commit() ? MtpConstants.RESPONSE_OK
910 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400911 }
912
913 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
914 }
915
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400916 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700917 char[] outName, long[] outCreatedModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400918 Cursor c = null;
919 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800920 c = mMediaProvider.query(mPackageName, mObjectsUri, OBJECT_INFO_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800921 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400922 if (c != null && c.moveToNext()) {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400923 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400924 outStorageFormatParent[1] = c.getInt(2);
925 outStorageFormatParent[2] = c.getInt(3);
926
927 // extract name from path
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400928 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400929 int lastSlash = path.lastIndexOf('/');
930 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
931 int end = path.length();
932 if (end - start > 255) {
933 end = start + 255;
934 }
935 path.getChars(start, end, outName, 0);
936 outName[end - start] = 0;
937
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700938 outCreatedModified[0] = c.getLong(5);
939 outCreatedModified[1] = c.getLong(6);
940 // use modification date as creation date if date added is not set
941 if (outCreatedModified[0] == 0) {
942 outCreatedModified[0] = outCreatedModified[1];
943 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400944 return true;
945 }
946 } catch (RemoteException e) {
Mike Lockwood2b5f9ad12010-10-29 19:16:27 -0400947 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400948 } finally {
949 if (c != null) {
950 c.close();
951 }
952 }
953 return false;
954 }
955
Mike Lockwood365e03e2010-12-08 16:08:01 -0800956 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400957 if (handle == 0) {
958 // special case root directory
959 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
960 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800961 outFileLengthFormat[0] = 0;
962 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400963 return MtpConstants.RESPONSE_OK;
964 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400965 Cursor c = null;
966 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800967 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800968 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400969 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500970 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400971 path.getChars(0, path.length(), outFilePath, 0);
972 outFilePath[path.length()] = 0;
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700973 // File transfers from device to host will likely fail if the size is incorrect.
974 // So to be safe, use the actual file size here.
975 outFileLengthFormat[0] = new File(path).length();
976 outFileLengthFormat[1] = c.getLong(2);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400977 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400978 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400979 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400980 }
981 } catch (RemoteException e) {
982 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400983 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400984 } finally {
985 if (c != null) {
986 c.close();
987 }
988 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400989 }
990
Mike Lockwood59c777a2010-08-02 10:37:41 -0400991 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400992 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800993 String path = null;
994 int format = 0;
995
996 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400997 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800998 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800999 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -08001000 if (c != null && c.moveToNext()) {
1001 // don't convert to media path here, since we will be matching
1002 // against paths in the database matching /data/media
1003 path = c.getString(1);
Mike Lockwoodf6f16612012-09-12 15:50:59 -07001004 format = c.getInt(2);
Mike Lockwood55f808c2010-12-14 13:14:29 -08001005 } else {
1006 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
1007 }
1008
1009 if (path == null || format == 0) {
1010 return MtpConstants.RESPONSE_GENERAL_ERROR;
1011 }
1012
Mike Lockwood73e56d92011-12-01 16:58:41 -05001013 // do not allow deleting any of the special subdirectories
1014 if (isStorageSubDirectory(path)) {
1015 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
1016 }
1017
Mike Lockwood55f808c2010-12-14 13:14:29 -08001018 if (format == MtpConstants.FORMAT_ASSOCIATION) {
1019 // recursive case - delete all children first
1020 Uri uri = Files.getMtpObjectsUri(mVolumeName);
Dianne Hackborn35654b62013-01-14 17:38:02 -08001021 int count = mMediaProvider.delete(mPackageName, uri,
Mike Lockwood1e855d92012-06-26 16:31:41 -07001022 // the 'like' makes it use the index, the 'lower()' makes it correct
1023 // when the path contains sqlite wildcard characters
1024 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
1025 new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -08001026 }
1027
1028 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
Dianne Hackborn35654b62013-01-14 17:38:02 -08001029 if (mMediaProvider.delete(mPackageName, uri, null, null) > 0) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001030 if (format != MtpConstants.FORMAT_ASSOCIATION
1031 && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
1032 try {
1033 String parentPath = path.substring(0, path.lastIndexOf("/"));
Dianne Hackborn35654b62013-01-14 17:38:02 -08001034 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001035 } catch (RemoteException e) {
1036 Log.e(TAG, "failed to unhide/rescan for " + path);
1037 }
1038 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001039 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001040 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001041 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001042 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001043 } catch (RemoteException e) {
1044 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -04001045 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -08001046 } finally {
1047 if (c != null) {
1048 c.close();
1049 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001050 }
1051 }
1052
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001053 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -04001054 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001055 Cursor c = null;
1056 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -08001057 c = mMediaProvider.query(mPackageName, uri, ID_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001058 if (c == null) {
1059 return null;
1060 }
1061 int count = c.getCount();
1062 if (count > 0) {
1063 int[] result = new int[count];
1064 for (int i = 0; i < count; i++) {
1065 c.moveToNext();
1066 result[i] = c.getInt(0);
1067 }
1068 return result;
1069 }
1070 } catch (RemoteException e) {
1071 Log.e(TAG, "RemoteException in getObjectList", e);
1072 } finally {
1073 if (c != null) {
1074 c.close();
1075 }
1076 }
1077 return null;
1078 }
1079
1080 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001081 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -04001082 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001083 int count = references.length;
1084 ContentValues[] valuesList = new ContentValues[count];
1085 for (int i = 0; i < count; i++) {
1086 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -04001087 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001088 valuesList[i] = values;
1089 }
1090 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -08001091 if (mMediaProvider.bulkInsert(mPackageName, uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001092 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001093 }
1094 } catch (RemoteException e) {
1095 Log.e(TAG, "RemoteException in setObjectReferences", e);
1096 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001097 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001098 }
1099
Mike Lockwood2837eef2010-08-31 16:25:12 -04001100 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001101 mDatabaseModified = false;
1102 }
1103
1104 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001105 if (mDatabaseModified) {
Mike Lockwooda3156052010-11-20 12:28:27 -05001106 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -04001107 mDatabaseModified = false;
1108 }
1109 }
1110
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001111 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +00001112 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001113
1114 private native final void native_setup();
1115 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001116}