blob: 5d9355aae00be3e54909fac395b59bfa23d8abf4 [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 Lockwood71827742015-01-23 10:50:08 -080091 private static final String[] FORMAT_PROJECTION = new String[] {
92 Files.FileColumns._ID, // 0
93 Files.FileColumns.FORMAT, // 1
94 };
Mike Lockwoodf6f16612012-09-12 15:50:59 -070095 private static final String[] PATH_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040096 Files.FileColumns._ID, // 0
97 Files.FileColumns.DATA, // 1
Mike Lockwoodf6f16612012-09-12 15:50:59 -070098 Files.FileColumns.FORMAT, // 2
Mike Lockwoodd21eac92010-07-03 00:44:05 -040099 };
100 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400101 Files.FileColumns._ID, // 0
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400102 Files.FileColumns.STORAGE_ID, // 1
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400103 Files.FileColumns.FORMAT, // 2
104 Files.FileColumns.PARENT, // 3
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400105 Files.FileColumns.DATA, // 4
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700106 Files.FileColumns.DATE_ADDED, // 5
107 Files.FileColumns.DATE_MODIFIED, // 6
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400108 };
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400109 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwoodbafca212010-12-13 21:50:09 -0800110 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400111
112 private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
Mike Lockwood58e6831c2012-09-11 10:49:34 -0700113 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
114 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400115 private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400116 + Files.FileColumns.FORMAT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400117 private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
118 + Files.FileColumns.PARENT + "=?";
119 private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
120 + Files.FileColumns.PARENT + "=?";
121 private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
122 + Files.FileColumns.PARENT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400123
Mike Lockwoodd815f792010-07-12 08:49:01 -0400124 private final MediaScanner mMediaScanner;
Mike Lockwood56c85242014-03-07 13:29:08 -0800125 private MtpServer mServer;
126
127 // read from native code
128 private int mBatteryLevel;
129 private int mBatteryScale;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400130
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400131 static {
132 System.loadLibrary("media_jni");
133 }
134
Mike Lockwood56c85242014-03-07 13:29:08 -0800135 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
136 @Override
137 public void onReceive(Context context, Intent intent) {
138 String action = intent.getAction();
139 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
140 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
141 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
142 if (newLevel != mBatteryLevel) {
143 mBatteryLevel = newLevel;
144 if (mServer != null) {
145 // send device property changed event
146 mServer.sendDevicePropertyChanged(
147 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
148 }
149 }
150 }
151 }
152 };
153
Mike Lockwood73e56d92011-12-01 16:58:41 -0500154 public MtpDatabase(Context context, String volumeName, String storagePath,
155 String[] subDirectories) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400156 native_setup();
157
Mike Lockwood2837eef2010-08-31 16:25:12 -0400158 mContext = context;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800159 mPackageName = context.getPackageName();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400160 mMediaProvider = context.getContentResolver().acquireProvider("media");
161 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400162 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400163 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400164 mMediaScanner = new MediaScanner(context);
dujin.chafe464a72011-11-22 12:13:33 +0900165
Mike Lockwood73e56d92011-12-01 16:58:41 -0500166 mSubDirectories = subDirectories;
167 if (subDirectories != null) {
168 // Compute "where" string for restricting queries to subdirectories
169 StringBuilder builder = new StringBuilder();
170 builder.append("(");
171 int count = subDirectories.length;
172 for (int i = 0; i < count; i++) {
173 builder.append(Files.FileColumns.DATA + "=? OR "
174 + Files.FileColumns.DATA + " LIKE ?");
175 if (i != count - 1) {
176 builder.append(" OR ");
177 }
178 }
179 builder.append(")");
180 mSubDirectoriesWhere = builder.toString();
181
182 // Compute "where" arguments for restricting queries to subdirectories
183 mSubDirectoriesWhereArgs = new String[count * 2];
184 for (int i = 0, j = 0; i < count; i++) {
185 String path = subDirectories[i];
186 mSubDirectoriesWhereArgs[j++] = path;
187 mSubDirectoriesWhereArgs[j++] = path + "/%";
188 }
189 }
190
dujin.chafe464a72011-11-22 12:13:33 +0900191 // Set locale to MediaScanner.
192 Locale locale = context.getResources().getConfiguration().locale;
193 if (locale != null) {
194 String language = locale.getLanguage();
195 String country = locale.getCountry();
196 if (language != null) {
197 if (country != null) {
198 mMediaScanner.setLocale(language + "_" + country);
199 } else {
200 mMediaScanner.setLocale(language);
201 }
202 }
203 }
Mike Lockwood775de952011-03-05 17:34:11 -0500204 initDeviceProperties(context);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400205 }
206
Mike Lockwood56c85242014-03-07 13:29:08 -0800207 public void setServer(MtpServer server) {
208 mServer = server;
209
Marco Nelissen1632fae2014-03-27 13:25:14 -0700210 // always unregister before registering
211 try {
212 mContext.unregisterReceiver(mBatteryReceiver);
213 } catch (IllegalArgumentException e) {
214 // wasn't previously registered, ignore
215 }
216
Mike Lockwood56c85242014-03-07 13:29:08 -0800217 // register for battery notifications when we are connected
218 if (server != null) {
219 mContext.registerReceiver(mBatteryReceiver,
220 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Mike Lockwood56c85242014-03-07 13:29:08 -0800221 }
222 }
223
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400224 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400225 protected void finalize() throws Throwable {
226 try {
227 native_finalize();
Mike Lockwooddbead322010-08-30 09:27:55 -0400228 } finally {
229 super.finalize();
230 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400231 }
232
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400233 public void addStorage(MtpStorage storage) {
234 mStorageMap.put(storage.getPath(), storage);
235 }
236
237 public void removeStorage(MtpStorage storage) {
238 mStorageMap.remove(storage.getPath());
239 }
240
Mike Lockwood775de952011-03-05 17:34:11 -0500241 private void initDeviceProperties(Context context) {
242 final String devicePropertiesName = "device-properties";
243 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
244 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400245
Mike Lockwood775de952011-03-05 17:34:11 -0500246 if (databaseFile.exists()) {
247 // for backward compatibility - read device properties from sqlite database
248 // and migrate them to shared prefs
249 SQLiteDatabase db = null;
250 Cursor c = null;
251 try {
252 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
253 if (db != null) {
254 c = db.query("properties", new String[] { "_id", "code", "value" },
255 null, null, null, null, null);
256 if (c != null) {
257 SharedPreferences.Editor e = mDeviceProperties.edit();
258 while (c.moveToNext()) {
259 String name = c.getString(1);
260 String value = c.getString(2);
261 e.putString(name, value);
262 }
263 e.commit();
264 }
265 }
266 } catch (Exception e) {
267 Log.e(TAG, "failed to migrate device properties", e);
268 } finally {
269 if (c != null) c.close();
270 if (db != null) db.close();
271 }
jangwon.lee3ed02532013-07-22 19:52:48 +0900272 context.deleteDatabase(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400273 }
274 }
275
Mike Lockwood73e56d92011-12-01 16:58:41 -0500276 // check to see if the path is contained in one of our storage subdirectories
277 // returns true if we have no special subdirectories
278 private boolean inStorageSubDirectory(String path) {
279 if (mSubDirectories == null) return true;
280 if (path == null) return false;
281
282 boolean allowed = false;
283 int pathLength = path.length();
284 for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
285 String subdir = mSubDirectories[i];
286 int subdirLength = subdir.length();
287 if (subdirLength < pathLength &&
288 path.charAt(subdirLength) == '/' &&
289 path.startsWith(subdir)) {
290 allowed = true;
291 }
292 }
293 return allowed;
294 }
295
296 // check to see if the path matches one of our storage subdirectories
297 // returns true if we have no special subdirectories
298 private boolean isStorageSubDirectory(String path) {
299 if (mSubDirectories == null) return false;
300 for (int i = 0; i < mSubDirectories.length; i++) {
301 if (path.equals(mSubDirectories[i])) {
302 return true;
303 }
304 }
305 return false;
306 }
307
Marco Nelissen5f411692014-09-26 16:03:49 -0700308 // returns true if the path is in the storage root
309 private boolean inStorageRoot(String path) {
310 try {
311 File f = new File(path);
312 String canonical = f.getCanonicalPath();
Marco Nelissenc1fda122014-10-15 14:32:22 -0700313 for (String root: mStorageMap.keySet()) {
314 if (canonical.startsWith(root)) {
315 return true;
316 }
Marco Nelissen5f411692014-09-26 16:03:49 -0700317 }
318 } catch (IOException e) {
319 // ignore
320 }
321 return false;
322 }
323
Mike Lockwoodd815f792010-07-12 08:49:01 -0400324 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400325 int storageId, long size, long modified) {
Marco Nelissen5f411692014-09-26 16:03:49 -0700326 // if the path is outside of the storage root, do not allow access
327 if (!inStorageRoot(path)) {
328 Log.e(TAG, "attempt to put file outside of storage area: " + path);
329 return -1;
330 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500331 // if mSubDirectories is not null, do not allow copying files to any other locations
332 if (!inStorageSubDirectory(path)) return -1;
333
334 // make sure the object does not exist
Mike Lockwoodbafca212010-12-13 21:50:09 -0800335 if (path != null) {
336 Cursor c = null;
337 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800338 c = mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, PATH_WHERE,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800339 new String[] { path }, null, null);
Mike Lockwoodbafca212010-12-13 21:50:09 -0800340 if (c != null && c.getCount() > 0) {
341 Log.w(TAG, "file already exists in beginSendObject: " + path);
342 return -1;
343 }
344 } catch (RemoteException e) {
345 Log.e(TAG, "RemoteException in beginSendObject", e);
346 } finally {
347 if (c != null) {
348 c.close();
349 }
350 }
351 }
352
Mike Lockwood2837eef2010-08-31 16:25:12 -0400353 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400354 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400355 values.put(Files.FileColumns.DATA, path);
356 values.put(Files.FileColumns.FORMAT, format);
357 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400358 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400359 values.put(Files.FileColumns.SIZE, size);
360 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400361
362 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800363 Uri uri = mMediaProvider.insert(mPackageName, mObjectsUri, values);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400364 if (uri != null) {
365 return Integer.parseInt(uri.getPathSegments().get(2));
366 } else {
367 return -1;
368 }
369 } catch (RemoteException e) {
370 Log.e(TAG, "RemoteException in beginSendObject", e);
371 return -1;
372 }
373 }
374
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800375 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400376 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400377 // handle abstract playlists separately
378 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400379 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400380 // extract name from path
381 String name = path;
382 int lastSlash = name.lastIndexOf('/');
383 if (lastSlash >= 0) {
384 name = name.substring(lastSlash + 1);
385 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800386 // strip trailing ".pla" from the name
387 if (name.endsWith(".pla")) {
388 name = name.substring(0, name.length() - 4);
389 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400390
391 ContentValues values = new ContentValues(1);
392 values.put(Audio.Playlists.DATA, path);
393 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500394 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800395 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400396 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
397 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800398 Uri uri = mMediaProvider.insert(mPackageName,
399 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400400 } catch (RemoteException e) {
401 Log.e(TAG, "RemoteException in endSendObject", e);
402 }
403 } else {
Mike Lockwoodc37255d2010-09-10 14:47:36 -0400404 mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400405 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400406 } else {
407 deleteFile(handle);
408 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400409 }
410
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400411 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500412 String where;
413 String[] whereArgs;
414
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400415 if (storageID == 0xFFFFFFFF) {
416 // query all stores
417 if (format == 0) {
418 // query all formats
419 if (parent == 0) {
420 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500421 where = null;
422 whereArgs = null;
423 } else {
424 if (parent == 0xFFFFFFFF) {
425 // all objects in root of store
426 parent = 0;
427 }
428 where = PARENT_WHERE;
429 whereArgs = new String[] { Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400430 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400431 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400432 // query specific format
433 if (parent == 0) {
434 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500435 where = FORMAT_WHERE;
436 whereArgs = new String[] { Integer.toString(format) };
437 } else {
438 if (parent == 0xFFFFFFFF) {
439 // all objects in root of store
440 parent = 0;
441 }
442 where = FORMAT_PARENT_WHERE;
443 whereArgs = new String[] { Integer.toString(format),
444 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400445 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400446 }
447 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400448 // query specific store
449 if (format == 0) {
450 // query all formats
451 if (parent == 0) {
452 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500453 where = STORAGE_WHERE;
454 whereArgs = new String[] { Integer.toString(storageID) };
455 } else {
456 if (parent == 0xFFFFFFFF) {
457 // all objects in root of store
458 parent = 0;
459 }
460 where = STORAGE_PARENT_WHERE;
461 whereArgs = new String[] { Integer.toString(storageID),
462 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400463 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400464 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400465 // query specific format
466 if (parent == 0) {
467 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500468 where = STORAGE_FORMAT_WHERE;
469 whereArgs = new String[] { Integer.toString(storageID),
470 Integer.toString(format) };
471 } else {
472 if (parent == 0xFFFFFFFF) {
473 // all objects in root of store
474 parent = 0;
475 }
476 where = STORAGE_FORMAT_PARENT_WHERE;
477 whereArgs = new String[] { Integer.toString(storageID),
478 Integer.toString(format),
479 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400480 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400481 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400482 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500483
484 // if we are restricting queries to mSubDirectories, we need to add the restriction
485 // onto our "where" arguments
486 if (mSubDirectoriesWhere != null) {
487 if (where == null) {
488 where = mSubDirectoriesWhere;
489 whereArgs = mSubDirectoriesWhereArgs;
490 } else {
491 where = where + " AND " + mSubDirectoriesWhere;
492
493 // create new array to hold whereArgs and mSubDirectoriesWhereArgs
494 String[] newWhereArgs =
495 new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
496 int i, j;
497 for (i = 0; i < whereArgs.length; i++) {
498 newWhereArgs[i] = whereArgs[i];
499 }
500 for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
501 newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
502 }
503 whereArgs = newWhereArgs;
504 }
505 }
506
Dianne Hackborn35654b62013-01-14 17:38:02 -0800507 return mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, where,
508 whereArgs, null, null);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400509 }
510
511 private int[] getObjectList(int storageID, int format, int parent) {
512 Cursor c = null;
513 try {
514 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400515 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400516 return null;
517 }
518 int count = c.getCount();
519 if (count > 0) {
520 int[] result = new int[count];
521 for (int i = 0; i < count; i++) {
522 c.moveToNext();
523 result[i] = c.getInt(0);
524 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400525 return result;
526 }
527 } catch (RemoteException e) {
528 Log.e(TAG, "RemoteException in getObjectList", e);
529 } finally {
530 if (c != null) {
531 c.close();
532 }
533 }
534 return null;
535 }
536
Mike Lockwood7a047c82010-08-02 10:52:20 -0400537 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400538 Cursor c = null;
539 try {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400540 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400541 if (c != null) {
542 return c.getCount();
543 }
544 } catch (RemoteException e) {
545 Log.e(TAG, "RemoteException in getNumObjects", e);
546 } finally {
547 if (c != null) {
548 c.close();
549 }
550 }
551 return -1;
552 }
553
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400554 private int[] getSupportedPlaybackFormats() {
555 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400556 // allow transfering arbitrary files
557 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400558
Mike Lockwood792ec842010-09-09 15:30:10 -0400559 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400560 MtpConstants.FORMAT_TEXT,
561 MtpConstants.FORMAT_HTML,
562 MtpConstants.FORMAT_WAV,
563 MtpConstants.FORMAT_MP3,
564 MtpConstants.FORMAT_MPEG,
565 MtpConstants.FORMAT_EXIF_JPEG,
566 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800567 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400568 MtpConstants.FORMAT_GIF,
569 MtpConstants.FORMAT_JFIF,
570 MtpConstants.FORMAT_PNG,
571 MtpConstants.FORMAT_TIFF,
572 MtpConstants.FORMAT_WMA,
573 MtpConstants.FORMAT_OGG,
574 MtpConstants.FORMAT_AAC,
575 MtpConstants.FORMAT_MP4_CONTAINER,
576 MtpConstants.FORMAT_MP2,
577 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400578 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400579 MtpConstants.FORMAT_WPL_PLAYLIST,
580 MtpConstants.FORMAT_M3U_PLAYLIST,
581 MtpConstants.FORMAT_PLS_PLAYLIST,
582 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800583 MtpConstants.FORMAT_FLAC,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400584 };
585 }
586
587 private int[] getSupportedCaptureFormats() {
588 // no capture formats yet
589 return null;
590 }
591
Mike Lockwoodae078f72010-09-26 12:35:51 -0400592 static final int[] FILE_PROPERTIES = {
593 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
594 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400595 MtpConstants.PROPERTY_STORAGE_ID,
596 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400597 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400598 MtpConstants.PROPERTY_OBJECT_SIZE,
599 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400600 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400601 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400602 MtpConstants.PROPERTY_PERSISTENT_UID,
603 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800604 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400605 MtpConstants.PROPERTY_DATE_ADDED,
606 };
607
608 static final int[] AUDIO_PROPERTIES = {
609 // NOTE must match FILE_PROPERTIES above
610 MtpConstants.PROPERTY_STORAGE_ID,
611 MtpConstants.PROPERTY_OBJECT_FORMAT,
612 MtpConstants.PROPERTY_PROTECTION_STATUS,
613 MtpConstants.PROPERTY_OBJECT_SIZE,
614 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
615 MtpConstants.PROPERTY_DATE_MODIFIED,
616 MtpConstants.PROPERTY_PARENT_OBJECT,
617 MtpConstants.PROPERTY_PERSISTENT_UID,
618 MtpConstants.PROPERTY_NAME,
619 MtpConstants.PROPERTY_DISPLAY_NAME,
620 MtpConstants.PROPERTY_DATE_ADDED,
621
622 // audio specific properties
623 MtpConstants.PROPERTY_ARTIST,
624 MtpConstants.PROPERTY_ALBUM_NAME,
625 MtpConstants.PROPERTY_ALBUM_ARTIST,
626 MtpConstants.PROPERTY_TRACK,
627 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
628 MtpConstants.PROPERTY_DURATION,
629 MtpConstants.PROPERTY_GENRE,
630 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700631 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
632 MtpConstants.PROPERTY_BITRATE_TYPE,
633 MtpConstants.PROPERTY_AUDIO_BITRATE,
634 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
635 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400636 };
637
638 static final int[] VIDEO_PROPERTIES = {
639 // NOTE must match FILE_PROPERTIES above
640 MtpConstants.PROPERTY_STORAGE_ID,
641 MtpConstants.PROPERTY_OBJECT_FORMAT,
642 MtpConstants.PROPERTY_PROTECTION_STATUS,
643 MtpConstants.PROPERTY_OBJECT_SIZE,
644 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
645 MtpConstants.PROPERTY_DATE_MODIFIED,
646 MtpConstants.PROPERTY_PARENT_OBJECT,
647 MtpConstants.PROPERTY_PERSISTENT_UID,
648 MtpConstants.PROPERTY_NAME,
649 MtpConstants.PROPERTY_DISPLAY_NAME,
650 MtpConstants.PROPERTY_DATE_ADDED,
651
652 // video specific properties
653 MtpConstants.PROPERTY_ARTIST,
654 MtpConstants.PROPERTY_ALBUM_NAME,
655 MtpConstants.PROPERTY_DURATION,
656 MtpConstants.PROPERTY_DESCRIPTION,
657 };
658
659 static final int[] IMAGE_PROPERTIES = {
660 // NOTE must match FILE_PROPERTIES above
661 MtpConstants.PROPERTY_STORAGE_ID,
662 MtpConstants.PROPERTY_OBJECT_FORMAT,
663 MtpConstants.PROPERTY_PROTECTION_STATUS,
664 MtpConstants.PROPERTY_OBJECT_SIZE,
665 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
666 MtpConstants.PROPERTY_DATE_MODIFIED,
667 MtpConstants.PROPERTY_PARENT_OBJECT,
668 MtpConstants.PROPERTY_PERSISTENT_UID,
669 MtpConstants.PROPERTY_NAME,
670 MtpConstants.PROPERTY_DISPLAY_NAME,
671 MtpConstants.PROPERTY_DATE_ADDED,
672
673 // image specific properties
674 MtpConstants.PROPERTY_DESCRIPTION,
675 };
676
677 private int[] getSupportedObjectProperties(int format) {
678 switch (format) {
679 case MtpConstants.FORMAT_MP3:
680 case MtpConstants.FORMAT_WAV:
681 case MtpConstants.FORMAT_WMA:
682 case MtpConstants.FORMAT_OGG:
683 case MtpConstants.FORMAT_AAC:
684 return AUDIO_PROPERTIES;
685 case MtpConstants.FORMAT_MPEG:
686 case MtpConstants.FORMAT_3GP_CONTAINER:
687 case MtpConstants.FORMAT_WMV:
688 return VIDEO_PROPERTIES;
689 case MtpConstants.FORMAT_EXIF_JPEG:
690 case MtpConstants.FORMAT_GIF:
691 case MtpConstants.FORMAT_PNG:
692 case MtpConstants.FORMAT_BMP:
693 return IMAGE_PROPERTIES;
694 default:
695 return FILE_PROPERTIES;
696 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400697 }
698
699 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400700 return new int[] {
701 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
702 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800703 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood56c85242014-03-07 13:29:08 -0800704 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400705 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400706 }
707
Mike Lockwoodae078f72010-09-26 12:35:51 -0400708
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500709 private MtpPropertyList getObjectPropertyList(long handle, int format, long property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400710 int groupCode, int depth) {
711 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400712 if (groupCode != 0) {
713 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
714 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400715
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500716 MtpPropertyGroup propertyGroup;
717 if (property == 0xFFFFFFFFL) {
Mike Lockwood71827742015-01-23 10:50:08 -0800718 if (format == 0 && handle > 0) {
719 // return properties based on the object's format
720 format = getObjectFormat((int)handle);
721 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500722 propertyGroup = mPropertyGroupsByFormat.get(format);
723 if (propertyGroup == null) {
724 int[] propertyList = getSupportedObjectProperties(format);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800725 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
726 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500727 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400728 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500729 } else {
730 propertyGroup = mPropertyGroupsByProperty.get(property);
731 if (propertyGroup == null) {
732 int[] propertyList = new int[] { (int)property };
Dianne Hackborn35654b62013-01-14 17:38:02 -0800733 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
734 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500735 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400736 }
737 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500738
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400739 return propertyGroup.getPropertyList((int)handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400740 }
741
Mike Lockwood5ebac832010-10-12 11:33:47 -0400742 private int renameFile(int handle, String newName) {
743 Cursor c = null;
744
745 // first compute current path
746 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400747 String[] whereArgs = new String[] { Integer.toString(handle) };
748 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800749 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_PROJECTION, ID_WHERE,
750 whereArgs, null, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400751 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500752 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400753 }
754 } catch (RemoteException e) {
755 Log.e(TAG, "RemoteException in getObjectFilePath", e);
756 return MtpConstants.RESPONSE_GENERAL_ERROR;
757 } finally {
758 if (c != null) {
759 c.close();
760 }
761 }
762 if (path == null) {
763 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
764 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400765
Mike Lockwood73e56d92011-12-01 16:58:41 -0500766 // do not allow renaming any of the special subdirectories
767 if (isStorageSubDirectory(path)) {
768 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
769 }
770
Mike Lockwood5ebac832010-10-12 11:33:47 -0400771 // now rename the file. make sure this succeeds before updating database
772 File oldFile = new File(path);
773 int lastSlash = path.lastIndexOf('/');
774 if (lastSlash <= 1) {
775 return MtpConstants.RESPONSE_GENERAL_ERROR;
776 }
777 String newPath = path.substring(0, lastSlash + 1) + newName;
778 File newFile = new File(newPath);
779 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400780 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800781 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400782 return MtpConstants.RESPONSE_GENERAL_ERROR;
783 }
784
785 // finally update database
786 ContentValues values = new ContentValues();
787 values.put(Files.FileColumns.DATA, newPath);
788 int updated = 0;
789 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400790 // note - we are relying on a special case in MediaProvider.update() to update
791 // the paths for all children in the case where this is a directory.
Dianne Hackborn35654b62013-01-14 17:38:02 -0800792 updated = mMediaProvider.update(mPackageName, mObjectsUri, values, ID_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400793 } catch (RemoteException e) {
794 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
795 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400796 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400797 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
798 // this shouldn't happen, but if it does we need to rename the file to its original name
799 newFile.renameTo(oldFile);
800 return MtpConstants.RESPONSE_GENERAL_ERROR;
801 }
802
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800803 // check if nomedia status changed
804 if (newFile.isDirectory()) {
805 // for directories, check if renamed from something hidden to something non-hidden
806 if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
807 // directory was unhidden
808 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800809 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, newPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800810 } catch (RemoteException e) {
811 Log.e(TAG, "failed to unhide/rescan for " + newPath);
812 }
813 }
814 } else {
815 // for files, check if renamed from .nomedia to something else
816 if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
817 && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
818 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800819 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800820 } catch (RemoteException e) {
821 Log.e(TAG, "failed to unhide/rescan for " + newPath);
822 }
823 }
824 }
825
Mike Lockwood5ebac832010-10-12 11:33:47 -0400826 return MtpConstants.RESPONSE_OK;
827 }
828
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400829 private int setObjectProperty(int handle, int property,
830 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400831 switch (property) {
832 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
833 return renameFile(handle, stringValue);
834
835 default:
836 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
837 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400838 }
839
840 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400841 switch (property) {
842 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
843 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500844 // writable string properties kept in shared preferences
845 String value = mDeviceProperties.getString(Integer.toString(property), "");
846 int length = value.length();
847 if (length > 255) {
848 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400849 }
Mike Lockwood775de952011-03-05 17:34:11 -0500850 value.getChars(0, length, outStringValue, 0);
851 outStringValue[length] = 0;
852 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400853
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800854 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
855 // use screen size as max image size
856 Display display = ((WindowManager)mContext.getSystemService(
857 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700858 int width = display.getMaximumSizeDimension();
859 int height = display.getMaximumSizeDimension();
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800860 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
861 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
862 outStringValue[imageSize.length()] = 0;
863 return MtpConstants.RESPONSE_OK;
864
Mike Lockwood56c85242014-03-07 13:29:08 -0800865 // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
866
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800867 default:
868 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
869 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400870 }
871
872 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400873 switch (property) {
874 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
875 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500876 // writable string properties kept in shared prefs
877 SharedPreferences.Editor e = mDeviceProperties.edit();
878 e.putString(Integer.toString(property), stringValue);
879 return (e.commit() ? MtpConstants.RESPONSE_OK
880 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400881 }
882
883 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
884 }
885
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400886 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700887 char[] outName, long[] outCreatedModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400888 Cursor c = null;
889 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800890 c = mMediaProvider.query(mPackageName, mObjectsUri, OBJECT_INFO_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800891 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400892 if (c != null && c.moveToNext()) {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400893 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400894 outStorageFormatParent[1] = c.getInt(2);
895 outStorageFormatParent[2] = c.getInt(3);
896
897 // extract name from path
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400898 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400899 int lastSlash = path.lastIndexOf('/');
900 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
901 int end = path.length();
902 if (end - start > 255) {
903 end = start + 255;
904 }
905 path.getChars(start, end, outName, 0);
906 outName[end - start] = 0;
907
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700908 outCreatedModified[0] = c.getLong(5);
909 outCreatedModified[1] = c.getLong(6);
910 // use modification date as creation date if date added is not set
911 if (outCreatedModified[0] == 0) {
912 outCreatedModified[0] = outCreatedModified[1];
913 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400914 return true;
915 }
916 } catch (RemoteException e) {
Mike Lockwood2b5f9ad12010-10-29 19:16:27 -0400917 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400918 } finally {
919 if (c != null) {
920 c.close();
921 }
922 }
923 return false;
924 }
925
Mike Lockwood365e03e2010-12-08 16:08:01 -0800926 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400927 if (handle == 0) {
928 // special case root directory
929 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
930 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800931 outFileLengthFormat[0] = 0;
932 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400933 return MtpConstants.RESPONSE_OK;
934 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400935 Cursor c = null;
936 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800937 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800938 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400939 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500940 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400941 path.getChars(0, path.length(), outFilePath, 0);
942 outFilePath[path.length()] = 0;
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700943 // File transfers from device to host will likely fail if the size is incorrect.
944 // So to be safe, use the actual file size here.
945 outFileLengthFormat[0] = new File(path).length();
946 outFileLengthFormat[1] = c.getLong(2);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400947 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400948 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400949 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400950 }
951 } catch (RemoteException e) {
952 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400953 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400954 } finally {
955 if (c != null) {
956 c.close();
957 }
958 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400959 }
960
Mike Lockwood71827742015-01-23 10:50:08 -0800961 private int getObjectFormat(int handle) {
962 Cursor c = null;
963 try {
964 c = mMediaProvider.query(mPackageName, mObjectsUri, FORMAT_PROJECTION,
965 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
966 if (c != null && c.moveToNext()) {
967 return c.getInt(1);
968 } else {
969 return -1;
970 }
971 } catch (RemoteException e) {
972 Log.e(TAG, "RemoteException in getObjectFilePath", e);
973 return -1;
974 } finally {
975 if (c != null) {
976 c.close();
977 }
978 }
979 }
980
Mike Lockwood59c777a2010-08-02 10:37:41 -0400981 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400982 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800983 String path = null;
984 int format = 0;
985
986 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400987 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800988 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800989 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800990 if (c != null && c.moveToNext()) {
991 // don't convert to media path here, since we will be matching
992 // against paths in the database matching /data/media
993 path = c.getString(1);
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700994 format = c.getInt(2);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800995 } else {
996 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
997 }
998
999 if (path == null || format == 0) {
1000 return MtpConstants.RESPONSE_GENERAL_ERROR;
1001 }
1002
Mike Lockwood73e56d92011-12-01 16:58:41 -05001003 // do not allow deleting any of the special subdirectories
1004 if (isStorageSubDirectory(path)) {
1005 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
1006 }
1007
Mike Lockwood55f808c2010-12-14 13:14:29 -08001008 if (format == MtpConstants.FORMAT_ASSOCIATION) {
1009 // recursive case - delete all children first
1010 Uri uri = Files.getMtpObjectsUri(mVolumeName);
Dianne Hackborn35654b62013-01-14 17:38:02 -08001011 int count = mMediaProvider.delete(mPackageName, uri,
Mike Lockwood1e855d92012-06-26 16:31:41 -07001012 // the 'like' makes it use the index, the 'lower()' makes it correct
1013 // when the path contains sqlite wildcard characters
1014 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
1015 new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -08001016 }
1017
1018 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
Dianne Hackborn35654b62013-01-14 17:38:02 -08001019 if (mMediaProvider.delete(mPackageName, uri, null, null) > 0) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001020 if (format != MtpConstants.FORMAT_ASSOCIATION
1021 && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
1022 try {
1023 String parentPath = path.substring(0, path.lastIndexOf("/"));
Dianne Hackborn35654b62013-01-14 17:38:02 -08001024 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001025 } catch (RemoteException e) {
1026 Log.e(TAG, "failed to unhide/rescan for " + path);
1027 }
1028 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001029 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001030 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001031 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001032 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001033 } catch (RemoteException e) {
1034 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -04001035 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -08001036 } finally {
1037 if (c != null) {
1038 c.close();
1039 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001040 }
1041 }
1042
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001043 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -04001044 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001045 Cursor c = null;
1046 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -08001047 c = mMediaProvider.query(mPackageName, uri, ID_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001048 if (c == null) {
1049 return null;
1050 }
1051 int count = c.getCount();
1052 if (count > 0) {
1053 int[] result = new int[count];
1054 for (int i = 0; i < count; i++) {
1055 c.moveToNext();
1056 result[i] = c.getInt(0);
1057 }
1058 return result;
1059 }
1060 } catch (RemoteException e) {
1061 Log.e(TAG, "RemoteException in getObjectList", e);
1062 } finally {
1063 if (c != null) {
1064 c.close();
1065 }
1066 }
1067 return null;
1068 }
1069
1070 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001071 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -04001072 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001073 int count = references.length;
1074 ContentValues[] valuesList = new ContentValues[count];
1075 for (int i = 0; i < count; i++) {
1076 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -04001077 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001078 valuesList[i] = values;
1079 }
1080 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -08001081 if (mMediaProvider.bulkInsert(mPackageName, uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001082 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001083 }
1084 } catch (RemoteException e) {
1085 Log.e(TAG, "RemoteException in setObjectReferences", e);
1086 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001087 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001088 }
1089
Mike Lockwood2837eef2010-08-31 16:25:12 -04001090 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001091 mDatabaseModified = false;
1092 }
1093
1094 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001095 if (mDatabaseModified) {
Mike Lockwooda3156052010-11-20 12:28:27 -05001096 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -04001097 mDatabaseModified = false;
1098 }
1099 }
1100
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001101 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +00001102 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001103
1104 private native final void native_setup();
1105 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001106}