blob: ba29d2daaa0e2216016d6b51d370e7c0a217399d [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;
Jerry Zhang13bb2f42016-12-14 15:39:29 -080032import android.os.SystemProperties;
Mike Lockwooda3156052010-11-20 12:28:27 -050033import android.provider.MediaStore;
Mike Lockwood9a2046f2010-08-03 15:30:09 -040034import android.provider.MediaStore.Audio;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040035import android.provider.MediaStore.Files;
Mike Lockwoodae078f72010-09-26 12:35:51 -040036import android.provider.MediaStore.MediaColumns;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040037import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080038import android.view.Display;
39import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040040
Jeff Sharkey60cfad82016-01-05 17:30:57 -070041import dalvik.system.CloseGuard;
42
Mike Lockwood5ebac832010-10-12 11:33:47 -040043import java.io.File;
Marco Nelissen5f411692014-09-26 16:03:49 -070044import java.io.IOException;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050045import java.util.HashMap;
dujin.chafe464a72011-11-22 12:13:33 +090046import java.util.Locale;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070047import java.util.concurrent.atomic.AtomicBoolean;
Mike Lockwood5ebac832010-10-12 11:33:47 -040048
Mike Lockwoodd21eac92010-07-03 00:44:05 -040049/**
50 * {@hide}
51 */
Jeff Sharkey60cfad82016-01-05 17:30:57 -070052public class MtpDatabase implements AutoCloseable {
Mike Lockwoodd21eac92010-07-03 00:44:05 -040053 private static final String TAG = "MtpDatabase";
54
Jerry Zhang5f0139d2017-08-22 17:42:54 -070055 private final Context mUserContext;
Mike Lockwood2837eef2010-08-31 16:25:12 -040056 private final Context mContext;
Dianne Hackborn35654b62013-01-14 17:38:02 -080057 private final String mPackageName;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070058 private final ContentProviderClient mMediaProvider;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040059 private final String mVolumeName;
60 private final Uri mObjectsUri;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070061 private final MediaScanner mMediaScanner;
62
63 private final AtomicBoolean mClosed = new AtomicBoolean();
64 private final CloseGuard mCloseGuard = CloseGuard.get();
65
Mike Lockwood73e56d92011-12-01 16:58:41 -050066 // path to primary storage
67 private final String mMediaStoragePath;
68 // if not null, restrict all queries to these subdirectories
69 private final String[] mSubDirectories;
70 // where clause for restricting queries to files in mSubDirectories
71 private String mSubDirectoriesWhere;
72 // where arguments for restricting queries to files in mSubDirectories
73 private String[] mSubDirectoriesWhereArgs;
74
Mike Lockwoodb239b6832011-04-05 10:21:27 -040075 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040076
Mike Lockwood7d7fb632010-12-01 18:46:23 -050077 // cached property groups for single properties
78 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
79 = new HashMap<Integer, MtpPropertyGroup>();
80
81 // cached property groups for all properties for a given format
82 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
83 = new HashMap<Integer, MtpPropertyGroup>();
84
Mike Lockwood2837eef2010-08-31 16:25:12 -040085 // true if the database has been modified in the current MTP session
86 private boolean mDatabaseModified;
87
Mike Lockwood775de952011-03-05 17:34:11 -050088 // SharedPreferences for writable MTP device properties
89 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040090 private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
91
Mike Lockwoodd21eac92010-07-03 00:44:05 -040092 private static final String[] ID_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040093 Files.FileColumns._ID, // 0
Mike Lockwoodd21eac92010-07-03 00:44:05 -040094 };
Mike Lockwood6a6a3af2010-10-12 14:19:51 -040095 private static final String[] PATH_PROJECTION = new String[] {
Mike Lockwood5ebac832010-10-12 11:33:47 -040096 Files.FileColumns._ID, // 0
97 Files.FileColumns.DATA, // 1
Mike Lockwood5ebac832010-10-12 11:33:47 -040098 };
Mike Lockwood71827742015-01-23 10:50:08 -080099 private static final String[] FORMAT_PROJECTION = new String[] {
100 Files.FileColumns._ID, // 0
101 Files.FileColumns.FORMAT, // 1
102 };
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700103 private static final String[] PATH_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400104 Files.FileColumns._ID, // 0
105 Files.FileColumns.DATA, // 1
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700106 Files.FileColumns.FORMAT, // 2
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400107 };
108 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400109 Files.FileColumns._ID, // 0
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400110 Files.FileColumns.STORAGE_ID, // 1
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400111 Files.FileColumns.FORMAT, // 2
112 Files.FileColumns.PARENT, // 3
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400113 Files.FileColumns.DATA, // 4
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700114 Files.FileColumns.DATE_ADDED, // 5
115 Files.FileColumns.DATE_MODIFIED, // 6
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400116 };
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400117 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwoodbafca212010-12-13 21:50:09 -0800118 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400119
120 private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
Mike Lockwood58e6831c2012-09-11 10:49:34 -0700121 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
122 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400123 private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400124 + Files.FileColumns.FORMAT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400125 private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
126 + Files.FileColumns.PARENT + "=?";
127 private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
128 + Files.FileColumns.PARENT + "=?";
129 private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
130 + Files.FileColumns.PARENT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400131
Mike Lockwood56c85242014-03-07 13:29:08 -0800132 private MtpServer mServer;
133
134 // read from native code
135 private int mBatteryLevel;
136 private int mBatteryScale;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400137
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800138 private int mDeviceType;
139
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400140 static {
141 System.loadLibrary("media_jni");
142 }
143
Mike Lockwood56c85242014-03-07 13:29:08 -0800144 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
145 @Override
146 public void onReceive(Context context, Intent intent) {
147 String action = intent.getAction();
148 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
149 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
150 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
151 if (newLevel != mBatteryLevel) {
152 mBatteryLevel = newLevel;
153 if (mServer != null) {
154 // send device property changed event
155 mServer.sendDevicePropertyChanged(
156 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
157 }
158 }
159 }
160 }
161 };
162
Jerry Zhang5f0139d2017-08-22 17:42:54 -0700163 public MtpDatabase(Context context, Context userContext, String volumeName, String storagePath,
Mike Lockwood73e56d92011-12-01 16:58:41 -0500164 String[] subDirectories) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400165 native_setup();
166
Mike Lockwood2837eef2010-08-31 16:25:12 -0400167 mContext = context;
Jerry Zhang5f0139d2017-08-22 17:42:54 -0700168 mUserContext = userContext;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800169 mPackageName = context.getPackageName();
Jerry Zhang5f0139d2017-08-22 17:42:54 -0700170 mMediaProvider = userContext.getContentResolver()
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700171 .acquireContentProviderClient(MediaStore.AUTHORITY);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400172 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400173 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400174 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700175 mMediaScanner = new MediaScanner(context, mVolumeName);
dujin.chafe464a72011-11-22 12:13:33 +0900176
Mike Lockwood73e56d92011-12-01 16:58:41 -0500177 mSubDirectories = subDirectories;
178 if (subDirectories != null) {
179 // Compute "where" string for restricting queries to subdirectories
180 StringBuilder builder = new StringBuilder();
181 builder.append("(");
182 int count = subDirectories.length;
183 for (int i = 0; i < count; i++) {
184 builder.append(Files.FileColumns.DATA + "=? OR "
185 + Files.FileColumns.DATA + " LIKE ?");
186 if (i != count - 1) {
187 builder.append(" OR ");
188 }
189 }
190 builder.append(")");
191 mSubDirectoriesWhere = builder.toString();
192
193 // Compute "where" arguments for restricting queries to subdirectories
194 mSubDirectoriesWhereArgs = new String[count * 2];
195 for (int i = 0, j = 0; i < count; i++) {
196 String path = subDirectories[i];
197 mSubDirectoriesWhereArgs[j++] = path;
198 mSubDirectoriesWhereArgs[j++] = path + "/%";
199 }
200 }
201
Mike Lockwood775de952011-03-05 17:34:11 -0500202 initDeviceProperties(context);
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800203 mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700204
205 mCloseGuard.open("close");
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400206 }
207
Mike Lockwood56c85242014-03-07 13:29:08 -0800208 public void setServer(MtpServer server) {
209 mServer = server;
210
Marco Nelissen1632fae2014-03-27 13:25:14 -0700211 // always unregister before registering
212 try {
213 mContext.unregisterReceiver(mBatteryReceiver);
214 } catch (IllegalArgumentException e) {
215 // wasn't previously registered, ignore
216 }
217
Mike Lockwood56c85242014-03-07 13:29:08 -0800218 // register for battery notifications when we are connected
219 if (server != null) {
220 mContext.registerReceiver(mBatteryReceiver,
221 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Mike Lockwood56c85242014-03-07 13:29:08 -0800222 }
223 }
224
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400225 @Override
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700226 public void close() {
227 mCloseGuard.close();
228 if (mClosed.compareAndSet(false, true)) {
229 mMediaScanner.close();
230 mMediaProvider.close();
231 native_finalize();
232 }
233 }
234
235 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400236 protected void finalize() throws Throwable {
237 try {
Narayan Kamath492e9e82017-03-22 14:28:08 +0000238 if (mCloseGuard != null) {
239 mCloseGuard.warnIfOpen();
240 }
241
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700242 close();
Mike Lockwooddbead322010-08-30 09:27:55 -0400243 } finally {
244 super.finalize();
245 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400246 }
247
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400248 public void addStorage(MtpStorage storage) {
249 mStorageMap.put(storage.getPath(), storage);
250 }
251
252 public void removeStorage(MtpStorage storage) {
253 mStorageMap.remove(storage.getPath());
254 }
255
Mike Lockwood775de952011-03-05 17:34:11 -0500256 private void initDeviceProperties(Context context) {
257 final String devicePropertiesName = "device-properties";
258 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
259 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400260
Mike Lockwood775de952011-03-05 17:34:11 -0500261 if (databaseFile.exists()) {
262 // for backward compatibility - read device properties from sqlite database
263 // and migrate them to shared prefs
264 SQLiteDatabase db = null;
265 Cursor c = null;
266 try {
267 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
268 if (db != null) {
269 c = db.query("properties", new String[] { "_id", "code", "value" },
270 null, null, null, null, null);
271 if (c != null) {
272 SharedPreferences.Editor e = mDeviceProperties.edit();
273 while (c.moveToNext()) {
274 String name = c.getString(1);
275 String value = c.getString(2);
276 e.putString(name, value);
277 }
278 e.commit();
279 }
280 }
281 } catch (Exception e) {
282 Log.e(TAG, "failed to migrate device properties", e);
283 } finally {
284 if (c != null) c.close();
285 if (db != null) db.close();
286 }
jangwon.lee3ed02532013-07-22 19:52:48 +0900287 context.deleteDatabase(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400288 }
289 }
290
Mike Lockwood73e56d92011-12-01 16:58:41 -0500291 // check to see if the path is contained in one of our storage subdirectories
292 // returns true if we have no special subdirectories
293 private boolean inStorageSubDirectory(String path) {
294 if (mSubDirectories == null) return true;
295 if (path == null) return false;
296
297 boolean allowed = false;
298 int pathLength = path.length();
299 for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
300 String subdir = mSubDirectories[i];
301 int subdirLength = subdir.length();
302 if (subdirLength < pathLength &&
303 path.charAt(subdirLength) == '/' &&
304 path.startsWith(subdir)) {
305 allowed = true;
306 }
307 }
308 return allowed;
309 }
310
311 // check to see if the path matches one of our storage subdirectories
312 // returns true if we have no special subdirectories
313 private boolean isStorageSubDirectory(String path) {
314 if (mSubDirectories == null) return false;
315 for (int i = 0; i < mSubDirectories.length; i++) {
316 if (path.equals(mSubDirectories[i])) {
317 return true;
318 }
319 }
320 return false;
321 }
322
Marco Nelissen5f411692014-09-26 16:03:49 -0700323 // returns true if the path is in the storage root
324 private boolean inStorageRoot(String path) {
325 try {
326 File f = new File(path);
327 String canonical = f.getCanonicalPath();
Marco Nelissenc1fda122014-10-15 14:32:22 -0700328 for (String root: mStorageMap.keySet()) {
329 if (canonical.startsWith(root)) {
330 return true;
331 }
Marco Nelissen5f411692014-09-26 16:03:49 -0700332 }
333 } catch (IOException e) {
334 // ignore
335 }
336 return false;
337 }
338
Mike Lockwoodd815f792010-07-12 08:49:01 -0400339 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400340 int storageId, long size, long modified) {
Marco Nelissen5f411692014-09-26 16:03:49 -0700341 // if the path is outside of the storage root, do not allow access
342 if (!inStorageRoot(path)) {
343 Log.e(TAG, "attempt to put file outside of storage area: " + path);
344 return -1;
345 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500346 // if mSubDirectories is not null, do not allow copying files to any other locations
347 if (!inStorageSubDirectory(path)) return -1;
348
349 // make sure the object does not exist
Mike Lockwoodbafca212010-12-13 21:50:09 -0800350 if (path != null) {
351 Cursor c = null;
352 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700353 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800354 new String[] { path }, null, null);
Mike Lockwoodbafca212010-12-13 21:50:09 -0800355 if (c != null && c.getCount() > 0) {
356 Log.w(TAG, "file already exists in beginSendObject: " + path);
357 return -1;
358 }
359 } catch (RemoteException e) {
360 Log.e(TAG, "RemoteException in beginSendObject", e);
361 } finally {
362 if (c != null) {
363 c.close();
364 }
365 }
366 }
367
Mike Lockwood2837eef2010-08-31 16:25:12 -0400368 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400369 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400370 values.put(Files.FileColumns.DATA, path);
371 values.put(Files.FileColumns.FORMAT, format);
372 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400373 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400374 values.put(Files.FileColumns.SIZE, size);
375 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400376
377 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700378 Uri uri = mMediaProvider.insert(mObjectsUri, values);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400379 if (uri != null) {
380 return Integer.parseInt(uri.getPathSegments().get(2));
381 } else {
382 return -1;
383 }
384 } catch (RemoteException e) {
385 Log.e(TAG, "RemoteException in beginSendObject", e);
386 return -1;
387 }
388 }
389
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800390 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400391 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400392 // handle abstract playlists separately
393 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400394 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400395 // extract name from path
396 String name = path;
397 int lastSlash = name.lastIndexOf('/');
398 if (lastSlash >= 0) {
399 name = name.substring(lastSlash + 1);
400 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800401 // strip trailing ".pla" from the name
402 if (name.endsWith(".pla")) {
403 name = name.substring(0, name.length() - 4);
404 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400405
406 ContentValues values = new ContentValues(1);
407 values.put(Audio.Playlists.DATA, path);
408 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500409 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800410 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400411 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
412 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700413 Uri uri = mMediaProvider.insert(
Dianne Hackborn35654b62013-01-14 17:38:02 -0800414 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400415 } catch (RemoteException e) {
416 Log.e(TAG, "RemoteException in endSendObject", e);
417 }
418 } else {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700419 mMediaScanner.scanMtpFile(path, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400420 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400421 } else {
422 deleteFile(handle);
423 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400424 }
425
kyle_tsob4aa69f2017-11-22 20:11:27 +0800426 private void doScanDirectory(String path) {
427 String[] scanPath;
428 scanPath = new String[] { path };
429 mMediaScanner.scanDirectories(scanPath);
430 }
431
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400432 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500433 String where;
434 String[] whereArgs;
435
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400436 if (storageID == 0xFFFFFFFF) {
437 // query all stores
438 if (format == 0) {
439 // query all formats
440 if (parent == 0) {
441 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500442 where = null;
443 whereArgs = null;
444 } else {
445 if (parent == 0xFFFFFFFF) {
446 // all objects in root of store
447 parent = 0;
448 }
449 where = PARENT_WHERE;
450 whereArgs = new String[] { Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400451 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400452 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400453 // query specific format
454 if (parent == 0) {
455 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500456 where = FORMAT_WHERE;
457 whereArgs = new String[] { Integer.toString(format) };
458 } else {
459 if (parent == 0xFFFFFFFF) {
460 // all objects in root of store
461 parent = 0;
462 }
463 where = FORMAT_PARENT_WHERE;
464 whereArgs = new String[] { Integer.toString(format),
465 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400466 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400467 }
468 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400469 // query specific store
470 if (format == 0) {
471 // query all formats
472 if (parent == 0) {
473 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500474 where = STORAGE_WHERE;
475 whereArgs = new String[] { Integer.toString(storageID) };
476 } else {
477 if (parent == 0xFFFFFFFF) {
478 // all objects in root of store
479 parent = 0;
Jerry Zhangdef7b1932017-10-17 13:47:51 -0700480 where = STORAGE_PARENT_WHERE;
481 whereArgs = new String[]{Integer.toString(storageID),
482 Integer.toString(parent)};
483 } else {
484 // If a parent is specified, the storage is redundant
485 where = PARENT_WHERE;
486 whereArgs = new String[]{Integer.toString(parent)};
Mike Lockwood73e56d92011-12-01 16:58:41 -0500487 }
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400488 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400489 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400490 // query specific format
491 if (parent == 0) {
492 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500493 where = STORAGE_FORMAT_WHERE;
494 whereArgs = new String[] { Integer.toString(storageID),
495 Integer.toString(format) };
496 } else {
497 if (parent == 0xFFFFFFFF) {
498 // all objects in root of store
499 parent = 0;
Jerry Zhangdef7b1932017-10-17 13:47:51 -0700500 where = STORAGE_FORMAT_PARENT_WHERE;
501 whereArgs = new String[]{Integer.toString(storageID),
502 Integer.toString(format),
503 Integer.toString(parent)};
504 } else {
505 // If a parent is specified, the storage is redundant
506 where = FORMAT_PARENT_WHERE;
507 whereArgs = new String[]{Integer.toString(format),
508 Integer.toString(parent)};
Mike Lockwood73e56d92011-12-01 16:58:41 -0500509 }
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400510 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400511 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400512 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500513
514 // if we are restricting queries to mSubDirectories, we need to add the restriction
515 // onto our "where" arguments
516 if (mSubDirectoriesWhere != null) {
517 if (where == null) {
518 where = mSubDirectoriesWhere;
519 whereArgs = mSubDirectoriesWhereArgs;
520 } else {
521 where = where + " AND " + mSubDirectoriesWhere;
522
523 // create new array to hold whereArgs and mSubDirectoriesWhereArgs
524 String[] newWhereArgs =
525 new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
526 int i, j;
527 for (i = 0; i < whereArgs.length; i++) {
528 newWhereArgs[i] = whereArgs[i];
529 }
530 for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
531 newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
532 }
533 whereArgs = newWhereArgs;
534 }
535 }
536
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700537 return mMediaProvider.query(mObjectsUri, ID_PROJECTION, where,
Dianne Hackborn35654b62013-01-14 17:38:02 -0800538 whereArgs, null, null);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400539 }
540
541 private int[] getObjectList(int storageID, int format, int parent) {
542 Cursor c = null;
543 try {
544 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400545 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400546 return null;
547 }
548 int count = c.getCount();
549 if (count > 0) {
550 int[] result = new int[count];
551 for (int i = 0; i < count; i++) {
552 c.moveToNext();
553 result[i] = c.getInt(0);
554 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400555 return result;
556 }
557 } catch (RemoteException e) {
558 Log.e(TAG, "RemoteException in getObjectList", e);
559 } finally {
560 if (c != null) {
561 c.close();
562 }
563 }
564 return null;
565 }
566
Mike Lockwood7a047c82010-08-02 10:52:20 -0400567 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400568 Cursor c = null;
569 try {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400570 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400571 if (c != null) {
572 return c.getCount();
573 }
574 } catch (RemoteException e) {
575 Log.e(TAG, "RemoteException in getNumObjects", e);
576 } finally {
577 if (c != null) {
578 c.close();
579 }
580 }
581 return -1;
582 }
583
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400584 private int[] getSupportedPlaybackFormats() {
585 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400586 // allow transfering arbitrary files
587 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400588
Mike Lockwood792ec842010-09-09 15:30:10 -0400589 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400590 MtpConstants.FORMAT_TEXT,
591 MtpConstants.FORMAT_HTML,
592 MtpConstants.FORMAT_WAV,
593 MtpConstants.FORMAT_MP3,
594 MtpConstants.FORMAT_MPEG,
595 MtpConstants.FORMAT_EXIF_JPEG,
596 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800597 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400598 MtpConstants.FORMAT_GIF,
599 MtpConstants.FORMAT_JFIF,
600 MtpConstants.FORMAT_PNG,
601 MtpConstants.FORMAT_TIFF,
602 MtpConstants.FORMAT_WMA,
603 MtpConstants.FORMAT_OGG,
604 MtpConstants.FORMAT_AAC,
605 MtpConstants.FORMAT_MP4_CONTAINER,
606 MtpConstants.FORMAT_MP2,
607 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400608 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400609 MtpConstants.FORMAT_WPL_PLAYLIST,
610 MtpConstants.FORMAT_M3U_PLAYLIST,
611 MtpConstants.FORMAT_PLS_PLAYLIST,
612 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800613 MtpConstants.FORMAT_FLAC,
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100614 MtpConstants.FORMAT_DNG,
Chong Zhang6e18cce2017-08-16 11:57:02 -0700615 MtpConstants.FORMAT_HEIF,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400616 };
617 }
618
619 private int[] getSupportedCaptureFormats() {
620 // no capture formats yet
621 return null;
622 }
623
Mike Lockwoodae078f72010-09-26 12:35:51 -0400624 static final int[] FILE_PROPERTIES = {
625 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
626 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400627 MtpConstants.PROPERTY_STORAGE_ID,
628 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400629 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400630 MtpConstants.PROPERTY_OBJECT_SIZE,
631 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400632 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400633 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400634 MtpConstants.PROPERTY_PERSISTENT_UID,
635 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800636 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400637 MtpConstants.PROPERTY_DATE_ADDED,
638 };
639
640 static final int[] AUDIO_PROPERTIES = {
641 // NOTE must match FILE_PROPERTIES above
642 MtpConstants.PROPERTY_STORAGE_ID,
643 MtpConstants.PROPERTY_OBJECT_FORMAT,
644 MtpConstants.PROPERTY_PROTECTION_STATUS,
645 MtpConstants.PROPERTY_OBJECT_SIZE,
646 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
647 MtpConstants.PROPERTY_DATE_MODIFIED,
648 MtpConstants.PROPERTY_PARENT_OBJECT,
649 MtpConstants.PROPERTY_PERSISTENT_UID,
650 MtpConstants.PROPERTY_NAME,
651 MtpConstants.PROPERTY_DISPLAY_NAME,
652 MtpConstants.PROPERTY_DATE_ADDED,
653
654 // audio specific properties
655 MtpConstants.PROPERTY_ARTIST,
656 MtpConstants.PROPERTY_ALBUM_NAME,
657 MtpConstants.PROPERTY_ALBUM_ARTIST,
658 MtpConstants.PROPERTY_TRACK,
659 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
660 MtpConstants.PROPERTY_DURATION,
661 MtpConstants.PROPERTY_GENRE,
662 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700663 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
664 MtpConstants.PROPERTY_BITRATE_TYPE,
665 MtpConstants.PROPERTY_AUDIO_BITRATE,
666 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
667 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400668 };
669
670 static final int[] VIDEO_PROPERTIES = {
671 // NOTE must match FILE_PROPERTIES above
672 MtpConstants.PROPERTY_STORAGE_ID,
673 MtpConstants.PROPERTY_OBJECT_FORMAT,
674 MtpConstants.PROPERTY_PROTECTION_STATUS,
675 MtpConstants.PROPERTY_OBJECT_SIZE,
676 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
677 MtpConstants.PROPERTY_DATE_MODIFIED,
678 MtpConstants.PROPERTY_PARENT_OBJECT,
679 MtpConstants.PROPERTY_PERSISTENT_UID,
680 MtpConstants.PROPERTY_NAME,
681 MtpConstants.PROPERTY_DISPLAY_NAME,
682 MtpConstants.PROPERTY_DATE_ADDED,
683
684 // video specific properties
685 MtpConstants.PROPERTY_ARTIST,
686 MtpConstants.PROPERTY_ALBUM_NAME,
687 MtpConstants.PROPERTY_DURATION,
688 MtpConstants.PROPERTY_DESCRIPTION,
689 };
690
691 static final int[] IMAGE_PROPERTIES = {
692 // NOTE must match FILE_PROPERTIES above
693 MtpConstants.PROPERTY_STORAGE_ID,
694 MtpConstants.PROPERTY_OBJECT_FORMAT,
695 MtpConstants.PROPERTY_PROTECTION_STATUS,
696 MtpConstants.PROPERTY_OBJECT_SIZE,
697 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
698 MtpConstants.PROPERTY_DATE_MODIFIED,
699 MtpConstants.PROPERTY_PARENT_OBJECT,
700 MtpConstants.PROPERTY_PERSISTENT_UID,
701 MtpConstants.PROPERTY_NAME,
702 MtpConstants.PROPERTY_DISPLAY_NAME,
703 MtpConstants.PROPERTY_DATE_ADDED,
704
705 // image specific properties
706 MtpConstants.PROPERTY_DESCRIPTION,
707 };
708
709 private int[] getSupportedObjectProperties(int format) {
710 switch (format) {
711 case MtpConstants.FORMAT_MP3:
712 case MtpConstants.FORMAT_WAV:
713 case MtpConstants.FORMAT_WMA:
714 case MtpConstants.FORMAT_OGG:
715 case MtpConstants.FORMAT_AAC:
716 return AUDIO_PROPERTIES;
717 case MtpConstants.FORMAT_MPEG:
718 case MtpConstants.FORMAT_3GP_CONTAINER:
719 case MtpConstants.FORMAT_WMV:
720 return VIDEO_PROPERTIES;
721 case MtpConstants.FORMAT_EXIF_JPEG:
722 case MtpConstants.FORMAT_GIF:
723 case MtpConstants.FORMAT_PNG:
724 case MtpConstants.FORMAT_BMP:
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100725 case MtpConstants.FORMAT_DNG:
Chong Zhang6e18cce2017-08-16 11:57:02 -0700726 case MtpConstants.FORMAT_HEIF:
Mike Lockwoodae078f72010-09-26 12:35:51 -0400727 return IMAGE_PROPERTIES;
728 default:
729 return FILE_PROPERTIES;
730 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400731 }
732
733 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400734 return new int[] {
735 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
736 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800737 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood56c85242014-03-07 13:29:08 -0800738 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800739 MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400740 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400741 }
742
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900743 private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400744 int groupCode, int depth) {
745 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400746 if (groupCode != 0) {
747 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
748 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400749
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500750 MtpPropertyGroup propertyGroup;
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900751 if (property == 0xffffffff) {
752 if (format == 0 && handle != 0 && handle != 0xffffffff) {
Mike Lockwood71827742015-01-23 10:50:08 -0800753 // return properties based on the object's format
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900754 format = getObjectFormat(handle);
Mike Lockwood71827742015-01-23 10:50:08 -0800755 }
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900756 propertyGroup = mPropertyGroupsByFormat.get(format);
757 if (propertyGroup == null) {
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500758 int[] propertyList = getSupportedObjectProperties(format);
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700759 propertyGroup = new MtpPropertyGroup(this, mMediaProvider,
Dianne Hackborn35654b62013-01-14 17:38:02 -0800760 mVolumeName, propertyList);
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900761 mPropertyGroupsByFormat.put(format, propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400762 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500763 } else {
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900764 propertyGroup = mPropertyGroupsByProperty.get(property);
765 if (propertyGroup == null) {
766 final int[] propertyList = new int[] { property };
767 propertyGroup = new MtpPropertyGroup(
768 this, mMediaProvider, mVolumeName, propertyList);
769 mPropertyGroupsByProperty.put(property, propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400770 }
771 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500772
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900773 return propertyGroup.getPropertyList(handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400774 }
775
Mike Lockwood5ebac832010-10-12 11:33:47 -0400776 private int renameFile(int handle, String newName) {
777 Cursor c = null;
778
779 // first compute current path
780 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400781 String[] whereArgs = new String[] { Integer.toString(handle) };
782 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700783 c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE,
Dianne Hackborn35654b62013-01-14 17:38:02 -0800784 whereArgs, null, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400785 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500786 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400787 }
788 } catch (RemoteException e) {
789 Log.e(TAG, "RemoteException in getObjectFilePath", e);
790 return MtpConstants.RESPONSE_GENERAL_ERROR;
791 } finally {
792 if (c != null) {
793 c.close();
794 }
795 }
796 if (path == null) {
797 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
798 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400799
Mike Lockwood73e56d92011-12-01 16:58:41 -0500800 // do not allow renaming any of the special subdirectories
801 if (isStorageSubDirectory(path)) {
802 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
803 }
804
Mike Lockwood5ebac832010-10-12 11:33:47 -0400805 // now rename the file. make sure this succeeds before updating database
806 File oldFile = new File(path);
807 int lastSlash = path.lastIndexOf('/');
808 if (lastSlash <= 1) {
809 return MtpConstants.RESPONSE_GENERAL_ERROR;
810 }
811 String newPath = path.substring(0, lastSlash + 1) + newName;
812 File newFile = new File(newPath);
813 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400814 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800815 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400816 return MtpConstants.RESPONSE_GENERAL_ERROR;
817 }
818
819 // finally update database
820 ContentValues values = new ContentValues();
821 values.put(Files.FileColumns.DATA, newPath);
822 int updated = 0;
823 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400824 // note - we are relying on a special case in MediaProvider.update() to update
825 // the paths for all children in the case where this is a directory.
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700826 updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400827 } catch (RemoteException e) {
828 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
829 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400830 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400831 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
832 // this shouldn't happen, but if it does we need to rename the file to its original name
833 newFile.renameTo(oldFile);
834 return MtpConstants.RESPONSE_GENERAL_ERROR;
835 }
836
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800837 // check if nomedia status changed
838 if (newFile.isDirectory()) {
839 // for directories, check if renamed from something hidden to something non-hidden
840 if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
841 // directory was unhidden
842 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700843 mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800844 } catch (RemoteException e) {
845 Log.e(TAG, "failed to unhide/rescan for " + newPath);
846 }
847 }
848 } else {
849 // for files, check if renamed from .nomedia to something else
850 if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
851 && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
852 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700853 mMediaProvider.call(MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800854 } catch (RemoteException e) {
855 Log.e(TAG, "failed to unhide/rescan for " + newPath);
856 }
857 }
858 }
859
Mike Lockwood5ebac832010-10-12 11:33:47 -0400860 return MtpConstants.RESPONSE_OK;
861 }
862
Jerry Zhangdef7b1932017-10-17 13:47:51 -0700863 private int moveObject(int handle, int newParent, int newStorage, String newPath) {
Jerry Zhang952558d42017-09-26 17:49:52 -0700864 String[] whereArgs = new String[] { Integer.toString(handle) };
865
866 // do not allow renaming any of the special subdirectories
867 if (isStorageSubDirectory(newPath)) {
868 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
869 }
870
871 // update database
872 ContentValues values = new ContentValues();
873 values.put(Files.FileColumns.DATA, newPath);
874 values.put(Files.FileColumns.PARENT, newParent);
Jerry Zhangdef7b1932017-10-17 13:47:51 -0700875 values.put(Files.FileColumns.STORAGE_ID, newStorage);
Jerry Zhang952558d42017-09-26 17:49:52 -0700876 int updated = 0;
877 try {
878 // note - we are relying on a special case in MediaProvider.update() to update
879 // the paths for all children in the case where this is a directory.
880 updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
881 } catch (RemoteException e) {
882 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
883 }
884 if (updated == 0) {
885 Log.e(TAG, "Unable to update path for " + handle + " to " + newPath);
886 return MtpConstants.RESPONSE_GENERAL_ERROR;
887 }
888 return MtpConstants.RESPONSE_OK;
889 }
890
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400891 private int setObjectProperty(int handle, int property,
892 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400893 switch (property) {
894 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
895 return renameFile(handle, stringValue);
896
897 default:
898 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
899 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400900 }
901
902 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400903 switch (property) {
904 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
905 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500906 // writable string properties kept in shared preferences
907 String value = mDeviceProperties.getString(Integer.toString(property), "");
908 int length = value.length();
909 if (length > 255) {
910 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400911 }
Mike Lockwood775de952011-03-05 17:34:11 -0500912 value.getChars(0, length, outStringValue, 0);
913 outStringValue[length] = 0;
914 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400915
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800916 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
917 // use screen size as max image size
918 Display display = ((WindowManager)mContext.getSystemService(
919 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700920 int width = display.getMaximumSizeDimension();
921 int height = display.getMaximumSizeDimension();
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800922 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
923 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
924 outStringValue[imageSize.length()] = 0;
925 return MtpConstants.RESPONSE_OK;
926
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800927 case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
928 outIntValue[0] = mDeviceType;
929 return MtpConstants.RESPONSE_OK;
930
Mike Lockwood56c85242014-03-07 13:29:08 -0800931 // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
932
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800933 default:
934 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
935 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400936 }
937
938 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400939 switch (property) {
940 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
941 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500942 // writable string properties kept in shared prefs
943 SharedPreferences.Editor e = mDeviceProperties.edit();
944 e.putString(Integer.toString(property), stringValue);
945 return (e.commit() ? MtpConstants.RESPONSE_OK
946 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400947 }
948
949 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
950 }
951
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400952 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700953 char[] outName, long[] outCreatedModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400954 Cursor c = null;
955 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700956 c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800957 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400958 if (c != null && c.moveToNext()) {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400959 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400960 outStorageFormatParent[1] = c.getInt(2);
961 outStorageFormatParent[2] = c.getInt(3);
962
963 // extract name from path
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400964 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400965 int lastSlash = path.lastIndexOf('/');
966 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
967 int end = path.length();
968 if (end - start > 255) {
969 end = start + 255;
970 }
971 path.getChars(start, end, outName, 0);
972 outName[end - start] = 0;
973
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700974 outCreatedModified[0] = c.getLong(5);
975 outCreatedModified[1] = c.getLong(6);
976 // use modification date as creation date if date added is not set
977 if (outCreatedModified[0] == 0) {
978 outCreatedModified[0] = outCreatedModified[1];
979 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400980 return true;
981 }
982 } catch (RemoteException e) {
Mike Lockwood2b5f9ad12010-10-29 19:16:27 -0400983 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400984 } finally {
985 if (c != null) {
986 c.close();
987 }
988 }
989 return false;
990 }
991
Mike Lockwood365e03e2010-12-08 16:08:01 -0800992 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400993 if (handle == 0) {
994 // special case root directory
995 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
996 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800997 outFileLengthFormat[0] = 0;
998 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400999 return MtpConstants.RESPONSE_OK;
1000 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001001 Cursor c = null;
1002 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001003 c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -08001004 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001005 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -05001006 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001007 path.getChars(0, path.length(), outFilePath, 0);
1008 outFilePath[path.length()] = 0;
Mike Lockwoodf6f16612012-09-12 15:50:59 -07001009 // File transfers from device to host will likely fail if the size is incorrect.
1010 // So to be safe, use the actual file size here.
1011 outFileLengthFormat[0] = new File(path).length();
1012 outFileLengthFormat[1] = c.getLong(2);
Mike Lockwood5367ab62010-08-30 13:23:02 -04001013 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001014 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001015 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001016 }
1017 } catch (RemoteException e) {
1018 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -04001019 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001020 } finally {
1021 if (c != null) {
1022 c.close();
1023 }
1024 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001025 }
1026
Mike Lockwood71827742015-01-23 10:50:08 -08001027 private int getObjectFormat(int handle) {
1028 Cursor c = null;
1029 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001030 c = mMediaProvider.query(mObjectsUri, FORMAT_PROJECTION,
Daichi Hirono486ad2e2016-02-29 17:28:47 +09001031 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood71827742015-01-23 10:50:08 -08001032 if (c != null && c.moveToNext()) {
1033 return c.getInt(1);
1034 } else {
1035 return -1;
1036 }
1037 } catch (RemoteException e) {
1038 Log.e(TAG, "RemoteException in getObjectFilePath", e);
1039 return -1;
1040 } finally {
1041 if (c != null) {
1042 c.close();
1043 }
1044 }
1045 }
1046
Mike Lockwood59c777a2010-08-02 10:37:41 -04001047 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001048 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -08001049 String path = null;
1050 int format = 0;
1051
1052 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001053 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001054 c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -08001055 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -08001056 if (c != null && c.moveToNext()) {
1057 // don't convert to media path here, since we will be matching
1058 // against paths in the database matching /data/media
1059 path = c.getString(1);
Mike Lockwoodf6f16612012-09-12 15:50:59 -07001060 format = c.getInt(2);
Mike Lockwood55f808c2010-12-14 13:14:29 -08001061 } else {
1062 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
1063 }
1064
1065 if (path == null || format == 0) {
1066 return MtpConstants.RESPONSE_GENERAL_ERROR;
1067 }
1068
Mike Lockwood73e56d92011-12-01 16:58:41 -05001069 // do not allow deleting any of the special subdirectories
1070 if (isStorageSubDirectory(path)) {
1071 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
1072 }
1073
Mike Lockwood55f808c2010-12-14 13:14:29 -08001074 if (format == MtpConstants.FORMAT_ASSOCIATION) {
1075 // recursive case - delete all children first
1076 Uri uri = Files.getMtpObjectsUri(mVolumeName);
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001077 int count = mMediaProvider.delete(uri,
Mike Lockwood1e855d92012-06-26 16:31:41 -07001078 // the 'like' makes it use the index, the 'lower()' makes it correct
1079 // when the path contains sqlite wildcard characters
1080 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
1081 new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -08001082 }
1083
1084 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001085 if (mMediaProvider.delete(uri, null, null) > 0) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001086 if (format != MtpConstants.FORMAT_ASSOCIATION
1087 && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
1088 try {
1089 String parentPath = path.substring(0, path.lastIndexOf("/"));
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001090 mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001091 } catch (RemoteException e) {
1092 Log.e(TAG, "failed to unhide/rescan for " + path);
1093 }
1094 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001095 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001096 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001097 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001098 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001099 } catch (RemoteException e) {
1100 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -04001101 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -08001102 } finally {
1103 if (c != null) {
1104 c.close();
1105 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001106 }
1107 }
1108
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001109 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -04001110 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001111 Cursor c = null;
1112 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001113 c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001114 if (c == null) {
1115 return null;
1116 }
1117 int count = c.getCount();
1118 if (count > 0) {
1119 int[] result = new int[count];
1120 for (int i = 0; i < count; i++) {
1121 c.moveToNext();
1122 result[i] = c.getInt(0);
1123 }
1124 return result;
1125 }
1126 } catch (RemoteException e) {
1127 Log.e(TAG, "RemoteException in getObjectList", e);
1128 } finally {
1129 if (c != null) {
1130 c.close();
1131 }
1132 }
1133 return null;
1134 }
1135
1136 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001137 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -04001138 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001139 int count = references.length;
1140 ContentValues[] valuesList = new ContentValues[count];
1141 for (int i = 0; i < count; i++) {
1142 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -04001143 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001144 valuesList[i] = values;
1145 }
1146 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001147 if (mMediaProvider.bulkInsert(uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001148 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001149 }
1150 } catch (RemoteException e) {
1151 Log.e(TAG, "RemoteException in setObjectReferences", e);
1152 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001153 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001154 }
1155
Mike Lockwood2837eef2010-08-31 16:25:12 -04001156 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001157 mDatabaseModified = false;
1158 }
1159
1160 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001161 if (mDatabaseModified) {
Jerry Zhang5f0139d2017-08-22 17:42:54 -07001162 mUserContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -04001163 mDatabaseModified = false;
1164 }
1165 }
1166
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001167 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +00001168 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001169
1170 private native final void native_setup();
1171 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001172}