blob: 3541fba7f3b131ba563d6177a0d724682080d57d [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;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040031import android.os.RemoteException;
Mike Lockwooda3156052010-11-20 12:28:27 -050032import android.provider.MediaStore;
Mike Lockwood9a2046f2010-08-03 15:30:09 -040033import android.provider.MediaStore.Audio;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040034import android.provider.MediaStore.Files;
Mike Lockwoodae078f72010-09-26 12:35:51 -040035import android.provider.MediaStore.MediaColumns;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040036import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080037import android.view.Display;
38import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040039
Mike Lockwood5ebac832010-10-12 11:33:47 -040040import java.io.File;
Marco Nelissen5f411692014-09-26 16:03:49 -070041import java.io.IOException;
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 Lockwood71827742015-01-23 10:50:08 -080090 private static final String[] FORMAT_PROJECTION = new String[] {
91 Files.FileColumns._ID, // 0
92 Files.FileColumns.FORMAT, // 1
93 };
Mike Lockwoodf6f16612012-09-12 15:50:59 -070094 private static final String[] PATH_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040095 Files.FileColumns._ID, // 0
96 Files.FileColumns.DATA, // 1
Mike Lockwoodf6f16612012-09-12 15:50:59 -070097 Files.FileColumns.FORMAT, // 2
Mike Lockwoodd21eac92010-07-03 00:44:05 -040098 };
99 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400100 Files.FileColumns._ID, // 0
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400101 Files.FileColumns.STORAGE_ID, // 1
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400102 Files.FileColumns.FORMAT, // 2
103 Files.FileColumns.PARENT, // 3
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400104 Files.FileColumns.DATA, // 4
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700105 Files.FileColumns.DATE_ADDED, // 5
106 Files.FileColumns.DATE_MODIFIED, // 6
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400107 };
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400108 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwoodbafca212010-12-13 21:50:09 -0800109 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400110
111 private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
Mike Lockwood58e6831c2012-09-11 10:49:34 -0700112 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
113 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400114 private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400115 + Files.FileColumns.FORMAT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400116 private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
117 + Files.FileColumns.PARENT + "=?";
118 private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
119 + Files.FileColumns.PARENT + "=?";
120 private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
121 + Files.FileColumns.PARENT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400122
Mike Lockwoodd815f792010-07-12 08:49:01 -0400123 private final MediaScanner mMediaScanner;
Mike Lockwood56c85242014-03-07 13:29:08 -0800124 private MtpServer mServer;
125
126 // read from native code
127 private int mBatteryLevel;
128 private int mBatteryScale;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400129
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400130 static {
131 System.loadLibrary("media_jni");
132 }
133
Mike Lockwood56c85242014-03-07 13:29:08 -0800134 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
135 @Override
136 public void onReceive(Context context, Intent intent) {
137 String action = intent.getAction();
138 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
139 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
140 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
141 if (newLevel != mBatteryLevel) {
142 mBatteryLevel = newLevel;
143 if (mServer != null) {
144 // send device property changed event
145 mServer.sendDevicePropertyChanged(
146 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
147 }
148 }
149 }
150 }
151 };
152
Mike Lockwood73e56d92011-12-01 16:58:41 -0500153 public MtpDatabase(Context context, String volumeName, String storagePath,
154 String[] subDirectories) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400155 native_setup();
156
Mike Lockwood2837eef2010-08-31 16:25:12 -0400157 mContext = context;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800158 mPackageName = context.getPackageName();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400159 mMediaProvider = context.getContentResolver().acquireProvider("media");
160 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400161 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400162 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400163 mMediaScanner = new MediaScanner(context);
dujin.chafe464a72011-11-22 12:13:33 +0900164
Mike Lockwood73e56d92011-12-01 16:58:41 -0500165 mSubDirectories = subDirectories;
166 if (subDirectories != null) {
167 // Compute "where" string for restricting queries to subdirectories
168 StringBuilder builder = new StringBuilder();
169 builder.append("(");
170 int count = subDirectories.length;
171 for (int i = 0; i < count; i++) {
172 builder.append(Files.FileColumns.DATA + "=? OR "
173 + Files.FileColumns.DATA + " LIKE ?");
174 if (i != count - 1) {
175 builder.append(" OR ");
176 }
177 }
178 builder.append(")");
179 mSubDirectoriesWhere = builder.toString();
180
181 // Compute "where" arguments for restricting queries to subdirectories
182 mSubDirectoriesWhereArgs = new String[count * 2];
183 for (int i = 0, j = 0; i < count; i++) {
184 String path = subDirectories[i];
185 mSubDirectoriesWhereArgs[j++] = path;
186 mSubDirectoriesWhereArgs[j++] = path + "/%";
187 }
188 }
189
dujin.chafe464a72011-11-22 12:13:33 +0900190 // Set locale to MediaScanner.
191 Locale locale = context.getResources().getConfiguration().locale;
192 if (locale != null) {
193 String language = locale.getLanguage();
194 String country = locale.getCountry();
195 if (language != null) {
196 if (country != null) {
197 mMediaScanner.setLocale(language + "_" + country);
198 } else {
199 mMediaScanner.setLocale(language);
200 }
201 }
202 }
Mike Lockwood775de952011-03-05 17:34:11 -0500203 initDeviceProperties(context);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400204 }
205
Mike Lockwood56c85242014-03-07 13:29:08 -0800206 public void setServer(MtpServer server) {
207 mServer = server;
208
Marco Nelissen1632fae2014-03-27 13:25:14 -0700209 // always unregister before registering
210 try {
211 mContext.unregisterReceiver(mBatteryReceiver);
212 } catch (IllegalArgumentException e) {
213 // wasn't previously registered, ignore
214 }
215
Mike Lockwood56c85242014-03-07 13:29:08 -0800216 // register for battery notifications when we are connected
217 if (server != null) {
218 mContext.registerReceiver(mBatteryReceiver,
219 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Mike Lockwood56c85242014-03-07 13:29:08 -0800220 }
221 }
222
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400223 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400224 protected void finalize() throws Throwable {
225 try {
226 native_finalize();
Mike Lockwooddbead322010-08-30 09:27:55 -0400227 } finally {
228 super.finalize();
229 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400230 }
231
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400232 public void addStorage(MtpStorage storage) {
233 mStorageMap.put(storage.getPath(), storage);
234 }
235
236 public void removeStorage(MtpStorage storage) {
237 mStorageMap.remove(storage.getPath());
238 }
239
Mike Lockwood775de952011-03-05 17:34:11 -0500240 private void initDeviceProperties(Context context) {
241 final String devicePropertiesName = "device-properties";
242 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
243 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400244
Mike Lockwood775de952011-03-05 17:34:11 -0500245 if (databaseFile.exists()) {
246 // for backward compatibility - read device properties from sqlite database
247 // and migrate them to shared prefs
248 SQLiteDatabase db = null;
249 Cursor c = null;
250 try {
251 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
252 if (db != null) {
253 c = db.query("properties", new String[] { "_id", "code", "value" },
254 null, null, null, null, null);
255 if (c != null) {
256 SharedPreferences.Editor e = mDeviceProperties.edit();
257 while (c.moveToNext()) {
258 String name = c.getString(1);
259 String value = c.getString(2);
260 e.putString(name, value);
261 }
262 e.commit();
263 }
264 }
265 } catch (Exception e) {
266 Log.e(TAG, "failed to migrate device properties", e);
267 } finally {
268 if (c != null) c.close();
269 if (db != null) db.close();
270 }
jangwon.lee3ed02532013-07-22 19:52:48 +0900271 context.deleteDatabase(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400272 }
273 }
274
Mike Lockwood73e56d92011-12-01 16:58:41 -0500275 // check to see if the path is contained in one of our storage subdirectories
276 // returns true if we have no special subdirectories
277 private boolean inStorageSubDirectory(String path) {
278 if (mSubDirectories == null) return true;
279 if (path == null) return false;
280
281 boolean allowed = false;
282 int pathLength = path.length();
283 for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
284 String subdir = mSubDirectories[i];
285 int subdirLength = subdir.length();
286 if (subdirLength < pathLength &&
287 path.charAt(subdirLength) == '/' &&
288 path.startsWith(subdir)) {
289 allowed = true;
290 }
291 }
292 return allowed;
293 }
294
295 // check to see if the path matches one of our storage subdirectories
296 // returns true if we have no special subdirectories
297 private boolean isStorageSubDirectory(String path) {
298 if (mSubDirectories == null) return false;
299 for (int i = 0; i < mSubDirectories.length; i++) {
300 if (path.equals(mSubDirectories[i])) {
301 return true;
302 }
303 }
304 return false;
305 }
306
Marco Nelissen5f411692014-09-26 16:03:49 -0700307 // returns true if the path is in the storage root
308 private boolean inStorageRoot(String path) {
309 try {
310 File f = new File(path);
311 String canonical = f.getCanonicalPath();
Marco Nelissenc1fda122014-10-15 14:32:22 -0700312 for (String root: mStorageMap.keySet()) {
313 if (canonical.startsWith(root)) {
314 return true;
315 }
Marco Nelissen5f411692014-09-26 16:03:49 -0700316 }
317 } catch (IOException e) {
318 // ignore
319 }
320 return false;
321 }
322
Mike Lockwoodd815f792010-07-12 08:49:01 -0400323 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400324 int storageId, long size, long modified) {
Marco Nelissen5f411692014-09-26 16:03:49 -0700325 // if the path is outside of the storage root, do not allow access
326 if (!inStorageRoot(path)) {
327 Log.e(TAG, "attempt to put file outside of storage area: " + path);
328 return -1;
329 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500330 // if mSubDirectories is not null, do not allow copying files to any other locations
331 if (!inStorageSubDirectory(path)) return -1;
332
333 // make sure the object does not exist
Mike Lockwoodbafca212010-12-13 21:50:09 -0800334 if (path != null) {
335 Cursor c = null;
336 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800337 c = mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, PATH_WHERE,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800338 new String[] { path }, null, null);
Mike Lockwoodbafca212010-12-13 21:50:09 -0800339 if (c != null && c.getCount() > 0) {
340 Log.w(TAG, "file already exists in beginSendObject: " + path);
341 return -1;
342 }
343 } catch (RemoteException e) {
344 Log.e(TAG, "RemoteException in beginSendObject", e);
345 } finally {
346 if (c != null) {
347 c.close();
348 }
349 }
350 }
351
Mike Lockwood2837eef2010-08-31 16:25:12 -0400352 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400353 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400354 values.put(Files.FileColumns.DATA, path);
355 values.put(Files.FileColumns.FORMAT, format);
356 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400357 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400358 values.put(Files.FileColumns.SIZE, size);
359 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400360
361 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800362 Uri uri = mMediaProvider.insert(mPackageName, mObjectsUri, values);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400363 if (uri != null) {
364 return Integer.parseInt(uri.getPathSegments().get(2));
365 } else {
366 return -1;
367 }
368 } catch (RemoteException e) {
369 Log.e(TAG, "RemoteException in beginSendObject", e);
370 return -1;
371 }
372 }
373
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800374 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400375 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400376 // handle abstract playlists separately
377 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400378 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400379 // extract name from path
380 String name = path;
381 int lastSlash = name.lastIndexOf('/');
382 if (lastSlash >= 0) {
383 name = name.substring(lastSlash + 1);
384 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800385 // strip trailing ".pla" from the name
386 if (name.endsWith(".pla")) {
387 name = name.substring(0, name.length() - 4);
388 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400389
390 ContentValues values = new ContentValues(1);
391 values.put(Audio.Playlists.DATA, path);
392 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500393 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800394 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400395 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
396 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800397 Uri uri = mMediaProvider.insert(mPackageName,
398 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400399 } catch (RemoteException e) {
400 Log.e(TAG, "RemoteException in endSendObject", e);
401 }
402 } else {
Mike Lockwoodc37255d2010-09-10 14:47:36 -0400403 mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400404 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400405 } else {
406 deleteFile(handle);
407 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400408 }
409
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400410 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500411 String where;
412 String[] whereArgs;
413
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400414 if (storageID == 0xFFFFFFFF) {
415 // query all stores
416 if (format == 0) {
417 // query all formats
418 if (parent == 0) {
419 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500420 where = null;
421 whereArgs = null;
422 } else {
423 if (parent == 0xFFFFFFFF) {
424 // all objects in root of store
425 parent = 0;
426 }
427 where = PARENT_WHERE;
428 whereArgs = new String[] { Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400429 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400430 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400431 // query specific format
432 if (parent == 0) {
433 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500434 where = FORMAT_WHERE;
435 whereArgs = new String[] { Integer.toString(format) };
436 } else {
437 if (parent == 0xFFFFFFFF) {
438 // all objects in root of store
439 parent = 0;
440 }
441 where = FORMAT_PARENT_WHERE;
442 whereArgs = new String[] { Integer.toString(format),
443 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400444 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400445 }
446 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400447 // query specific store
448 if (format == 0) {
449 // query all formats
450 if (parent == 0) {
451 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500452 where = STORAGE_WHERE;
453 whereArgs = new String[] { Integer.toString(storageID) };
454 } else {
455 if (parent == 0xFFFFFFFF) {
456 // all objects in root of store
457 parent = 0;
458 }
459 where = STORAGE_PARENT_WHERE;
460 whereArgs = new String[] { Integer.toString(storageID),
461 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400462 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400463 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400464 // query specific format
465 if (parent == 0) {
466 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500467 where = STORAGE_FORMAT_WHERE;
468 whereArgs = new String[] { Integer.toString(storageID),
469 Integer.toString(format) };
470 } else {
471 if (parent == 0xFFFFFFFF) {
472 // all objects in root of store
473 parent = 0;
474 }
475 where = STORAGE_FORMAT_PARENT_WHERE;
476 whereArgs = new String[] { Integer.toString(storageID),
477 Integer.toString(format),
478 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400479 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400480 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400481 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500482
483 // if we are restricting queries to mSubDirectories, we need to add the restriction
484 // onto our "where" arguments
485 if (mSubDirectoriesWhere != null) {
486 if (where == null) {
487 where = mSubDirectoriesWhere;
488 whereArgs = mSubDirectoriesWhereArgs;
489 } else {
490 where = where + " AND " + mSubDirectoriesWhere;
491
492 // create new array to hold whereArgs and mSubDirectoriesWhereArgs
493 String[] newWhereArgs =
494 new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
495 int i, j;
496 for (i = 0; i < whereArgs.length; i++) {
497 newWhereArgs[i] = whereArgs[i];
498 }
499 for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
500 newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
501 }
502 whereArgs = newWhereArgs;
503 }
504 }
505
Dianne Hackborn35654b62013-01-14 17:38:02 -0800506 return mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, where,
507 whereArgs, null, null);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400508 }
509
510 private int[] getObjectList(int storageID, int format, int parent) {
511 Cursor c = null;
512 try {
513 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400514 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400515 return null;
516 }
517 int count = c.getCount();
518 if (count > 0) {
519 int[] result = new int[count];
520 for (int i = 0; i < count; i++) {
521 c.moveToNext();
522 result[i] = c.getInt(0);
523 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400524 return result;
525 }
526 } catch (RemoteException e) {
527 Log.e(TAG, "RemoteException in getObjectList", e);
528 } finally {
529 if (c != null) {
530 c.close();
531 }
532 }
533 return null;
534 }
535
Mike Lockwood7a047c82010-08-02 10:52:20 -0400536 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400537 Cursor c = null;
538 try {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400539 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400540 if (c != null) {
541 return c.getCount();
542 }
543 } catch (RemoteException e) {
544 Log.e(TAG, "RemoteException in getNumObjects", e);
545 } finally {
546 if (c != null) {
547 c.close();
548 }
549 }
550 return -1;
551 }
552
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400553 private int[] getSupportedPlaybackFormats() {
554 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400555 // allow transfering arbitrary files
556 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400557
Mike Lockwood792ec842010-09-09 15:30:10 -0400558 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400559 MtpConstants.FORMAT_TEXT,
560 MtpConstants.FORMAT_HTML,
561 MtpConstants.FORMAT_WAV,
562 MtpConstants.FORMAT_MP3,
563 MtpConstants.FORMAT_MPEG,
564 MtpConstants.FORMAT_EXIF_JPEG,
565 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800566 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400567 MtpConstants.FORMAT_GIF,
568 MtpConstants.FORMAT_JFIF,
569 MtpConstants.FORMAT_PNG,
570 MtpConstants.FORMAT_TIFF,
571 MtpConstants.FORMAT_WMA,
572 MtpConstants.FORMAT_OGG,
573 MtpConstants.FORMAT_AAC,
574 MtpConstants.FORMAT_MP4_CONTAINER,
575 MtpConstants.FORMAT_MP2,
576 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400577 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400578 MtpConstants.FORMAT_WPL_PLAYLIST,
579 MtpConstants.FORMAT_M3U_PLAYLIST,
580 MtpConstants.FORMAT_PLS_PLAYLIST,
581 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800582 MtpConstants.FORMAT_FLAC,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400583 };
584 }
585
586 private int[] getSupportedCaptureFormats() {
587 // no capture formats yet
588 return null;
589 }
590
Mike Lockwoodae078f72010-09-26 12:35:51 -0400591 static final int[] FILE_PROPERTIES = {
592 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
593 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400594 MtpConstants.PROPERTY_STORAGE_ID,
595 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400596 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400597 MtpConstants.PROPERTY_OBJECT_SIZE,
598 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400599 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400600 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400601 MtpConstants.PROPERTY_PERSISTENT_UID,
602 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800603 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400604 MtpConstants.PROPERTY_DATE_ADDED,
605 };
606
607 static final int[] AUDIO_PROPERTIES = {
608 // NOTE must match FILE_PROPERTIES above
609 MtpConstants.PROPERTY_STORAGE_ID,
610 MtpConstants.PROPERTY_OBJECT_FORMAT,
611 MtpConstants.PROPERTY_PROTECTION_STATUS,
612 MtpConstants.PROPERTY_OBJECT_SIZE,
613 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
614 MtpConstants.PROPERTY_DATE_MODIFIED,
615 MtpConstants.PROPERTY_PARENT_OBJECT,
616 MtpConstants.PROPERTY_PERSISTENT_UID,
617 MtpConstants.PROPERTY_NAME,
618 MtpConstants.PROPERTY_DISPLAY_NAME,
619 MtpConstants.PROPERTY_DATE_ADDED,
620
621 // audio specific properties
622 MtpConstants.PROPERTY_ARTIST,
623 MtpConstants.PROPERTY_ALBUM_NAME,
624 MtpConstants.PROPERTY_ALBUM_ARTIST,
625 MtpConstants.PROPERTY_TRACK,
626 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
627 MtpConstants.PROPERTY_DURATION,
628 MtpConstants.PROPERTY_GENRE,
629 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700630 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
631 MtpConstants.PROPERTY_BITRATE_TYPE,
632 MtpConstants.PROPERTY_AUDIO_BITRATE,
633 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
634 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400635 };
636
637 static final int[] VIDEO_PROPERTIES = {
638 // NOTE must match FILE_PROPERTIES above
639 MtpConstants.PROPERTY_STORAGE_ID,
640 MtpConstants.PROPERTY_OBJECT_FORMAT,
641 MtpConstants.PROPERTY_PROTECTION_STATUS,
642 MtpConstants.PROPERTY_OBJECT_SIZE,
643 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
644 MtpConstants.PROPERTY_DATE_MODIFIED,
645 MtpConstants.PROPERTY_PARENT_OBJECT,
646 MtpConstants.PROPERTY_PERSISTENT_UID,
647 MtpConstants.PROPERTY_NAME,
648 MtpConstants.PROPERTY_DISPLAY_NAME,
649 MtpConstants.PROPERTY_DATE_ADDED,
650
651 // video specific properties
652 MtpConstants.PROPERTY_ARTIST,
653 MtpConstants.PROPERTY_ALBUM_NAME,
654 MtpConstants.PROPERTY_DURATION,
655 MtpConstants.PROPERTY_DESCRIPTION,
656 };
657
658 static final int[] IMAGE_PROPERTIES = {
659 // NOTE must match FILE_PROPERTIES above
660 MtpConstants.PROPERTY_STORAGE_ID,
661 MtpConstants.PROPERTY_OBJECT_FORMAT,
662 MtpConstants.PROPERTY_PROTECTION_STATUS,
663 MtpConstants.PROPERTY_OBJECT_SIZE,
664 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
665 MtpConstants.PROPERTY_DATE_MODIFIED,
666 MtpConstants.PROPERTY_PARENT_OBJECT,
667 MtpConstants.PROPERTY_PERSISTENT_UID,
668 MtpConstants.PROPERTY_NAME,
669 MtpConstants.PROPERTY_DISPLAY_NAME,
670 MtpConstants.PROPERTY_DATE_ADDED,
671
672 // image specific properties
673 MtpConstants.PROPERTY_DESCRIPTION,
674 };
675
676 private int[] getSupportedObjectProperties(int format) {
677 switch (format) {
678 case MtpConstants.FORMAT_MP3:
679 case MtpConstants.FORMAT_WAV:
680 case MtpConstants.FORMAT_WMA:
681 case MtpConstants.FORMAT_OGG:
682 case MtpConstants.FORMAT_AAC:
683 return AUDIO_PROPERTIES;
684 case MtpConstants.FORMAT_MPEG:
685 case MtpConstants.FORMAT_3GP_CONTAINER:
686 case MtpConstants.FORMAT_WMV:
687 return VIDEO_PROPERTIES;
688 case MtpConstants.FORMAT_EXIF_JPEG:
689 case MtpConstants.FORMAT_GIF:
690 case MtpConstants.FORMAT_PNG:
691 case MtpConstants.FORMAT_BMP:
692 return IMAGE_PROPERTIES;
693 default:
694 return FILE_PROPERTIES;
695 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400696 }
697
698 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400699 return new int[] {
700 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
701 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800702 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood56c85242014-03-07 13:29:08 -0800703 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400704 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400705 }
706
Mike Lockwoodae078f72010-09-26 12:35:51 -0400707
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500708 private MtpPropertyList getObjectPropertyList(long handle, int format, long property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400709 int groupCode, int depth) {
710 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400711 if (groupCode != 0) {
712 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
713 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400714
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500715 MtpPropertyGroup propertyGroup;
716 if (property == 0xFFFFFFFFL) {
Mike Lockwood71827742015-01-23 10:50:08 -0800717 if (format == 0 && handle > 0) {
718 // return properties based on the object's format
719 format = getObjectFormat((int)handle);
720 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500721 propertyGroup = mPropertyGroupsByFormat.get(format);
722 if (propertyGroup == null) {
723 int[] propertyList = getSupportedObjectProperties(format);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800724 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
725 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500726 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400727 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500728 } else {
729 propertyGroup = mPropertyGroupsByProperty.get(property);
730 if (propertyGroup == null) {
731 int[] propertyList = new int[] { (int)property };
Dianne Hackborn35654b62013-01-14 17:38:02 -0800732 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
733 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500734 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400735 }
736 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500737
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400738 return propertyGroup.getPropertyList((int)handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400739 }
740
Mike Lockwood5ebac832010-10-12 11:33:47 -0400741 private int renameFile(int handle, String newName) {
742 Cursor c = null;
743
744 // first compute current path
745 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400746 String[] whereArgs = new String[] { Integer.toString(handle) };
747 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800748 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_PROJECTION, ID_WHERE,
749 whereArgs, null, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400750 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500751 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400752 }
753 } catch (RemoteException e) {
754 Log.e(TAG, "RemoteException in getObjectFilePath", e);
755 return MtpConstants.RESPONSE_GENERAL_ERROR;
756 } finally {
757 if (c != null) {
758 c.close();
759 }
760 }
761 if (path == null) {
762 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
763 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400764
Mike Lockwood73e56d92011-12-01 16:58:41 -0500765 // do not allow renaming any of the special subdirectories
766 if (isStorageSubDirectory(path)) {
767 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
768 }
769
Mike Lockwood5ebac832010-10-12 11:33:47 -0400770 // now rename the file. make sure this succeeds before updating database
771 File oldFile = new File(path);
772 int lastSlash = path.lastIndexOf('/');
773 if (lastSlash <= 1) {
774 return MtpConstants.RESPONSE_GENERAL_ERROR;
775 }
776 String newPath = path.substring(0, lastSlash + 1) + newName;
777 File newFile = new File(newPath);
778 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400779 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800780 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400781 return MtpConstants.RESPONSE_GENERAL_ERROR;
782 }
783
784 // finally update database
785 ContentValues values = new ContentValues();
786 values.put(Files.FileColumns.DATA, newPath);
787 int updated = 0;
788 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400789 // note - we are relying on a special case in MediaProvider.update() to update
790 // the paths for all children in the case where this is a directory.
Dianne Hackborn35654b62013-01-14 17:38:02 -0800791 updated = mMediaProvider.update(mPackageName, mObjectsUri, values, ID_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400792 } catch (RemoteException e) {
793 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
794 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400795 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400796 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
797 // this shouldn't happen, but if it does we need to rename the file to its original name
798 newFile.renameTo(oldFile);
799 return MtpConstants.RESPONSE_GENERAL_ERROR;
800 }
801
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800802 // check if nomedia status changed
803 if (newFile.isDirectory()) {
804 // for directories, check if renamed from something hidden to something non-hidden
805 if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
806 // directory was unhidden
807 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800808 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, newPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800809 } catch (RemoteException e) {
810 Log.e(TAG, "failed to unhide/rescan for " + newPath);
811 }
812 }
813 } else {
814 // for files, check if renamed from .nomedia to something else
815 if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
816 && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
817 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800818 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800819 } catch (RemoteException e) {
820 Log.e(TAG, "failed to unhide/rescan for " + newPath);
821 }
822 }
823 }
824
Mike Lockwood5ebac832010-10-12 11:33:47 -0400825 return MtpConstants.RESPONSE_OK;
826 }
827
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400828 private int setObjectProperty(int handle, int property,
829 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400830 switch (property) {
831 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
832 return renameFile(handle, stringValue);
833
834 default:
835 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
836 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400837 }
838
839 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400840 switch (property) {
841 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
842 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500843 // writable string properties kept in shared preferences
844 String value = mDeviceProperties.getString(Integer.toString(property), "");
845 int length = value.length();
846 if (length > 255) {
847 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400848 }
Mike Lockwood775de952011-03-05 17:34:11 -0500849 value.getChars(0, length, outStringValue, 0);
850 outStringValue[length] = 0;
851 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400852
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800853 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
854 // use screen size as max image size
855 Display display = ((WindowManager)mContext.getSystemService(
856 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700857 int width = display.getMaximumSizeDimension();
858 int height = display.getMaximumSizeDimension();
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800859 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
860 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
861 outStringValue[imageSize.length()] = 0;
862 return MtpConstants.RESPONSE_OK;
863
Mike Lockwood56c85242014-03-07 13:29:08 -0800864 // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
865
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800866 default:
867 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
868 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400869 }
870
871 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400872 switch (property) {
873 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
874 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500875 // writable string properties kept in shared prefs
876 SharedPreferences.Editor e = mDeviceProperties.edit();
877 e.putString(Integer.toString(property), stringValue);
878 return (e.commit() ? MtpConstants.RESPONSE_OK
879 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400880 }
881
882 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
883 }
884
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400885 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700886 char[] outName, long[] outCreatedModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400887 Cursor c = null;
888 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800889 c = mMediaProvider.query(mPackageName, mObjectsUri, OBJECT_INFO_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800890 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400891 if (c != null && c.moveToNext()) {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400892 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400893 outStorageFormatParent[1] = c.getInt(2);
894 outStorageFormatParent[2] = c.getInt(3);
895
896 // extract name from path
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400897 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400898 int lastSlash = path.lastIndexOf('/');
899 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
900 int end = path.length();
901 if (end - start > 255) {
902 end = start + 255;
903 }
904 path.getChars(start, end, outName, 0);
905 outName[end - start] = 0;
906
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700907 outCreatedModified[0] = c.getLong(5);
908 outCreatedModified[1] = c.getLong(6);
909 // use modification date as creation date if date added is not set
910 if (outCreatedModified[0] == 0) {
911 outCreatedModified[0] = outCreatedModified[1];
912 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400913 return true;
914 }
915 } catch (RemoteException e) {
Mike Lockwood2b5f9ad12010-10-29 19:16:27 -0400916 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400917 } finally {
918 if (c != null) {
919 c.close();
920 }
921 }
922 return false;
923 }
924
Mike Lockwood365e03e2010-12-08 16:08:01 -0800925 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400926 if (handle == 0) {
927 // special case root directory
928 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
929 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800930 outFileLengthFormat[0] = 0;
931 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400932 return MtpConstants.RESPONSE_OK;
933 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400934 Cursor c = null;
935 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800936 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800937 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400938 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500939 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400940 path.getChars(0, path.length(), outFilePath, 0);
941 outFilePath[path.length()] = 0;
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700942 // File transfers from device to host will likely fail if the size is incorrect.
943 // So to be safe, use the actual file size here.
944 outFileLengthFormat[0] = new File(path).length();
945 outFileLengthFormat[1] = c.getLong(2);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400946 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400947 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400948 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400949 }
950 } catch (RemoteException e) {
951 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400952 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400953 } finally {
954 if (c != null) {
955 c.close();
956 }
957 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400958 }
959
Mike Lockwood71827742015-01-23 10:50:08 -0800960 private int getObjectFormat(int handle) {
961 Cursor c = null;
962 try {
963 c = mMediaProvider.query(mPackageName, mObjectsUri, FORMAT_PROJECTION,
964 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
965 if (c != null && c.moveToNext()) {
966 return c.getInt(1);
967 } else {
968 return -1;
969 }
970 } catch (RemoteException e) {
971 Log.e(TAG, "RemoteException in getObjectFilePath", e);
972 return -1;
973 } finally {
974 if (c != null) {
975 c.close();
976 }
977 }
978 }
979
Mike Lockwood59c777a2010-08-02 10:37:41 -0400980 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400981 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800982 String path = null;
983 int format = 0;
984
985 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400986 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800987 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800988 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800989 if (c != null && c.moveToNext()) {
990 // don't convert to media path here, since we will be matching
991 // against paths in the database matching /data/media
992 path = c.getString(1);
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700993 format = c.getInt(2);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800994 } else {
995 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
996 }
997
998 if (path == null || format == 0) {
999 return MtpConstants.RESPONSE_GENERAL_ERROR;
1000 }
1001
Mike Lockwood73e56d92011-12-01 16:58:41 -05001002 // do not allow deleting any of the special subdirectories
1003 if (isStorageSubDirectory(path)) {
1004 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
1005 }
1006
Mike Lockwood55f808c2010-12-14 13:14:29 -08001007 if (format == MtpConstants.FORMAT_ASSOCIATION) {
1008 // recursive case - delete all children first
1009 Uri uri = Files.getMtpObjectsUri(mVolumeName);
Dianne Hackborn35654b62013-01-14 17:38:02 -08001010 int count = mMediaProvider.delete(mPackageName, uri,
Mike Lockwood1e855d92012-06-26 16:31:41 -07001011 // the 'like' makes it use the index, the 'lower()' makes it correct
1012 // when the path contains sqlite wildcard characters
1013 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
1014 new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -08001015 }
1016
1017 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
Dianne Hackborn35654b62013-01-14 17:38:02 -08001018 if (mMediaProvider.delete(mPackageName, uri, null, null) > 0) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001019 if (format != MtpConstants.FORMAT_ASSOCIATION
1020 && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
1021 try {
1022 String parentPath = path.substring(0, path.lastIndexOf("/"));
Dianne Hackborn35654b62013-01-14 17:38:02 -08001023 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001024 } catch (RemoteException e) {
1025 Log.e(TAG, "failed to unhide/rescan for " + path);
1026 }
1027 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001028 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001029 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001030 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001031 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001032 } catch (RemoteException e) {
1033 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -04001034 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -08001035 } finally {
1036 if (c != null) {
1037 c.close();
1038 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001039 }
1040 }
1041
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001042 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -04001043 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001044 Cursor c = null;
1045 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -08001046 c = mMediaProvider.query(mPackageName, uri, ID_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001047 if (c == null) {
1048 return null;
1049 }
1050 int count = c.getCount();
1051 if (count > 0) {
1052 int[] result = new int[count];
1053 for (int i = 0; i < count; i++) {
1054 c.moveToNext();
1055 result[i] = c.getInt(0);
1056 }
1057 return result;
1058 }
1059 } catch (RemoteException e) {
1060 Log.e(TAG, "RemoteException in getObjectList", e);
1061 } finally {
1062 if (c != null) {
1063 c.close();
1064 }
1065 }
1066 return null;
1067 }
1068
1069 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001070 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -04001071 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001072 int count = references.length;
1073 ContentValues[] valuesList = new ContentValues[count];
1074 for (int i = 0; i < count; i++) {
1075 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -04001076 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001077 valuesList[i] = values;
1078 }
1079 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -08001080 if (mMediaProvider.bulkInsert(mPackageName, uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001081 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001082 }
1083 } catch (RemoteException e) {
1084 Log.e(TAG, "RemoteException in setObjectReferences", e);
1085 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001086 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001087 }
1088
Mike Lockwood2837eef2010-08-31 16:25:12 -04001089 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001090 mDatabaseModified = false;
1091 }
1092
1093 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001094 if (mDatabaseModified) {
Mike Lockwooda3156052010-11-20 12:28:27 -05001095 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -04001096 mDatabaseModified = false;
1097 }
1098 }
1099
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001100 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +00001101 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001102
1103 private native final void native_setup();
1104 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001105}