blob: 15ae238b232cd1454918470d2318a1e08bfcb5f6 [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;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050042import java.util.HashMap;
dujin.chafe464a72011-11-22 12:13:33 +090043import java.util.Locale;
Mike Lockwood5ebac832010-10-12 11:33:47 -040044
Mike Lockwoodd21eac92010-07-03 00:44:05 -040045/**
46 * {@hide}
47 */
48public class MtpDatabase {
49
50 private static final String TAG = "MtpDatabase";
51
Mike Lockwood2837eef2010-08-31 16:25:12 -040052 private final Context mContext;
Dianne Hackborn35654b62013-01-14 17:38:02 -080053 private final String mPackageName;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040054 private final IContentProvider mMediaProvider;
55 private final String mVolumeName;
56 private final Uri mObjectsUri;
Mike Lockwood73e56d92011-12-01 16:58:41 -050057 // path to primary storage
58 private final String mMediaStoragePath;
59 // if not null, restrict all queries to these subdirectories
60 private final String[] mSubDirectories;
61 // where clause for restricting queries to files in mSubDirectories
62 private String mSubDirectoriesWhere;
63 // where arguments for restricting queries to files in mSubDirectories
64 private String[] mSubDirectoriesWhereArgs;
65
Mike Lockwoodb239b6832011-04-05 10:21:27 -040066 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040067
Mike Lockwood7d7fb632010-12-01 18:46:23 -050068 // cached property groups for single properties
69 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
70 = new HashMap<Integer, MtpPropertyGroup>();
71
72 // cached property groups for all properties for a given format
73 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
74 = new HashMap<Integer, MtpPropertyGroup>();
75
Mike Lockwood2837eef2010-08-31 16:25:12 -040076 // true if the database has been modified in the current MTP session
77 private boolean mDatabaseModified;
78
Mike Lockwood775de952011-03-05 17:34:11 -050079 // SharedPreferences for writable MTP device properties
80 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040081 private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
82
Mike Lockwoodd21eac92010-07-03 00:44:05 -040083 private static final String[] ID_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040084 Files.FileColumns._ID, // 0
Mike Lockwoodd21eac92010-07-03 00:44:05 -040085 };
Mike Lockwood6a6a3af2010-10-12 14:19:51 -040086 private static final String[] PATH_PROJECTION = new String[] {
Mike Lockwood5ebac832010-10-12 11:33:47 -040087 Files.FileColumns._ID, // 0
88 Files.FileColumns.DATA, // 1
Mike Lockwood5ebac832010-10-12 11:33:47 -040089 };
Mike Lockwoodf6f16612012-09-12 15:50:59 -070090 private static final String[] PATH_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040091 Files.FileColumns._ID, // 0
92 Files.FileColumns.DATA, // 1
Mike Lockwoodf6f16612012-09-12 15:50:59 -070093 Files.FileColumns.FORMAT, // 2
Mike Lockwoodd21eac92010-07-03 00:44:05 -040094 };
95 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040096 Files.FileColumns._ID, // 0
Mike Lockwoodb239b6832011-04-05 10:21:27 -040097 Files.FileColumns.STORAGE_ID, // 1
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040098 Files.FileColumns.FORMAT, // 2
99 Files.FileColumns.PARENT, // 3
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400100 Files.FileColumns.DATA, // 4
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700101 Files.FileColumns.DATE_ADDED, // 5
102 Files.FileColumns.DATE_MODIFIED, // 6
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400103 };
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400104 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwoodbafca212010-12-13 21:50:09 -0800105 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400106
107 private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
Mike Lockwood58e6831c2012-09-11 10:49:34 -0700108 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
109 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400110 private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400111 + Files.FileColumns.FORMAT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400112 private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
113 + Files.FileColumns.PARENT + "=?";
114 private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
115 + Files.FileColumns.PARENT + "=?";
116 private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
117 + Files.FileColumns.PARENT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400118
Mike Lockwoodd815f792010-07-12 08:49:01 -0400119 private final MediaScanner mMediaScanner;
Mike Lockwood56c85242014-03-07 13:29:08 -0800120 private MtpServer mServer;
121
122 // read from native code
123 private int mBatteryLevel;
124 private int mBatteryScale;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400125
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400126 static {
127 System.loadLibrary("media_jni");
128 }
129
Mike Lockwood56c85242014-03-07 13:29:08 -0800130 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
131 @Override
132 public void onReceive(Context context, Intent intent) {
133 String action = intent.getAction();
134 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
135 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
136 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
137 if (newLevel != mBatteryLevel) {
138 mBatteryLevel = newLevel;
139 if (mServer != null) {
140 // send device property changed event
141 mServer.sendDevicePropertyChanged(
142 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
143 }
144 }
145 }
146 }
147 };
148
Mike Lockwood73e56d92011-12-01 16:58:41 -0500149 public MtpDatabase(Context context, String volumeName, String storagePath,
150 String[] subDirectories) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400151 native_setup();
152
Mike Lockwood2837eef2010-08-31 16:25:12 -0400153 mContext = context;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800154 mPackageName = context.getPackageName();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400155 mMediaProvider = context.getContentResolver().acquireProvider("media");
156 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400157 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400158 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400159 mMediaScanner = new MediaScanner(context);
dujin.chafe464a72011-11-22 12:13:33 +0900160
Mike Lockwood73e56d92011-12-01 16:58:41 -0500161 mSubDirectories = subDirectories;
162 if (subDirectories != null) {
163 // Compute "where" string for restricting queries to subdirectories
164 StringBuilder builder = new StringBuilder();
165 builder.append("(");
166 int count = subDirectories.length;
167 for (int i = 0; i < count; i++) {
168 builder.append(Files.FileColumns.DATA + "=? OR "
169 + Files.FileColumns.DATA + " LIKE ?");
170 if (i != count - 1) {
171 builder.append(" OR ");
172 }
173 }
174 builder.append(")");
175 mSubDirectoriesWhere = builder.toString();
176
177 // Compute "where" arguments for restricting queries to subdirectories
178 mSubDirectoriesWhereArgs = new String[count * 2];
179 for (int i = 0, j = 0; i < count; i++) {
180 String path = subDirectories[i];
181 mSubDirectoriesWhereArgs[j++] = path;
182 mSubDirectoriesWhereArgs[j++] = path + "/%";
183 }
184 }
185
dujin.chafe464a72011-11-22 12:13:33 +0900186 // Set locale to MediaScanner.
187 Locale locale = context.getResources().getConfiguration().locale;
188 if (locale != null) {
189 String language = locale.getLanguage();
190 String country = locale.getCountry();
191 if (language != null) {
192 if (country != null) {
193 mMediaScanner.setLocale(language + "_" + country);
194 } else {
195 mMediaScanner.setLocale(language);
196 }
197 }
198 }
Mike Lockwood775de952011-03-05 17:34:11 -0500199 initDeviceProperties(context);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400200 }
201
Mike Lockwood56c85242014-03-07 13:29:08 -0800202 public void setServer(MtpServer server) {
203 mServer = server;
204
205 // register for battery notifications when we are connected
206 if (server != null) {
207 mContext.registerReceiver(mBatteryReceiver,
208 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
209 } else {
210 mContext.unregisterReceiver(mBatteryReceiver);
211 }
212 }
213
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400214 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400215 protected void finalize() throws Throwable {
216 try {
217 native_finalize();
Mike Lockwooddbead322010-08-30 09:27:55 -0400218 } finally {
219 super.finalize();
220 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400221 }
222
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400223 public void addStorage(MtpStorage storage) {
224 mStorageMap.put(storage.getPath(), storage);
225 }
226
227 public void removeStorage(MtpStorage storage) {
228 mStorageMap.remove(storage.getPath());
229 }
230
Mike Lockwood775de952011-03-05 17:34:11 -0500231 private void initDeviceProperties(Context context) {
232 final String devicePropertiesName = "device-properties";
233 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
234 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400235
Mike Lockwood775de952011-03-05 17:34:11 -0500236 if (databaseFile.exists()) {
237 // for backward compatibility - read device properties from sqlite database
238 // and migrate them to shared prefs
239 SQLiteDatabase db = null;
240 Cursor c = null;
241 try {
242 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
243 if (db != null) {
244 c = db.query("properties", new String[] { "_id", "code", "value" },
245 null, null, null, null, null);
246 if (c != null) {
247 SharedPreferences.Editor e = mDeviceProperties.edit();
248 while (c.moveToNext()) {
249 String name = c.getString(1);
250 String value = c.getString(2);
251 e.putString(name, value);
252 }
253 e.commit();
254 }
255 }
256 } catch (Exception e) {
257 Log.e(TAG, "failed to migrate device properties", e);
258 } finally {
259 if (c != null) c.close();
260 if (db != null) db.close();
261 }
jangwon.lee3ed02532013-07-22 19:52:48 +0900262 context.deleteDatabase(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400263 }
264 }
265
Mike Lockwood73e56d92011-12-01 16:58:41 -0500266 // check to see if the path is contained in one of our storage subdirectories
267 // returns true if we have no special subdirectories
268 private boolean inStorageSubDirectory(String path) {
269 if (mSubDirectories == null) return true;
270 if (path == null) return false;
271
272 boolean allowed = false;
273 int pathLength = path.length();
274 for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
275 String subdir = mSubDirectories[i];
276 int subdirLength = subdir.length();
277 if (subdirLength < pathLength &&
278 path.charAt(subdirLength) == '/' &&
279 path.startsWith(subdir)) {
280 allowed = true;
281 }
282 }
283 return allowed;
284 }
285
286 // check to see if the path matches one of our storage subdirectories
287 // returns true if we have no special subdirectories
288 private boolean isStorageSubDirectory(String path) {
289 if (mSubDirectories == null) return false;
290 for (int i = 0; i < mSubDirectories.length; i++) {
291 if (path.equals(mSubDirectories[i])) {
292 return true;
293 }
294 }
295 return false;
296 }
297
Mike Lockwoodd815f792010-07-12 08:49:01 -0400298 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400299 int storageId, long size, long modified) {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500300 // if mSubDirectories is not null, do not allow copying files to any other locations
301 if (!inStorageSubDirectory(path)) return -1;
302
303 // make sure the object does not exist
Mike Lockwoodbafca212010-12-13 21:50:09 -0800304 if (path != null) {
305 Cursor c = null;
306 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800307 c = mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, PATH_WHERE,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800308 new String[] { path }, null, null);
Mike Lockwoodbafca212010-12-13 21:50:09 -0800309 if (c != null && c.getCount() > 0) {
310 Log.w(TAG, "file already exists in beginSendObject: " + path);
311 return -1;
312 }
313 } catch (RemoteException e) {
314 Log.e(TAG, "RemoteException in beginSendObject", e);
315 } finally {
316 if (c != null) {
317 c.close();
318 }
319 }
320 }
321
Mike Lockwood2837eef2010-08-31 16:25:12 -0400322 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400323 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400324 values.put(Files.FileColumns.DATA, path);
325 values.put(Files.FileColumns.FORMAT, format);
326 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400327 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400328 values.put(Files.FileColumns.SIZE, size);
329 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400330
331 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800332 Uri uri = mMediaProvider.insert(mPackageName, mObjectsUri, values);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400333 if (uri != null) {
334 return Integer.parseInt(uri.getPathSegments().get(2));
335 } else {
336 return -1;
337 }
338 } catch (RemoteException e) {
339 Log.e(TAG, "RemoteException in beginSendObject", e);
340 return -1;
341 }
342 }
343
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800344 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400345 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400346 // handle abstract playlists separately
347 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400348 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400349 // extract name from path
350 String name = path;
351 int lastSlash = name.lastIndexOf('/');
352 if (lastSlash >= 0) {
353 name = name.substring(lastSlash + 1);
354 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800355 // strip trailing ".pla" from the name
356 if (name.endsWith(".pla")) {
357 name = name.substring(0, name.length() - 4);
358 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400359
360 ContentValues values = new ContentValues(1);
361 values.put(Audio.Playlists.DATA, path);
362 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500363 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800364 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400365 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
366 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800367 Uri uri = mMediaProvider.insert(mPackageName,
368 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400369 } catch (RemoteException e) {
370 Log.e(TAG, "RemoteException in endSendObject", e);
371 }
372 } else {
Mike Lockwoodc37255d2010-09-10 14:47:36 -0400373 mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400374 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400375 } else {
376 deleteFile(handle);
377 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400378 }
379
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400380 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500381 String where;
382 String[] whereArgs;
383
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400384 if (storageID == 0xFFFFFFFF) {
385 // query all stores
386 if (format == 0) {
387 // query all formats
388 if (parent == 0) {
389 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500390 where = null;
391 whereArgs = null;
392 } else {
393 if (parent == 0xFFFFFFFF) {
394 // all objects in root of store
395 parent = 0;
396 }
397 where = PARENT_WHERE;
398 whereArgs = new String[] { Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400399 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400400 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400401 // query specific format
402 if (parent == 0) {
403 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500404 where = FORMAT_WHERE;
405 whereArgs = new String[] { Integer.toString(format) };
406 } else {
407 if (parent == 0xFFFFFFFF) {
408 // all objects in root of store
409 parent = 0;
410 }
411 where = FORMAT_PARENT_WHERE;
412 whereArgs = new String[] { Integer.toString(format),
413 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400414 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400415 }
416 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400417 // query specific store
418 if (format == 0) {
419 // query all formats
420 if (parent == 0) {
421 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500422 where = STORAGE_WHERE;
423 whereArgs = new String[] { Integer.toString(storageID) };
424 } else {
425 if (parent == 0xFFFFFFFF) {
426 // all objects in root of store
427 parent = 0;
428 }
429 where = STORAGE_PARENT_WHERE;
430 whereArgs = new String[] { Integer.toString(storageID),
431 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400432 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400433 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400434 // query specific format
435 if (parent == 0) {
436 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500437 where = STORAGE_FORMAT_WHERE;
438 whereArgs = new String[] { Integer.toString(storageID),
439 Integer.toString(format) };
440 } else {
441 if (parent == 0xFFFFFFFF) {
442 // all objects in root of store
443 parent = 0;
444 }
445 where = STORAGE_FORMAT_PARENT_WHERE;
446 whereArgs = new String[] { Integer.toString(storageID),
447 Integer.toString(format),
448 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400449 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400450 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400451 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500452
453 // if we are restricting queries to mSubDirectories, we need to add the restriction
454 // onto our "where" arguments
455 if (mSubDirectoriesWhere != null) {
456 if (where == null) {
457 where = mSubDirectoriesWhere;
458 whereArgs = mSubDirectoriesWhereArgs;
459 } else {
460 where = where + " AND " + mSubDirectoriesWhere;
461
462 // create new array to hold whereArgs and mSubDirectoriesWhereArgs
463 String[] newWhereArgs =
464 new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
465 int i, j;
466 for (i = 0; i < whereArgs.length; i++) {
467 newWhereArgs[i] = whereArgs[i];
468 }
469 for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
470 newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
471 }
472 whereArgs = newWhereArgs;
473 }
474 }
475
Dianne Hackborn35654b62013-01-14 17:38:02 -0800476 return mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, where,
477 whereArgs, null, null);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400478 }
479
480 private int[] getObjectList(int storageID, int format, int parent) {
481 Cursor c = null;
482 try {
483 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400484 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400485 return null;
486 }
487 int count = c.getCount();
488 if (count > 0) {
489 int[] result = new int[count];
490 for (int i = 0; i < count; i++) {
491 c.moveToNext();
492 result[i] = c.getInt(0);
493 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400494 return result;
495 }
496 } catch (RemoteException e) {
497 Log.e(TAG, "RemoteException in getObjectList", e);
498 } finally {
499 if (c != null) {
500 c.close();
501 }
502 }
503 return null;
504 }
505
Mike Lockwood7a047c82010-08-02 10:52:20 -0400506 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400507 Cursor c = null;
508 try {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400509 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400510 if (c != null) {
511 return c.getCount();
512 }
513 } catch (RemoteException e) {
514 Log.e(TAG, "RemoteException in getNumObjects", e);
515 } finally {
516 if (c != null) {
517 c.close();
518 }
519 }
520 return -1;
521 }
522
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400523 private int[] getSupportedPlaybackFormats() {
524 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400525 // allow transfering arbitrary files
526 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400527
Mike Lockwood792ec842010-09-09 15:30:10 -0400528 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400529 MtpConstants.FORMAT_TEXT,
530 MtpConstants.FORMAT_HTML,
531 MtpConstants.FORMAT_WAV,
532 MtpConstants.FORMAT_MP3,
533 MtpConstants.FORMAT_MPEG,
534 MtpConstants.FORMAT_EXIF_JPEG,
535 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800536 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400537 MtpConstants.FORMAT_GIF,
538 MtpConstants.FORMAT_JFIF,
539 MtpConstants.FORMAT_PNG,
540 MtpConstants.FORMAT_TIFF,
541 MtpConstants.FORMAT_WMA,
542 MtpConstants.FORMAT_OGG,
543 MtpConstants.FORMAT_AAC,
544 MtpConstants.FORMAT_MP4_CONTAINER,
545 MtpConstants.FORMAT_MP2,
546 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400547 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400548 MtpConstants.FORMAT_WPL_PLAYLIST,
549 MtpConstants.FORMAT_M3U_PLAYLIST,
550 MtpConstants.FORMAT_PLS_PLAYLIST,
551 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800552 MtpConstants.FORMAT_FLAC,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400553 };
554 }
555
556 private int[] getSupportedCaptureFormats() {
557 // no capture formats yet
558 return null;
559 }
560
Mike Lockwoodae078f72010-09-26 12:35:51 -0400561 static final int[] FILE_PROPERTIES = {
562 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
563 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400564 MtpConstants.PROPERTY_STORAGE_ID,
565 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400566 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400567 MtpConstants.PROPERTY_OBJECT_SIZE,
568 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400569 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400570 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400571 MtpConstants.PROPERTY_PERSISTENT_UID,
572 MtpConstants.PROPERTY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400573 MtpConstants.PROPERTY_DATE_ADDED,
574 };
575
576 static final int[] AUDIO_PROPERTIES = {
577 // NOTE must match FILE_PROPERTIES above
578 MtpConstants.PROPERTY_STORAGE_ID,
579 MtpConstants.PROPERTY_OBJECT_FORMAT,
580 MtpConstants.PROPERTY_PROTECTION_STATUS,
581 MtpConstants.PROPERTY_OBJECT_SIZE,
582 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
583 MtpConstants.PROPERTY_DATE_MODIFIED,
584 MtpConstants.PROPERTY_PARENT_OBJECT,
585 MtpConstants.PROPERTY_PERSISTENT_UID,
586 MtpConstants.PROPERTY_NAME,
587 MtpConstants.PROPERTY_DISPLAY_NAME,
588 MtpConstants.PROPERTY_DATE_ADDED,
589
590 // audio specific properties
591 MtpConstants.PROPERTY_ARTIST,
592 MtpConstants.PROPERTY_ALBUM_NAME,
593 MtpConstants.PROPERTY_ALBUM_ARTIST,
594 MtpConstants.PROPERTY_TRACK,
595 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
596 MtpConstants.PROPERTY_DURATION,
597 MtpConstants.PROPERTY_GENRE,
598 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700599 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
600 MtpConstants.PROPERTY_BITRATE_TYPE,
601 MtpConstants.PROPERTY_AUDIO_BITRATE,
602 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
603 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400604 };
605
606 static final int[] VIDEO_PROPERTIES = {
607 // NOTE must match FILE_PROPERTIES above
608 MtpConstants.PROPERTY_STORAGE_ID,
609 MtpConstants.PROPERTY_OBJECT_FORMAT,
610 MtpConstants.PROPERTY_PROTECTION_STATUS,
611 MtpConstants.PROPERTY_OBJECT_SIZE,
612 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
613 MtpConstants.PROPERTY_DATE_MODIFIED,
614 MtpConstants.PROPERTY_PARENT_OBJECT,
615 MtpConstants.PROPERTY_PERSISTENT_UID,
616 MtpConstants.PROPERTY_NAME,
617 MtpConstants.PROPERTY_DISPLAY_NAME,
618 MtpConstants.PROPERTY_DATE_ADDED,
619
620 // video specific properties
621 MtpConstants.PROPERTY_ARTIST,
622 MtpConstants.PROPERTY_ALBUM_NAME,
623 MtpConstants.PROPERTY_DURATION,
624 MtpConstants.PROPERTY_DESCRIPTION,
625 };
626
627 static final int[] IMAGE_PROPERTIES = {
628 // NOTE must match FILE_PROPERTIES above
629 MtpConstants.PROPERTY_STORAGE_ID,
630 MtpConstants.PROPERTY_OBJECT_FORMAT,
631 MtpConstants.PROPERTY_PROTECTION_STATUS,
632 MtpConstants.PROPERTY_OBJECT_SIZE,
633 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
634 MtpConstants.PROPERTY_DATE_MODIFIED,
635 MtpConstants.PROPERTY_PARENT_OBJECT,
636 MtpConstants.PROPERTY_PERSISTENT_UID,
637 MtpConstants.PROPERTY_NAME,
638 MtpConstants.PROPERTY_DISPLAY_NAME,
639 MtpConstants.PROPERTY_DATE_ADDED,
640
641 // image specific properties
642 MtpConstants.PROPERTY_DESCRIPTION,
643 };
644
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500645 static final int[] ALL_PROPERTIES = {
646 // NOTE must match FILE_PROPERTIES above
647 MtpConstants.PROPERTY_STORAGE_ID,
648 MtpConstants.PROPERTY_OBJECT_FORMAT,
649 MtpConstants.PROPERTY_PROTECTION_STATUS,
650 MtpConstants.PROPERTY_OBJECT_SIZE,
651 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
652 MtpConstants.PROPERTY_DATE_MODIFIED,
653 MtpConstants.PROPERTY_PARENT_OBJECT,
654 MtpConstants.PROPERTY_PERSISTENT_UID,
655 MtpConstants.PROPERTY_NAME,
656 MtpConstants.PROPERTY_DISPLAY_NAME,
657 MtpConstants.PROPERTY_DATE_ADDED,
658
659 // image specific properties
660 MtpConstants.PROPERTY_DESCRIPTION,
661
662 // audio specific properties
663 MtpConstants.PROPERTY_ARTIST,
664 MtpConstants.PROPERTY_ALBUM_NAME,
665 MtpConstants.PROPERTY_ALBUM_ARTIST,
666 MtpConstants.PROPERTY_TRACK,
667 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
668 MtpConstants.PROPERTY_DURATION,
669 MtpConstants.PROPERTY_GENRE,
670 MtpConstants.PROPERTY_COMPOSER,
671
672 // video specific properties
673 MtpConstants.PROPERTY_ARTIST,
674 MtpConstants.PROPERTY_ALBUM_NAME,
675 MtpConstants.PROPERTY_DURATION,
676 MtpConstants.PROPERTY_DESCRIPTION,
677
678 // image specific properties
679 MtpConstants.PROPERTY_DESCRIPTION,
680 };
681
Mike Lockwoodae078f72010-09-26 12:35:51 -0400682 private int[] getSupportedObjectProperties(int format) {
683 switch (format) {
684 case MtpConstants.FORMAT_MP3:
685 case MtpConstants.FORMAT_WAV:
686 case MtpConstants.FORMAT_WMA:
687 case MtpConstants.FORMAT_OGG:
688 case MtpConstants.FORMAT_AAC:
689 return AUDIO_PROPERTIES;
690 case MtpConstants.FORMAT_MPEG:
691 case MtpConstants.FORMAT_3GP_CONTAINER:
692 case MtpConstants.FORMAT_WMV:
693 return VIDEO_PROPERTIES;
694 case MtpConstants.FORMAT_EXIF_JPEG:
695 case MtpConstants.FORMAT_GIF:
696 case MtpConstants.FORMAT_PNG:
697 case MtpConstants.FORMAT_BMP:
698 return IMAGE_PROPERTIES;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500699 case 0:
700 return ALL_PROPERTIES;
Mike Lockwoodae078f72010-09-26 12:35:51 -0400701 default:
702 return FILE_PROPERTIES;
703 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400704 }
705
706 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400707 return new int[] {
708 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
709 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800710 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood56c85242014-03-07 13:29:08 -0800711 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400712 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400713 }
714
Mike Lockwoodae078f72010-09-26 12:35:51 -0400715
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500716 private MtpPropertyList getObjectPropertyList(long handle, int format, long property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400717 int groupCode, int depth) {
718 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400719 if (groupCode != 0) {
720 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
721 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400722
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500723 MtpPropertyGroup propertyGroup;
724 if (property == 0xFFFFFFFFL) {
725 propertyGroup = mPropertyGroupsByFormat.get(format);
726 if (propertyGroup == null) {
727 int[] propertyList = getSupportedObjectProperties(format);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800728 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
729 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500730 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400731 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500732 } else {
733 propertyGroup = mPropertyGroupsByProperty.get(property);
734 if (propertyGroup == null) {
735 int[] propertyList = new int[] { (int)property };
Dianne Hackborn35654b62013-01-14 17:38:02 -0800736 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
737 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500738 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400739 }
740 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500741
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400742 return propertyGroup.getPropertyList((int)handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400743 }
744
Mike Lockwood5ebac832010-10-12 11:33:47 -0400745 private int renameFile(int handle, String newName) {
746 Cursor c = null;
747
748 // first compute current path
749 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400750 String[] whereArgs = new String[] { Integer.toString(handle) };
751 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800752 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_PROJECTION, ID_WHERE,
753 whereArgs, null, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400754 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500755 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400756 }
757 } catch (RemoteException e) {
758 Log.e(TAG, "RemoteException in getObjectFilePath", e);
759 return MtpConstants.RESPONSE_GENERAL_ERROR;
760 } finally {
761 if (c != null) {
762 c.close();
763 }
764 }
765 if (path == null) {
766 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
767 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400768
Mike Lockwood73e56d92011-12-01 16:58:41 -0500769 // do not allow renaming any of the special subdirectories
770 if (isStorageSubDirectory(path)) {
771 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
772 }
773
Mike Lockwood5ebac832010-10-12 11:33:47 -0400774 // now rename the file. make sure this succeeds before updating database
775 File oldFile = new File(path);
776 int lastSlash = path.lastIndexOf('/');
777 if (lastSlash <= 1) {
778 return MtpConstants.RESPONSE_GENERAL_ERROR;
779 }
780 String newPath = path.substring(0, lastSlash + 1) + newName;
781 File newFile = new File(newPath);
782 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400783 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800784 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400785 return MtpConstants.RESPONSE_GENERAL_ERROR;
786 }
787
788 // finally update database
789 ContentValues values = new ContentValues();
790 values.put(Files.FileColumns.DATA, newPath);
791 int updated = 0;
792 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400793 // note - we are relying on a special case in MediaProvider.update() to update
794 // the paths for all children in the case where this is a directory.
Dianne Hackborn35654b62013-01-14 17:38:02 -0800795 updated = mMediaProvider.update(mPackageName, mObjectsUri, values, ID_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400796 } catch (RemoteException e) {
797 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
798 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400799 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400800 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
801 // this shouldn't happen, but if it does we need to rename the file to its original name
802 newFile.renameTo(oldFile);
803 return MtpConstants.RESPONSE_GENERAL_ERROR;
804 }
805
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800806 // check if nomedia status changed
807 if (newFile.isDirectory()) {
808 // for directories, check if renamed from something hidden to something non-hidden
809 if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
810 // directory was unhidden
811 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800812 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, newPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800813 } catch (RemoteException e) {
814 Log.e(TAG, "failed to unhide/rescan for " + newPath);
815 }
816 }
817 } else {
818 // for files, check if renamed from .nomedia to something else
819 if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
820 && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
821 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800822 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800823 } catch (RemoteException e) {
824 Log.e(TAG, "failed to unhide/rescan for " + newPath);
825 }
826 }
827 }
828
Mike Lockwood5ebac832010-10-12 11:33:47 -0400829 return MtpConstants.RESPONSE_OK;
830 }
831
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400832 private int setObjectProperty(int handle, int property,
833 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400834 switch (property) {
835 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
836 return renameFile(handle, stringValue);
837
838 default:
839 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
840 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400841 }
842
843 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400844 switch (property) {
845 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
846 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500847 // writable string properties kept in shared preferences
848 String value = mDeviceProperties.getString(Integer.toString(property), "");
849 int length = value.length();
850 if (length > 255) {
851 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400852 }
Mike Lockwood775de952011-03-05 17:34:11 -0500853 value.getChars(0, length, outStringValue, 0);
854 outStringValue[length] = 0;
855 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400856
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800857 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
858 // use screen size as max image size
859 Display display = ((WindowManager)mContext.getSystemService(
860 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700861 int width = display.getMaximumSizeDimension();
862 int height = display.getMaximumSizeDimension();
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800863 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
864 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
865 outStringValue[imageSize.length()] = 0;
866 return MtpConstants.RESPONSE_OK;
867
Mike Lockwood56c85242014-03-07 13:29:08 -0800868 // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
869
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800870 default:
871 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
872 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400873 }
874
875 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400876 switch (property) {
877 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
878 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500879 // writable string properties kept in shared prefs
880 SharedPreferences.Editor e = mDeviceProperties.edit();
881 e.putString(Integer.toString(property), stringValue);
882 return (e.commit() ? MtpConstants.RESPONSE_OK
883 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400884 }
885
886 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
887 }
888
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400889 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700890 char[] outName, long[] outCreatedModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400891 Cursor c = null;
892 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800893 c = mMediaProvider.query(mPackageName, mObjectsUri, OBJECT_INFO_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800894 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400895 if (c != null && c.moveToNext()) {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400896 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400897 outStorageFormatParent[1] = c.getInt(2);
898 outStorageFormatParent[2] = c.getInt(3);
899
900 // extract name from path
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400901 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400902 int lastSlash = path.lastIndexOf('/');
903 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
904 int end = path.length();
905 if (end - start > 255) {
906 end = start + 255;
907 }
908 path.getChars(start, end, outName, 0);
909 outName[end - start] = 0;
910
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700911 outCreatedModified[0] = c.getLong(5);
912 outCreatedModified[1] = c.getLong(6);
913 // use modification date as creation date if date added is not set
914 if (outCreatedModified[0] == 0) {
915 outCreatedModified[0] = outCreatedModified[1];
916 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400917 return true;
918 }
919 } catch (RemoteException e) {
Mike Lockwood2b5f9ad12010-10-29 19:16:27 -0400920 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400921 } finally {
922 if (c != null) {
923 c.close();
924 }
925 }
926 return false;
927 }
928
Mike Lockwood365e03e2010-12-08 16:08:01 -0800929 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400930 if (handle == 0) {
931 // special case root directory
932 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
933 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800934 outFileLengthFormat[0] = 0;
935 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400936 return MtpConstants.RESPONSE_OK;
937 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400938 Cursor c = null;
939 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800940 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800941 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400942 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500943 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400944 path.getChars(0, path.length(), outFilePath, 0);
945 outFilePath[path.length()] = 0;
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700946 // File transfers from device to host will likely fail if the size is incorrect.
947 // So to be safe, use the actual file size here.
948 outFileLengthFormat[0] = new File(path).length();
949 outFileLengthFormat[1] = c.getLong(2);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400950 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400951 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400952 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400953 }
954 } catch (RemoteException e) {
955 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400956 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400957 } finally {
958 if (c != null) {
959 c.close();
960 }
961 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400962 }
963
Mike Lockwood59c777a2010-08-02 10:37:41 -0400964 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400965 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800966 String path = null;
967 int format = 0;
968
969 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400970 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800971 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800972 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800973 if (c != null && c.moveToNext()) {
974 // don't convert to media path here, since we will be matching
975 // against paths in the database matching /data/media
976 path = c.getString(1);
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700977 format = c.getInt(2);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800978 } else {
979 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
980 }
981
982 if (path == null || format == 0) {
983 return MtpConstants.RESPONSE_GENERAL_ERROR;
984 }
985
Mike Lockwood73e56d92011-12-01 16:58:41 -0500986 // do not allow deleting any of the special subdirectories
987 if (isStorageSubDirectory(path)) {
988 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
989 }
990
Mike Lockwood55f808c2010-12-14 13:14:29 -0800991 if (format == MtpConstants.FORMAT_ASSOCIATION) {
992 // recursive case - delete all children first
993 Uri uri = Files.getMtpObjectsUri(mVolumeName);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800994 int count = mMediaProvider.delete(mPackageName, uri,
Mike Lockwood1e855d92012-06-26 16:31:41 -0700995 // the 'like' makes it use the index, the 'lower()' makes it correct
996 // when the path contains sqlite wildcard characters
997 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
998 new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800999 }
1000
1001 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
Dianne Hackborn35654b62013-01-14 17:38:02 -08001002 if (mMediaProvider.delete(mPackageName, uri, null, null) > 0) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001003 if (format != MtpConstants.FORMAT_ASSOCIATION
1004 && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
1005 try {
1006 String parentPath = path.substring(0, path.lastIndexOf("/"));
Dianne Hackborn35654b62013-01-14 17:38:02 -08001007 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001008 } catch (RemoteException e) {
1009 Log.e(TAG, "failed to unhide/rescan for " + path);
1010 }
1011 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001012 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001013 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001014 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001015 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001016 } catch (RemoteException e) {
1017 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -04001018 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -08001019 } finally {
1020 if (c != null) {
1021 c.close();
1022 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001023 }
1024 }
1025
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001026 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -04001027 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001028 Cursor c = null;
1029 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -08001030 c = mMediaProvider.query(mPackageName, uri, ID_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001031 if (c == null) {
1032 return null;
1033 }
1034 int count = c.getCount();
1035 if (count > 0) {
1036 int[] result = new int[count];
1037 for (int i = 0; i < count; i++) {
1038 c.moveToNext();
1039 result[i] = c.getInt(0);
1040 }
1041 return result;
1042 }
1043 } catch (RemoteException e) {
1044 Log.e(TAG, "RemoteException in getObjectList", e);
1045 } finally {
1046 if (c != null) {
1047 c.close();
1048 }
1049 }
1050 return null;
1051 }
1052
1053 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001054 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -04001055 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001056 int count = references.length;
1057 ContentValues[] valuesList = new ContentValues[count];
1058 for (int i = 0; i < count; i++) {
1059 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -04001060 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001061 valuesList[i] = values;
1062 }
1063 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -08001064 if (mMediaProvider.bulkInsert(mPackageName, uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001065 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001066 }
1067 } catch (RemoteException e) {
1068 Log.e(TAG, "RemoteException in setObjectReferences", e);
1069 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001070 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001071 }
1072
Mike Lockwood2837eef2010-08-31 16:25:12 -04001073 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001074 mDatabaseModified = false;
1075 }
1076
1077 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001078 if (mDatabaseModified) {
Mike Lockwooda3156052010-11-20 12:28:27 -05001079 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -04001080 mDatabaseModified = false;
1081 }
1082 }
1083
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001084 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +00001085 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001086
1087 private native final void native_setup();
1088 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001089}