blob: 760a2d13eecbf62fd3129861327779ae2ad1bc15 [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;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070020import android.content.ContentProviderClient;
Mike Lockwoodd815f792010-07-12 08:49:01 -040021import android.content.ContentValues;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070022import android.content.Context;
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
Jeff Sharkey60cfad82016-01-05 17:30:57 -070040import dalvik.system.CloseGuard;
41
Mike Lockwood5ebac832010-10-12 11:33:47 -040042import java.io.File;
Marco Nelissen5f411692014-09-26 16:03:49 -070043import java.io.IOException;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050044import java.util.HashMap;
dujin.chafe464a72011-11-22 12:13:33 +090045import java.util.Locale;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070046import java.util.concurrent.atomic.AtomicBoolean;
Mike Lockwood5ebac832010-10-12 11:33:47 -040047
Mike Lockwoodd21eac92010-07-03 00:44:05 -040048/**
49 * {@hide}
50 */
Jeff Sharkey60cfad82016-01-05 17:30:57 -070051public class MtpDatabase implements AutoCloseable {
Mike Lockwoodd21eac92010-07-03 00:44:05 -040052 private static final String TAG = "MtpDatabase";
53
Mike Lockwood2837eef2010-08-31 16:25:12 -040054 private final Context mContext;
Dianne Hackborn35654b62013-01-14 17:38:02 -080055 private final String mPackageName;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070056 private final ContentProviderClient mMediaProvider;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040057 private final String mVolumeName;
58 private final Uri mObjectsUri;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070059 private final MediaScanner mMediaScanner;
60
61 private final AtomicBoolean mClosed = new AtomicBoolean();
62 private final CloseGuard mCloseGuard = CloseGuard.get();
63
Mike Lockwood73e56d92011-12-01 16:58:41 -050064 // path to primary storage
65 private final String mMediaStoragePath;
66 // if not null, restrict all queries to these subdirectories
67 private final String[] mSubDirectories;
68 // where clause for restricting queries to files in mSubDirectories
69 private String mSubDirectoriesWhere;
70 // where arguments for restricting queries to files in mSubDirectories
71 private String[] mSubDirectoriesWhereArgs;
72
Mike Lockwoodb239b6832011-04-05 10:21:27 -040073 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040074
Mike Lockwood7d7fb632010-12-01 18:46:23 -050075 // cached property groups for single properties
76 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
77 = new HashMap<Integer, MtpPropertyGroup>();
78
79 // cached property groups for all properties for a given format
80 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
81 = new HashMap<Integer, MtpPropertyGroup>();
82
Mike Lockwood2837eef2010-08-31 16:25:12 -040083 // true if the database has been modified in the current MTP session
84 private boolean mDatabaseModified;
85
Mike Lockwood775de952011-03-05 17:34:11 -050086 // SharedPreferences for writable MTP device properties
87 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040088 private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
89
Mike Lockwoodd21eac92010-07-03 00:44:05 -040090 private static final String[] ID_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040091 Files.FileColumns._ID, // 0
Mike Lockwoodd21eac92010-07-03 00:44:05 -040092 };
Mike Lockwood6a6a3af2010-10-12 14:19:51 -040093 private static final String[] PATH_PROJECTION = new String[] {
Mike Lockwood5ebac832010-10-12 11:33:47 -040094 Files.FileColumns._ID, // 0
95 Files.FileColumns.DATA, // 1
Mike Lockwood5ebac832010-10-12 11:33:47 -040096 };
Mike Lockwood71827742015-01-23 10:50:08 -080097 private static final String[] FORMAT_PROJECTION = new String[] {
98 Files.FileColumns._ID, // 0
99 Files.FileColumns.FORMAT, // 1
100 };
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700101 private static final String[] PATH_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400102 Files.FileColumns._ID, // 0
103 Files.FileColumns.DATA, // 1
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700104 Files.FileColumns.FORMAT, // 2
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400105 };
106 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400107 Files.FileColumns._ID, // 0
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400108 Files.FileColumns.STORAGE_ID, // 1
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400109 Files.FileColumns.FORMAT, // 2
110 Files.FileColumns.PARENT, // 3
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400111 Files.FileColumns.DATA, // 4
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700112 Files.FileColumns.DATE_ADDED, // 5
113 Files.FileColumns.DATE_MODIFIED, // 6
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400114 };
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400115 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwoodbafca212010-12-13 21:50:09 -0800116 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400117
118 private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
Mike Lockwood58e68312012-09-11 10:49:34 -0700119 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
120 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400121 private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400122 + Files.FileColumns.FORMAT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400123 private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
124 + Files.FileColumns.PARENT + "=?";
125 private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
126 + Files.FileColumns.PARENT + "=?";
127 private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
128 + Files.FileColumns.PARENT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400129
Mike Lockwood56c85242014-03-07 13:29:08 -0800130 private MtpServer mServer;
131
132 // read from native code
133 private int mBatteryLevel;
134 private int mBatteryScale;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400135
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400136 static {
137 System.loadLibrary("media_jni");
138 }
139
Mike Lockwood56c85242014-03-07 13:29:08 -0800140 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
141 @Override
142 public void onReceive(Context context, Intent intent) {
143 String action = intent.getAction();
144 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
145 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
146 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
147 if (newLevel != mBatteryLevel) {
148 mBatteryLevel = newLevel;
149 if (mServer != null) {
150 // send device property changed event
151 mServer.sendDevicePropertyChanged(
152 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
153 }
154 }
155 }
156 }
157 };
158
Mike Lockwood73e56d92011-12-01 16:58:41 -0500159 public MtpDatabase(Context context, String volumeName, String storagePath,
160 String[] subDirectories) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400161 native_setup();
162
Mike Lockwood2837eef2010-08-31 16:25:12 -0400163 mContext = context;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800164 mPackageName = context.getPackageName();
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700165 mMediaProvider = context.getContentResolver()
166 .acquireContentProviderClient(MediaStore.AUTHORITY);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400167 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400168 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400169 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700170 mMediaScanner = new MediaScanner(context, mVolumeName);
dujin.chafe464a72011-11-22 12:13:33 +0900171
Mike Lockwood73e56d92011-12-01 16:58:41 -0500172 mSubDirectories = subDirectories;
173 if (subDirectories != null) {
174 // Compute "where" string for restricting queries to subdirectories
175 StringBuilder builder = new StringBuilder();
176 builder.append("(");
177 int count = subDirectories.length;
178 for (int i = 0; i < count; i++) {
179 builder.append(Files.FileColumns.DATA + "=? OR "
180 + Files.FileColumns.DATA + " LIKE ?");
181 if (i != count - 1) {
182 builder.append(" OR ");
183 }
184 }
185 builder.append(")");
186 mSubDirectoriesWhere = builder.toString();
187
188 // Compute "where" arguments for restricting queries to subdirectories
189 mSubDirectoriesWhereArgs = new String[count * 2];
190 for (int i = 0, j = 0; i < count; i++) {
191 String path = subDirectories[i];
192 mSubDirectoriesWhereArgs[j++] = path;
193 mSubDirectoriesWhereArgs[j++] = path + "/%";
194 }
195 }
196
Mike Lockwood775de952011-03-05 17:34:11 -0500197 initDeviceProperties(context);
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700198
199 mCloseGuard.open("close");
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400200 }
201
Mike Lockwood56c85242014-03-07 13:29:08 -0800202 public void setServer(MtpServer server) {
203 mServer = server;
204
Marco Nelissen1632fae2014-03-27 13:25:14 -0700205 // always unregister before registering
206 try {
207 mContext.unregisterReceiver(mBatteryReceiver);
208 } catch (IllegalArgumentException e) {
209 // wasn't previously registered, ignore
210 }
211
Mike Lockwood56c85242014-03-07 13:29:08 -0800212 // register for battery notifications when we are connected
213 if (server != null) {
214 mContext.registerReceiver(mBatteryReceiver,
215 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Mike Lockwood56c85242014-03-07 13:29:08 -0800216 }
217 }
218
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400219 @Override
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700220 public void close() {
221 mCloseGuard.close();
222 if (mClosed.compareAndSet(false, true)) {
223 mMediaScanner.close();
224 mMediaProvider.close();
225 native_finalize();
226 }
227 }
228
229 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400230 protected void finalize() throws Throwable {
231 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700232 mCloseGuard.warnIfOpen();
233 close();
Mike Lockwooddbead322010-08-30 09:27:55 -0400234 } finally {
235 super.finalize();
236 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400237 }
238
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400239 public void addStorage(MtpStorage storage) {
240 mStorageMap.put(storage.getPath(), storage);
241 }
242
243 public void removeStorage(MtpStorage storage) {
244 mStorageMap.remove(storage.getPath());
245 }
246
Mike Lockwood775de952011-03-05 17:34:11 -0500247 private void initDeviceProperties(Context context) {
248 final String devicePropertiesName = "device-properties";
249 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
250 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400251
Mike Lockwood775de952011-03-05 17:34:11 -0500252 if (databaseFile.exists()) {
253 // for backward compatibility - read device properties from sqlite database
254 // and migrate them to shared prefs
255 SQLiteDatabase db = null;
256 Cursor c = null;
257 try {
258 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
259 if (db != null) {
260 c = db.query("properties", new String[] { "_id", "code", "value" },
261 null, null, null, null, null);
262 if (c != null) {
263 SharedPreferences.Editor e = mDeviceProperties.edit();
264 while (c.moveToNext()) {
265 String name = c.getString(1);
266 String value = c.getString(2);
267 e.putString(name, value);
268 }
269 e.commit();
270 }
271 }
272 } catch (Exception e) {
273 Log.e(TAG, "failed to migrate device properties", e);
274 } finally {
275 if (c != null) c.close();
276 if (db != null) db.close();
277 }
jangwon.lee3ed02532013-07-22 19:52:48 +0900278 context.deleteDatabase(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400279 }
280 }
281
Mike Lockwood73e56d92011-12-01 16:58:41 -0500282 // check to see if the path is contained in one of our storage subdirectories
283 // returns true if we have no special subdirectories
284 private boolean inStorageSubDirectory(String path) {
285 if (mSubDirectories == null) return true;
286 if (path == null) return false;
287
288 boolean allowed = false;
289 int pathLength = path.length();
290 for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
291 String subdir = mSubDirectories[i];
292 int subdirLength = subdir.length();
293 if (subdirLength < pathLength &&
294 path.charAt(subdirLength) == '/' &&
295 path.startsWith(subdir)) {
296 allowed = true;
297 }
298 }
299 return allowed;
300 }
301
302 // check to see if the path matches one of our storage subdirectories
303 // returns true if we have no special subdirectories
304 private boolean isStorageSubDirectory(String path) {
305 if (mSubDirectories == null) return false;
306 for (int i = 0; i < mSubDirectories.length; i++) {
307 if (path.equals(mSubDirectories[i])) {
308 return true;
309 }
310 }
311 return false;
312 }
313
Marco Nelissen5f411692014-09-26 16:03:49 -0700314 // returns true if the path is in the storage root
315 private boolean inStorageRoot(String path) {
316 try {
317 File f = new File(path);
318 String canonical = f.getCanonicalPath();
Marco Nelissenc1fda122014-10-15 14:32:22 -0700319 for (String root: mStorageMap.keySet()) {
320 if (canonical.startsWith(root)) {
321 return true;
322 }
Marco Nelissen5f411692014-09-26 16:03:49 -0700323 }
324 } catch (IOException e) {
325 // ignore
326 }
327 return false;
328 }
329
Mike Lockwoodd815f792010-07-12 08:49:01 -0400330 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400331 int storageId, long size, long modified) {
Marco Nelissen5f411692014-09-26 16:03:49 -0700332 // if the path is outside of the storage root, do not allow access
333 if (!inStorageRoot(path)) {
334 Log.e(TAG, "attempt to put file outside of storage area: " + path);
335 return -1;
336 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500337 // if mSubDirectories is not null, do not allow copying files to any other locations
338 if (!inStorageSubDirectory(path)) return -1;
339
340 // make sure the object does not exist
Mike Lockwoodbafca212010-12-13 21:50:09 -0800341 if (path != null) {
342 Cursor c = null;
343 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700344 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800345 new String[] { path }, null, null);
Mike Lockwoodbafca212010-12-13 21:50:09 -0800346 if (c != null && c.getCount() > 0) {
347 Log.w(TAG, "file already exists in beginSendObject: " + path);
348 return -1;
349 }
350 } catch (RemoteException e) {
351 Log.e(TAG, "RemoteException in beginSendObject", e);
352 } finally {
353 if (c != null) {
354 c.close();
355 }
356 }
357 }
358
Mike Lockwood2837eef2010-08-31 16:25:12 -0400359 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400360 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400361 values.put(Files.FileColumns.DATA, path);
362 values.put(Files.FileColumns.FORMAT, format);
363 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400364 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400365 values.put(Files.FileColumns.SIZE, size);
366 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400367
368 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700369 Uri uri = mMediaProvider.insert(mObjectsUri, values);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400370 if (uri != null) {
371 return Integer.parseInt(uri.getPathSegments().get(2));
372 } else {
373 return -1;
374 }
375 } catch (RemoteException e) {
376 Log.e(TAG, "RemoteException in beginSendObject", e);
377 return -1;
378 }
379 }
380
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800381 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400382 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400383 // handle abstract playlists separately
384 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400385 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400386 // extract name from path
387 String name = path;
388 int lastSlash = name.lastIndexOf('/');
389 if (lastSlash >= 0) {
390 name = name.substring(lastSlash + 1);
391 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800392 // strip trailing ".pla" from the name
393 if (name.endsWith(".pla")) {
394 name = name.substring(0, name.length() - 4);
395 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400396
397 ContentValues values = new ContentValues(1);
398 values.put(Audio.Playlists.DATA, path);
399 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500400 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800401 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400402 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
403 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700404 Uri uri = mMediaProvider.insert(
Dianne Hackborn35654b62013-01-14 17:38:02 -0800405 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400406 } catch (RemoteException e) {
407 Log.e(TAG, "RemoteException in endSendObject", e);
408 }
409 } else {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700410 mMediaScanner.scanMtpFile(path, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400411 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400412 } else {
413 deleteFile(handle);
414 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400415 }
416
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400417 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500418 String where;
419 String[] whereArgs;
420
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400421 if (storageID == 0xFFFFFFFF) {
422 // query all stores
423 if (format == 0) {
424 // query all formats
425 if (parent == 0) {
426 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500427 where = null;
428 whereArgs = null;
429 } else {
430 if (parent == 0xFFFFFFFF) {
431 // all objects in root of store
432 parent = 0;
433 }
434 where = PARENT_WHERE;
435 whereArgs = new String[] { Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400436 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400437 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400438 // query specific format
439 if (parent == 0) {
440 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500441 where = FORMAT_WHERE;
442 whereArgs = new String[] { Integer.toString(format) };
443 } else {
444 if (parent == 0xFFFFFFFF) {
445 // all objects in root of store
446 parent = 0;
447 }
448 where = FORMAT_PARENT_WHERE;
449 whereArgs = new String[] { Integer.toString(format),
450 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400451 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400452 }
453 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400454 // query specific store
455 if (format == 0) {
456 // query all formats
457 if (parent == 0) {
458 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500459 where = STORAGE_WHERE;
460 whereArgs = new String[] { Integer.toString(storageID) };
461 } else {
462 if (parent == 0xFFFFFFFF) {
463 // all objects in root of store
464 parent = 0;
465 }
466 where = STORAGE_PARENT_WHERE;
467 whereArgs = new String[] { Integer.toString(storageID),
468 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400469 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400470 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400471 // query specific format
472 if (parent == 0) {
473 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500474 where = STORAGE_FORMAT_WHERE;
475 whereArgs = new String[] { Integer.toString(storageID),
476 Integer.toString(format) };
477 } else {
478 if (parent == 0xFFFFFFFF) {
479 // all objects in root of store
480 parent = 0;
481 }
482 where = STORAGE_FORMAT_PARENT_WHERE;
483 whereArgs = new String[] { Integer.toString(storageID),
484 Integer.toString(format),
485 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400486 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400487 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400488 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500489
490 // if we are restricting queries to mSubDirectories, we need to add the restriction
491 // onto our "where" arguments
492 if (mSubDirectoriesWhere != null) {
493 if (where == null) {
494 where = mSubDirectoriesWhere;
495 whereArgs = mSubDirectoriesWhereArgs;
496 } else {
497 where = where + " AND " + mSubDirectoriesWhere;
498
499 // create new array to hold whereArgs and mSubDirectoriesWhereArgs
500 String[] newWhereArgs =
501 new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
502 int i, j;
503 for (i = 0; i < whereArgs.length; i++) {
504 newWhereArgs[i] = whereArgs[i];
505 }
506 for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
507 newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
508 }
509 whereArgs = newWhereArgs;
510 }
511 }
512
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700513 return mMediaProvider.query(mObjectsUri, ID_PROJECTION, where,
Dianne Hackborn35654b62013-01-14 17:38:02 -0800514 whereArgs, null, null);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400515 }
516
517 private int[] getObjectList(int storageID, int format, int parent) {
518 Cursor c = null;
519 try {
520 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400521 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400522 return null;
523 }
524 int count = c.getCount();
525 if (count > 0) {
526 int[] result = new int[count];
527 for (int i = 0; i < count; i++) {
528 c.moveToNext();
529 result[i] = c.getInt(0);
530 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400531 return result;
532 }
533 } catch (RemoteException e) {
534 Log.e(TAG, "RemoteException in getObjectList", e);
535 } finally {
536 if (c != null) {
537 c.close();
538 }
539 }
540 return null;
541 }
542
Mike Lockwood7a047c82010-08-02 10:52:20 -0400543 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400544 Cursor c = null;
545 try {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400546 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400547 if (c != null) {
548 return c.getCount();
549 }
550 } catch (RemoteException e) {
551 Log.e(TAG, "RemoteException in getNumObjects", e);
552 } finally {
553 if (c != null) {
554 c.close();
555 }
556 }
557 return -1;
558 }
559
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400560 private int[] getSupportedPlaybackFormats() {
561 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400562 // allow transfering arbitrary files
563 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400564
Mike Lockwood792ec842010-09-09 15:30:10 -0400565 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400566 MtpConstants.FORMAT_TEXT,
567 MtpConstants.FORMAT_HTML,
568 MtpConstants.FORMAT_WAV,
569 MtpConstants.FORMAT_MP3,
570 MtpConstants.FORMAT_MPEG,
571 MtpConstants.FORMAT_EXIF_JPEG,
572 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800573 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400574 MtpConstants.FORMAT_GIF,
575 MtpConstants.FORMAT_JFIF,
576 MtpConstants.FORMAT_PNG,
577 MtpConstants.FORMAT_TIFF,
578 MtpConstants.FORMAT_WMA,
579 MtpConstants.FORMAT_OGG,
580 MtpConstants.FORMAT_AAC,
581 MtpConstants.FORMAT_MP4_CONTAINER,
582 MtpConstants.FORMAT_MP2,
583 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400584 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400585 MtpConstants.FORMAT_WPL_PLAYLIST,
586 MtpConstants.FORMAT_M3U_PLAYLIST,
587 MtpConstants.FORMAT_PLS_PLAYLIST,
588 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800589 MtpConstants.FORMAT_FLAC,
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100590 MtpConstants.FORMAT_DNG,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400591 };
592 }
593
594 private int[] getSupportedCaptureFormats() {
595 // no capture formats yet
596 return null;
597 }
598
Mike Lockwoodae078f72010-09-26 12:35:51 -0400599 static final int[] FILE_PROPERTIES = {
600 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
601 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400602 MtpConstants.PROPERTY_STORAGE_ID,
603 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400604 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400605 MtpConstants.PROPERTY_OBJECT_SIZE,
606 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400607 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400608 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400609 MtpConstants.PROPERTY_PERSISTENT_UID,
610 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800611 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400612 MtpConstants.PROPERTY_DATE_ADDED,
613 };
614
615 static final int[] AUDIO_PROPERTIES = {
616 // NOTE must match FILE_PROPERTIES above
617 MtpConstants.PROPERTY_STORAGE_ID,
618 MtpConstants.PROPERTY_OBJECT_FORMAT,
619 MtpConstants.PROPERTY_PROTECTION_STATUS,
620 MtpConstants.PROPERTY_OBJECT_SIZE,
621 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
622 MtpConstants.PROPERTY_DATE_MODIFIED,
623 MtpConstants.PROPERTY_PARENT_OBJECT,
624 MtpConstants.PROPERTY_PERSISTENT_UID,
625 MtpConstants.PROPERTY_NAME,
626 MtpConstants.PROPERTY_DISPLAY_NAME,
627 MtpConstants.PROPERTY_DATE_ADDED,
628
629 // audio specific properties
630 MtpConstants.PROPERTY_ARTIST,
631 MtpConstants.PROPERTY_ALBUM_NAME,
632 MtpConstants.PROPERTY_ALBUM_ARTIST,
633 MtpConstants.PROPERTY_TRACK,
634 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
635 MtpConstants.PROPERTY_DURATION,
636 MtpConstants.PROPERTY_GENRE,
637 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700638 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
639 MtpConstants.PROPERTY_BITRATE_TYPE,
640 MtpConstants.PROPERTY_AUDIO_BITRATE,
641 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
642 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400643 };
644
645 static final int[] VIDEO_PROPERTIES = {
646 // NOTE must match FILE_PROPERTIES above
647 MtpConstants.PROPERTY_STORAGE_ID,
648 MtpConstants.PROPERTY_OBJECT_FORMAT,
649 MtpConstants.PROPERTY_PROTECTION_STATUS,
650 MtpConstants.PROPERTY_OBJECT_SIZE,
651 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
652 MtpConstants.PROPERTY_DATE_MODIFIED,
653 MtpConstants.PROPERTY_PARENT_OBJECT,
654 MtpConstants.PROPERTY_PERSISTENT_UID,
655 MtpConstants.PROPERTY_NAME,
656 MtpConstants.PROPERTY_DISPLAY_NAME,
657 MtpConstants.PROPERTY_DATE_ADDED,
658
659 // video specific properties
660 MtpConstants.PROPERTY_ARTIST,
661 MtpConstants.PROPERTY_ALBUM_NAME,
662 MtpConstants.PROPERTY_DURATION,
663 MtpConstants.PROPERTY_DESCRIPTION,
664 };
665
666 static final int[] IMAGE_PROPERTIES = {
667 // NOTE must match FILE_PROPERTIES above
668 MtpConstants.PROPERTY_STORAGE_ID,
669 MtpConstants.PROPERTY_OBJECT_FORMAT,
670 MtpConstants.PROPERTY_PROTECTION_STATUS,
671 MtpConstants.PROPERTY_OBJECT_SIZE,
672 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
673 MtpConstants.PROPERTY_DATE_MODIFIED,
674 MtpConstants.PROPERTY_PARENT_OBJECT,
675 MtpConstants.PROPERTY_PERSISTENT_UID,
676 MtpConstants.PROPERTY_NAME,
677 MtpConstants.PROPERTY_DISPLAY_NAME,
678 MtpConstants.PROPERTY_DATE_ADDED,
679
680 // image specific properties
681 MtpConstants.PROPERTY_DESCRIPTION,
682 };
683
684 private int[] getSupportedObjectProperties(int format) {
685 switch (format) {
686 case MtpConstants.FORMAT_MP3:
687 case MtpConstants.FORMAT_WAV:
688 case MtpConstants.FORMAT_WMA:
689 case MtpConstants.FORMAT_OGG:
690 case MtpConstants.FORMAT_AAC:
691 return AUDIO_PROPERTIES;
692 case MtpConstants.FORMAT_MPEG:
693 case MtpConstants.FORMAT_3GP_CONTAINER:
694 case MtpConstants.FORMAT_WMV:
695 return VIDEO_PROPERTIES;
696 case MtpConstants.FORMAT_EXIF_JPEG:
697 case MtpConstants.FORMAT_GIF:
698 case MtpConstants.FORMAT_PNG:
699 case MtpConstants.FORMAT_BMP:
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100700 case MtpConstants.FORMAT_DNG:
Mike Lockwoodae078f72010-09-26 12:35:51 -0400701 return IMAGE_PROPERTIES;
702 default:
703 return FILE_PROPERTIES;
704 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400705 }
706
707 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400708 return new int[] {
709 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
710 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800711 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood56c85242014-03-07 13:29:08 -0800712 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400713 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400714 }
715
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900716 private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400717 int groupCode, int depth) {
718 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400719 if (groupCode != 0) {
720 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
721 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400722
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500723 MtpPropertyGroup propertyGroup;
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900724 if (property == 0xffffffff) {
725 if (format == 0 && handle != 0 && handle != 0xffffffff) {
Mike Lockwood71827742015-01-23 10:50:08 -0800726 // return properties based on the object's format
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900727 format = getObjectFormat(handle);
Mike Lockwood71827742015-01-23 10:50:08 -0800728 }
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900729 propertyGroup = mPropertyGroupsByFormat.get(format);
730 if (propertyGroup == null) {
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500731 int[] propertyList = getSupportedObjectProperties(format);
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700732 propertyGroup = new MtpPropertyGroup(this, mMediaProvider,
Dianne Hackborn35654b62013-01-14 17:38:02 -0800733 mVolumeName, propertyList);
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900734 mPropertyGroupsByFormat.put(format, propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400735 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500736 } else {
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900737 propertyGroup = mPropertyGroupsByProperty.get(property);
738 if (propertyGroup == null) {
739 final int[] propertyList = new int[] { property };
740 propertyGroup = new MtpPropertyGroup(
741 this, mMediaProvider, mVolumeName, propertyList);
742 mPropertyGroupsByProperty.put(property, propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400743 }
744 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500745
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900746 return propertyGroup.getPropertyList(handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400747 }
748
Mike Lockwood5ebac832010-10-12 11:33:47 -0400749 private int renameFile(int handle, String newName) {
750 Cursor c = null;
751
752 // first compute current path
753 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400754 String[] whereArgs = new String[] { Integer.toString(handle) };
755 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700756 c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE,
Dianne Hackborn35654b62013-01-14 17:38:02 -0800757 whereArgs, null, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400758 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500759 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400760 }
761 } catch (RemoteException e) {
762 Log.e(TAG, "RemoteException in getObjectFilePath", e);
763 return MtpConstants.RESPONSE_GENERAL_ERROR;
764 } finally {
765 if (c != null) {
766 c.close();
767 }
768 }
769 if (path == null) {
770 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
771 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400772
Mike Lockwood73e56d92011-12-01 16:58:41 -0500773 // do not allow renaming any of the special subdirectories
774 if (isStorageSubDirectory(path)) {
775 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
776 }
777
Mike Lockwood5ebac832010-10-12 11:33:47 -0400778 // now rename the file. make sure this succeeds before updating database
779 File oldFile = new File(path);
780 int lastSlash = path.lastIndexOf('/');
781 if (lastSlash <= 1) {
782 return MtpConstants.RESPONSE_GENERAL_ERROR;
783 }
784 String newPath = path.substring(0, lastSlash + 1) + newName;
785 File newFile = new File(newPath);
786 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400787 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800788 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400789 return MtpConstants.RESPONSE_GENERAL_ERROR;
790 }
791
792 // finally update database
793 ContentValues values = new ContentValues();
794 values.put(Files.FileColumns.DATA, newPath);
795 int updated = 0;
796 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400797 // note - we are relying on a special case in MediaProvider.update() to update
798 // the paths for all children in the case where this is a directory.
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700799 updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400800 } catch (RemoteException e) {
801 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
802 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400803 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400804 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
805 // this shouldn't happen, but if it does we need to rename the file to its original name
806 newFile.renameTo(oldFile);
807 return MtpConstants.RESPONSE_GENERAL_ERROR;
808 }
809
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800810 // check if nomedia status changed
811 if (newFile.isDirectory()) {
812 // for directories, check if renamed from something hidden to something non-hidden
813 if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
814 // directory was unhidden
815 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700816 mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800817 } catch (RemoteException e) {
818 Log.e(TAG, "failed to unhide/rescan for " + newPath);
819 }
820 }
821 } else {
822 // for files, check if renamed from .nomedia to something else
823 if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
824 && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
825 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700826 mMediaProvider.call(MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800827 } catch (RemoteException e) {
828 Log.e(TAG, "failed to unhide/rescan for " + newPath);
829 }
830 }
831 }
832
Mike Lockwood5ebac832010-10-12 11:33:47 -0400833 return MtpConstants.RESPONSE_OK;
834 }
835
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400836 private int setObjectProperty(int handle, int property,
837 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400838 switch (property) {
839 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
840 return renameFile(handle, stringValue);
841
842 default:
843 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
844 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400845 }
846
847 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400848 switch (property) {
849 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
850 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500851 // writable string properties kept in shared preferences
852 String value = mDeviceProperties.getString(Integer.toString(property), "");
853 int length = value.length();
854 if (length > 255) {
855 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400856 }
Mike Lockwood775de952011-03-05 17:34:11 -0500857 value.getChars(0, length, outStringValue, 0);
858 outStringValue[length] = 0;
859 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400860
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800861 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
862 // use screen size as max image size
863 Display display = ((WindowManager)mContext.getSystemService(
864 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700865 int width = display.getMaximumSizeDimension();
866 int height = display.getMaximumSizeDimension();
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800867 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
868 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
869 outStringValue[imageSize.length()] = 0;
870 return MtpConstants.RESPONSE_OK;
871
Mike Lockwood56c85242014-03-07 13:29:08 -0800872 // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
873
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800874 default:
875 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
876 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400877 }
878
879 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400880 switch (property) {
881 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
882 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500883 // writable string properties kept in shared prefs
884 SharedPreferences.Editor e = mDeviceProperties.edit();
885 e.putString(Integer.toString(property), stringValue);
886 return (e.commit() ? MtpConstants.RESPONSE_OK
887 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400888 }
889
890 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
891 }
892
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400893 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700894 char[] outName, long[] outCreatedModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400895 Cursor c = null;
896 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700897 c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800898 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400899 if (c != null && c.moveToNext()) {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400900 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400901 outStorageFormatParent[1] = c.getInt(2);
902 outStorageFormatParent[2] = c.getInt(3);
903
904 // extract name from path
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400905 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400906 int lastSlash = path.lastIndexOf('/');
907 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
908 int end = path.length();
909 if (end - start > 255) {
910 end = start + 255;
911 }
912 path.getChars(start, end, outName, 0);
913 outName[end - start] = 0;
914
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700915 outCreatedModified[0] = c.getLong(5);
916 outCreatedModified[1] = c.getLong(6);
917 // use modification date as creation date if date added is not set
918 if (outCreatedModified[0] == 0) {
919 outCreatedModified[0] = outCreatedModified[1];
920 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400921 return true;
922 }
923 } catch (RemoteException e) {
Mike Lockwood2b5f9ad2010-10-29 19:16:27 -0400924 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400925 } finally {
926 if (c != null) {
927 c.close();
928 }
929 }
930 return false;
931 }
932
Mike Lockwood365e03e2010-12-08 16:08:01 -0800933 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400934 if (handle == 0) {
935 // special case root directory
936 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
937 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800938 outFileLengthFormat[0] = 0;
939 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400940 return MtpConstants.RESPONSE_OK;
941 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400942 Cursor c = null;
943 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700944 c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800945 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400946 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500947 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400948 path.getChars(0, path.length(), outFilePath, 0);
949 outFilePath[path.length()] = 0;
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700950 // File transfers from device to host will likely fail if the size is incorrect.
951 // So to be safe, use the actual file size here.
952 outFileLengthFormat[0] = new File(path).length();
953 outFileLengthFormat[1] = c.getLong(2);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400954 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400955 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400956 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400957 }
958 } catch (RemoteException e) {
959 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400960 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400961 } finally {
962 if (c != null) {
963 c.close();
964 }
965 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400966 }
967
Mike Lockwood71827742015-01-23 10:50:08 -0800968 private int getObjectFormat(int handle) {
969 Cursor c = null;
970 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700971 c = mMediaProvider.query(mObjectsUri, FORMAT_PROJECTION,
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900972 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood71827742015-01-23 10:50:08 -0800973 if (c != null && c.moveToNext()) {
974 return c.getInt(1);
975 } else {
976 return -1;
977 }
978 } catch (RemoteException e) {
979 Log.e(TAG, "RemoteException in getObjectFilePath", e);
980 return -1;
981 } finally {
982 if (c != null) {
983 c.close();
984 }
985 }
986 }
987
Mike Lockwood59c777a2010-08-02 10:37:41 -0400988 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400989 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800990 String path = null;
991 int format = 0;
992
993 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400994 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700995 c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800996 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800997 if (c != null && c.moveToNext()) {
998 // don't convert to media path here, since we will be matching
999 // against paths in the database matching /data/media
1000 path = c.getString(1);
Mike Lockwoodf6f16612012-09-12 15:50:59 -07001001 format = c.getInt(2);
Mike Lockwood55f808c2010-12-14 13:14:29 -08001002 } else {
1003 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
1004 }
1005
1006 if (path == null || format == 0) {
1007 return MtpConstants.RESPONSE_GENERAL_ERROR;
1008 }
1009
Mike Lockwood73e56d92011-12-01 16:58:41 -05001010 // do not allow deleting any of the special subdirectories
1011 if (isStorageSubDirectory(path)) {
1012 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
1013 }
1014
Mike Lockwood55f808c2010-12-14 13:14:29 -08001015 if (format == MtpConstants.FORMAT_ASSOCIATION) {
1016 // recursive case - delete all children first
1017 Uri uri = Files.getMtpObjectsUri(mVolumeName);
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001018 int count = mMediaProvider.delete(uri,
Mike Lockwood1e855d92012-06-26 16:31:41 -07001019 // the 'like' makes it use the index, the 'lower()' makes it correct
1020 // when the path contains sqlite wildcard characters
1021 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
1022 new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -08001023 }
1024
1025 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001026 if (mMediaProvider.delete(uri, null, null) > 0) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001027 if (format != MtpConstants.FORMAT_ASSOCIATION
1028 && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
1029 try {
1030 String parentPath = path.substring(0, path.lastIndexOf("/"));
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001031 mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001032 } catch (RemoteException e) {
1033 Log.e(TAG, "failed to unhide/rescan for " + path);
1034 }
1035 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001036 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001037 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001038 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001039 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001040 } catch (RemoteException e) {
1041 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -04001042 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -08001043 } finally {
1044 if (c != null) {
1045 c.close();
1046 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001047 }
1048 }
1049
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001050 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -04001051 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001052 Cursor c = null;
1053 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001054 c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001055 if (c == null) {
1056 return null;
1057 }
1058 int count = c.getCount();
1059 if (count > 0) {
1060 int[] result = new int[count];
1061 for (int i = 0; i < count; i++) {
1062 c.moveToNext();
1063 result[i] = c.getInt(0);
1064 }
1065 return result;
1066 }
1067 } catch (RemoteException e) {
1068 Log.e(TAG, "RemoteException in getObjectList", e);
1069 } finally {
1070 if (c != null) {
1071 c.close();
1072 }
1073 }
1074 return null;
1075 }
1076
1077 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001078 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -04001079 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001080 int count = references.length;
1081 ContentValues[] valuesList = new ContentValues[count];
1082 for (int i = 0; i < count; i++) {
1083 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -04001084 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001085 valuesList[i] = values;
1086 }
1087 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001088 if (mMediaProvider.bulkInsert(uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001089 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001090 }
1091 } catch (RemoteException e) {
1092 Log.e(TAG, "RemoteException in setObjectReferences", e);
1093 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001094 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001095 }
1096
Mike Lockwood2837eef2010-08-31 16:25:12 -04001097 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001098 mDatabaseModified = false;
1099 }
1100
1101 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001102 if (mDatabaseModified) {
Mike Lockwooda3156052010-11-20 12:28:27 -05001103 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -04001104 mDatabaseModified = false;
1105 }
1106 }
1107
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001108 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +00001109 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001110
1111 private native final void native_setup();
1112 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001113}