blob: 09a04bc1a508b5468bd9f3d3e939c4b0cbb834f6 [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
Mike Lockwood2837eef2010-08-31 16:25:12 -040055 private final Context mContext;
Dianne Hackborn35654b62013-01-14 17:38:02 -080056 private final String mPackageName;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070057 private final ContentProviderClient mMediaProvider;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040058 private final String mVolumeName;
59 private final Uri mObjectsUri;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070060 private final MediaScanner mMediaScanner;
61
62 private final AtomicBoolean mClosed = new AtomicBoolean();
63 private final CloseGuard mCloseGuard = CloseGuard.get();
64
Mike Lockwood73e56d92011-12-01 16:58:41 -050065 // path to primary storage
66 private final String mMediaStoragePath;
67 // if not null, restrict all queries to these subdirectories
68 private final String[] mSubDirectories;
69 // where clause for restricting queries to files in mSubDirectories
70 private String mSubDirectoriesWhere;
71 // where arguments for restricting queries to files in mSubDirectories
72 private String[] mSubDirectoriesWhereArgs;
73
Mike Lockwoodb239b6832011-04-05 10:21:27 -040074 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040075
Mike Lockwood7d7fb632010-12-01 18:46:23 -050076 // cached property groups for single properties
77 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
78 = new HashMap<Integer, MtpPropertyGroup>();
79
80 // cached property groups for all properties for a given format
81 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
82 = new HashMap<Integer, MtpPropertyGroup>();
83
Mike Lockwood2837eef2010-08-31 16:25:12 -040084 // true if the database has been modified in the current MTP session
85 private boolean mDatabaseModified;
86
Mike Lockwood775de952011-03-05 17:34:11 -050087 // SharedPreferences for writable MTP device properties
88 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040089 private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
90
Mike Lockwoodd21eac92010-07-03 00:44:05 -040091 private static final String[] ID_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040092 Files.FileColumns._ID, // 0
Mike Lockwoodd21eac92010-07-03 00:44:05 -040093 };
Mike Lockwood6a6a3af2010-10-12 14:19:51 -040094 private static final String[] PATH_PROJECTION = new String[] {
Mike Lockwood5ebac832010-10-12 11:33:47 -040095 Files.FileColumns._ID, // 0
96 Files.FileColumns.DATA, // 1
Mike Lockwood5ebac832010-10-12 11:33:47 -040097 };
Mike Lockwood71827742015-01-23 10:50:08 -080098 private static final String[] FORMAT_PROJECTION = new String[] {
99 Files.FileColumns._ID, // 0
100 Files.FileColumns.FORMAT, // 1
101 };
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700102 private static final String[] PATH_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400103 Files.FileColumns._ID, // 0
104 Files.FileColumns.DATA, // 1
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700105 Files.FileColumns.FORMAT, // 2
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400106 };
107 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400108 Files.FileColumns._ID, // 0
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400109 Files.FileColumns.STORAGE_ID, // 1
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400110 Files.FileColumns.FORMAT, // 2
111 Files.FileColumns.PARENT, // 3
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400112 Files.FileColumns.DATA, // 4
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700113 Files.FileColumns.DATE_ADDED, // 5
114 Files.FileColumns.DATE_MODIFIED, // 6
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400115 };
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400116 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwoodbafca212010-12-13 21:50:09 -0800117 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400118
119 private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
Mike Lockwood58e6831c2012-09-11 10:49:34 -0700120 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
121 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400122 private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400123 + Files.FileColumns.FORMAT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400124 private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
125 + Files.FileColumns.PARENT + "=?";
126 private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
127 + Files.FileColumns.PARENT + "=?";
128 private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
129 + Files.FileColumns.PARENT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400130
Mike Lockwood56c85242014-03-07 13:29:08 -0800131 private MtpServer mServer;
132
133 // read from native code
134 private int mBatteryLevel;
135 private int mBatteryScale;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400136
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800137 private int mDeviceType;
138
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400139 static {
140 System.loadLibrary("media_jni");
141 }
142
Mike Lockwood56c85242014-03-07 13:29:08 -0800143 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
144 @Override
145 public void onReceive(Context context, Intent intent) {
146 String action = intent.getAction();
147 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
148 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
149 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
150 if (newLevel != mBatteryLevel) {
151 mBatteryLevel = newLevel;
152 if (mServer != null) {
153 // send device property changed event
154 mServer.sendDevicePropertyChanged(
155 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
156 }
157 }
158 }
159 }
160 };
161
Mike Lockwood73e56d92011-12-01 16:58:41 -0500162 public MtpDatabase(Context context, String volumeName, String storagePath,
163 String[] subDirectories) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400164 native_setup();
165
Mike Lockwood2837eef2010-08-31 16:25:12 -0400166 mContext = context;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800167 mPackageName = context.getPackageName();
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700168 mMediaProvider = context.getContentResolver()
169 .acquireContentProviderClient(MediaStore.AUTHORITY);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400170 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400171 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400172 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700173 mMediaScanner = new MediaScanner(context, mVolumeName);
dujin.chafe464a72011-11-22 12:13:33 +0900174
Mike Lockwood73e56d92011-12-01 16:58:41 -0500175 mSubDirectories = subDirectories;
176 if (subDirectories != null) {
177 // Compute "where" string for restricting queries to subdirectories
178 StringBuilder builder = new StringBuilder();
179 builder.append("(");
180 int count = subDirectories.length;
181 for (int i = 0; i < count; i++) {
182 builder.append(Files.FileColumns.DATA + "=? OR "
183 + Files.FileColumns.DATA + " LIKE ?");
184 if (i != count - 1) {
185 builder.append(" OR ");
186 }
187 }
188 builder.append(")");
189 mSubDirectoriesWhere = builder.toString();
190
191 // Compute "where" arguments for restricting queries to subdirectories
192 mSubDirectoriesWhereArgs = new String[count * 2];
193 for (int i = 0, j = 0; i < count; i++) {
194 String path = subDirectories[i];
195 mSubDirectoriesWhereArgs[j++] = path;
196 mSubDirectoriesWhereArgs[j++] = path + "/%";
197 }
198 }
199
Mike Lockwood775de952011-03-05 17:34:11 -0500200 initDeviceProperties(context);
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800201 mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700202
203 mCloseGuard.open("close");
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400204 }
205
Mike Lockwood56c85242014-03-07 13:29:08 -0800206 public void setServer(MtpServer server) {
207 mServer = server;
208
Marco Nelissen1632fae2014-03-27 13:25:14 -0700209 // always unregister before registering
210 try {
211 mContext.unregisterReceiver(mBatteryReceiver);
212 } catch (IllegalArgumentException e) {
213 // wasn't previously registered, ignore
214 }
215
Mike Lockwood56c85242014-03-07 13:29:08 -0800216 // register for battery notifications when we are connected
217 if (server != null) {
218 mContext.registerReceiver(mBatteryReceiver,
219 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Mike Lockwood56c85242014-03-07 13:29:08 -0800220 }
221 }
222
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400223 @Override
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700224 public void close() {
225 mCloseGuard.close();
226 if (mClosed.compareAndSet(false, true)) {
227 mMediaScanner.close();
228 mMediaProvider.close();
229 native_finalize();
230 }
231 }
232
233 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400234 protected void finalize() throws Throwable {
235 try {
Narayan Kamath492e9e82017-03-22 14:28:08 +0000236 if (mCloseGuard != null) {
237 mCloseGuard.warnIfOpen();
238 }
239
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700240 close();
Mike Lockwooddbead322010-08-30 09:27:55 -0400241 } finally {
242 super.finalize();
243 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400244 }
245
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400246 public void addStorage(MtpStorage storage) {
247 mStorageMap.put(storage.getPath(), storage);
248 }
249
250 public void removeStorage(MtpStorage storage) {
251 mStorageMap.remove(storage.getPath());
252 }
253
Mike Lockwood775de952011-03-05 17:34:11 -0500254 private void initDeviceProperties(Context context) {
255 final String devicePropertiesName = "device-properties";
256 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
257 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400258
Mike Lockwood775de952011-03-05 17:34:11 -0500259 if (databaseFile.exists()) {
260 // for backward compatibility - read device properties from sqlite database
261 // and migrate them to shared prefs
262 SQLiteDatabase db = null;
263 Cursor c = null;
264 try {
265 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
266 if (db != null) {
267 c = db.query("properties", new String[] { "_id", "code", "value" },
268 null, null, null, null, null);
269 if (c != null) {
270 SharedPreferences.Editor e = mDeviceProperties.edit();
271 while (c.moveToNext()) {
272 String name = c.getString(1);
273 String value = c.getString(2);
274 e.putString(name, value);
275 }
276 e.commit();
277 }
278 }
279 } catch (Exception e) {
280 Log.e(TAG, "failed to migrate device properties", e);
281 } finally {
282 if (c != null) c.close();
283 if (db != null) db.close();
284 }
jangwon.lee3ed02532013-07-22 19:52:48 +0900285 context.deleteDatabase(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400286 }
287 }
288
Mike Lockwood73e56d92011-12-01 16:58:41 -0500289 // check to see if the path is contained in one of our storage subdirectories
290 // returns true if we have no special subdirectories
291 private boolean inStorageSubDirectory(String path) {
292 if (mSubDirectories == null) return true;
293 if (path == null) return false;
294
295 boolean allowed = false;
296 int pathLength = path.length();
297 for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
298 String subdir = mSubDirectories[i];
299 int subdirLength = subdir.length();
300 if (subdirLength < pathLength &&
301 path.charAt(subdirLength) == '/' &&
302 path.startsWith(subdir)) {
303 allowed = true;
304 }
305 }
306 return allowed;
307 }
308
309 // check to see if the path matches one of our storage subdirectories
310 // returns true if we have no special subdirectories
311 private boolean isStorageSubDirectory(String path) {
312 if (mSubDirectories == null) return false;
313 for (int i = 0; i < mSubDirectories.length; i++) {
314 if (path.equals(mSubDirectories[i])) {
315 return true;
316 }
317 }
318 return false;
319 }
320
Marco Nelissen5f411692014-09-26 16:03:49 -0700321 // returns true if the path is in the storage root
322 private boolean inStorageRoot(String path) {
323 try {
324 File f = new File(path);
325 String canonical = f.getCanonicalPath();
Marco Nelissenc1fda122014-10-15 14:32:22 -0700326 for (String root: mStorageMap.keySet()) {
327 if (canonical.startsWith(root)) {
328 return true;
329 }
Marco Nelissen5f411692014-09-26 16:03:49 -0700330 }
331 } catch (IOException e) {
332 // ignore
333 }
334 return false;
335 }
336
Mike Lockwoodd815f792010-07-12 08:49:01 -0400337 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400338 int storageId, long size, long modified) {
Marco Nelissen5f411692014-09-26 16:03:49 -0700339 // if the path is outside of the storage root, do not allow access
340 if (!inStorageRoot(path)) {
341 Log.e(TAG, "attempt to put file outside of storage area: " + path);
342 return -1;
343 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500344 // if mSubDirectories is not null, do not allow copying files to any other locations
345 if (!inStorageSubDirectory(path)) return -1;
346
347 // make sure the object does not exist
Mike Lockwoodbafca212010-12-13 21:50:09 -0800348 if (path != null) {
349 Cursor c = null;
350 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700351 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800352 new String[] { path }, null, null);
Mike Lockwoodbafca212010-12-13 21:50:09 -0800353 if (c != null && c.getCount() > 0) {
354 Log.w(TAG, "file already exists in beginSendObject: " + path);
355 return -1;
356 }
357 } catch (RemoteException e) {
358 Log.e(TAG, "RemoteException in beginSendObject", e);
359 } finally {
360 if (c != null) {
361 c.close();
362 }
363 }
364 }
365
Mike Lockwood2837eef2010-08-31 16:25:12 -0400366 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400367 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400368 values.put(Files.FileColumns.DATA, path);
369 values.put(Files.FileColumns.FORMAT, format);
370 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400371 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400372 values.put(Files.FileColumns.SIZE, size);
373 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400374
375 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700376 Uri uri = mMediaProvider.insert(mObjectsUri, values);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400377 if (uri != null) {
378 return Integer.parseInt(uri.getPathSegments().get(2));
379 } else {
380 return -1;
381 }
382 } catch (RemoteException e) {
383 Log.e(TAG, "RemoteException in beginSendObject", e);
384 return -1;
385 }
386 }
387
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800388 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400389 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400390 // handle abstract playlists separately
391 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400392 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400393 // extract name from path
394 String name = path;
395 int lastSlash = name.lastIndexOf('/');
396 if (lastSlash >= 0) {
397 name = name.substring(lastSlash + 1);
398 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800399 // strip trailing ".pla" from the name
400 if (name.endsWith(".pla")) {
401 name = name.substring(0, name.length() - 4);
402 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400403
404 ContentValues values = new ContentValues(1);
405 values.put(Audio.Playlists.DATA, path);
406 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500407 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800408 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400409 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
410 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700411 Uri uri = mMediaProvider.insert(
Dianne Hackborn35654b62013-01-14 17:38:02 -0800412 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400413 } catch (RemoteException e) {
414 Log.e(TAG, "RemoteException in endSendObject", e);
415 }
416 } else {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700417 mMediaScanner.scanMtpFile(path, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400418 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400419 } else {
420 deleteFile(handle);
421 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400422 }
423
kyle_tsob4aa69f2017-11-22 20:11:27 +0800424 private void doScanDirectory(String path) {
425 String[] scanPath;
426 scanPath = new String[] { path };
427 mMediaScanner.scanDirectories(scanPath);
428 }
429
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400430 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500431 String where;
432 String[] whereArgs;
433
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400434 if (storageID == 0xFFFFFFFF) {
435 // query all stores
436 if (format == 0) {
437 // query all formats
438 if (parent == 0) {
439 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500440 where = null;
441 whereArgs = null;
442 } else {
443 if (parent == 0xFFFFFFFF) {
444 // all objects in root of store
445 parent = 0;
446 }
447 where = PARENT_WHERE;
448 whereArgs = new String[] { Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400449 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400450 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400451 // query specific format
452 if (parent == 0) {
453 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500454 where = FORMAT_WHERE;
455 whereArgs = new String[] { Integer.toString(format) };
456 } else {
457 if (parent == 0xFFFFFFFF) {
458 // all objects in root of store
459 parent = 0;
460 }
461 where = FORMAT_PARENT_WHERE;
462 whereArgs = new String[] { Integer.toString(format),
463 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400464 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400465 }
466 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400467 // query specific store
468 if (format == 0) {
469 // query all formats
470 if (parent == 0) {
471 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500472 where = STORAGE_WHERE;
473 whereArgs = new String[] { Integer.toString(storageID) };
474 } else {
475 if (parent == 0xFFFFFFFF) {
476 // all objects in root of store
477 parent = 0;
Jerry Zhangdef7b1932017-10-17 13:47:51 -0700478 where = STORAGE_PARENT_WHERE;
479 whereArgs = new String[]{Integer.toString(storageID),
480 Integer.toString(parent)};
481 } else {
482 // If a parent is specified, the storage is redundant
483 where = PARENT_WHERE;
484 whereArgs = new String[]{Integer.toString(parent)};
Mike Lockwood73e56d92011-12-01 16:58:41 -0500485 }
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400486 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400487 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400488 // query specific format
489 if (parent == 0) {
490 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500491 where = STORAGE_FORMAT_WHERE;
492 whereArgs = new String[] { Integer.toString(storageID),
493 Integer.toString(format) };
494 } else {
495 if (parent == 0xFFFFFFFF) {
496 // all objects in root of store
497 parent = 0;
Jerry Zhangdef7b1932017-10-17 13:47:51 -0700498 where = STORAGE_FORMAT_PARENT_WHERE;
499 whereArgs = new String[]{Integer.toString(storageID),
500 Integer.toString(format),
501 Integer.toString(parent)};
502 } else {
503 // If a parent is specified, the storage is redundant
504 where = FORMAT_PARENT_WHERE;
505 whereArgs = new String[]{Integer.toString(format),
506 Integer.toString(parent)};
Mike Lockwood73e56d92011-12-01 16:58:41 -0500507 }
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400508 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400509 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400510 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500511
512 // if we are restricting queries to mSubDirectories, we need to add the restriction
513 // onto our "where" arguments
514 if (mSubDirectoriesWhere != null) {
515 if (where == null) {
516 where = mSubDirectoriesWhere;
517 whereArgs = mSubDirectoriesWhereArgs;
518 } else {
519 where = where + " AND " + mSubDirectoriesWhere;
520
521 // create new array to hold whereArgs and mSubDirectoriesWhereArgs
522 String[] newWhereArgs =
523 new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
524 int i, j;
525 for (i = 0; i < whereArgs.length; i++) {
526 newWhereArgs[i] = whereArgs[i];
527 }
528 for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
529 newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
530 }
531 whereArgs = newWhereArgs;
532 }
533 }
534
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700535 return mMediaProvider.query(mObjectsUri, ID_PROJECTION, where,
Dianne Hackborn35654b62013-01-14 17:38:02 -0800536 whereArgs, null, null);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400537 }
538
539 private int[] getObjectList(int storageID, int format, int parent) {
540 Cursor c = null;
541 try {
542 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400543 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400544 return null;
545 }
546 int count = c.getCount();
547 if (count > 0) {
548 int[] result = new int[count];
549 for (int i = 0; i < count; i++) {
550 c.moveToNext();
551 result[i] = c.getInt(0);
552 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400553 return result;
554 }
555 } catch (RemoteException e) {
556 Log.e(TAG, "RemoteException in getObjectList", e);
557 } finally {
558 if (c != null) {
559 c.close();
560 }
561 }
562 return null;
563 }
564
Mike Lockwood7a047c82010-08-02 10:52:20 -0400565 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400566 Cursor c = null;
567 try {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400568 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400569 if (c != null) {
570 return c.getCount();
571 }
572 } catch (RemoteException e) {
573 Log.e(TAG, "RemoteException in getNumObjects", e);
574 } finally {
575 if (c != null) {
576 c.close();
577 }
578 }
579 return -1;
580 }
581
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400582 private int[] getSupportedPlaybackFormats() {
583 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400584 // allow transfering arbitrary files
585 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400586
Mike Lockwood792ec842010-09-09 15:30:10 -0400587 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400588 MtpConstants.FORMAT_TEXT,
589 MtpConstants.FORMAT_HTML,
590 MtpConstants.FORMAT_WAV,
591 MtpConstants.FORMAT_MP3,
592 MtpConstants.FORMAT_MPEG,
593 MtpConstants.FORMAT_EXIF_JPEG,
594 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800595 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400596 MtpConstants.FORMAT_GIF,
597 MtpConstants.FORMAT_JFIF,
598 MtpConstants.FORMAT_PNG,
599 MtpConstants.FORMAT_TIFF,
600 MtpConstants.FORMAT_WMA,
601 MtpConstants.FORMAT_OGG,
602 MtpConstants.FORMAT_AAC,
603 MtpConstants.FORMAT_MP4_CONTAINER,
604 MtpConstants.FORMAT_MP2,
605 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400606 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400607 MtpConstants.FORMAT_WPL_PLAYLIST,
608 MtpConstants.FORMAT_M3U_PLAYLIST,
609 MtpConstants.FORMAT_PLS_PLAYLIST,
610 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800611 MtpConstants.FORMAT_FLAC,
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100612 MtpConstants.FORMAT_DNG,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400613 };
614 }
615
616 private int[] getSupportedCaptureFormats() {
617 // no capture formats yet
618 return null;
619 }
620
Mike Lockwoodae078f72010-09-26 12:35:51 -0400621 static final int[] FILE_PROPERTIES = {
622 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
623 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400624 MtpConstants.PROPERTY_STORAGE_ID,
625 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400626 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400627 MtpConstants.PROPERTY_OBJECT_SIZE,
628 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400629 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400630 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400631 MtpConstants.PROPERTY_PERSISTENT_UID,
632 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800633 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400634 MtpConstants.PROPERTY_DATE_ADDED,
635 };
636
637 static final int[] AUDIO_PROPERTIES = {
638 // NOTE must match FILE_PROPERTIES above
639 MtpConstants.PROPERTY_STORAGE_ID,
640 MtpConstants.PROPERTY_OBJECT_FORMAT,
641 MtpConstants.PROPERTY_PROTECTION_STATUS,
642 MtpConstants.PROPERTY_OBJECT_SIZE,
643 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
644 MtpConstants.PROPERTY_DATE_MODIFIED,
645 MtpConstants.PROPERTY_PARENT_OBJECT,
646 MtpConstants.PROPERTY_PERSISTENT_UID,
647 MtpConstants.PROPERTY_NAME,
648 MtpConstants.PROPERTY_DISPLAY_NAME,
649 MtpConstants.PROPERTY_DATE_ADDED,
650
651 // audio specific properties
652 MtpConstants.PROPERTY_ARTIST,
653 MtpConstants.PROPERTY_ALBUM_NAME,
654 MtpConstants.PROPERTY_ALBUM_ARTIST,
655 MtpConstants.PROPERTY_TRACK,
656 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
657 MtpConstants.PROPERTY_DURATION,
658 MtpConstants.PROPERTY_GENRE,
659 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700660 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
661 MtpConstants.PROPERTY_BITRATE_TYPE,
662 MtpConstants.PROPERTY_AUDIO_BITRATE,
663 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
664 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400665 };
666
667 static final int[] VIDEO_PROPERTIES = {
668 // NOTE must match FILE_PROPERTIES above
669 MtpConstants.PROPERTY_STORAGE_ID,
670 MtpConstants.PROPERTY_OBJECT_FORMAT,
671 MtpConstants.PROPERTY_PROTECTION_STATUS,
672 MtpConstants.PROPERTY_OBJECT_SIZE,
673 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
674 MtpConstants.PROPERTY_DATE_MODIFIED,
675 MtpConstants.PROPERTY_PARENT_OBJECT,
676 MtpConstants.PROPERTY_PERSISTENT_UID,
677 MtpConstants.PROPERTY_NAME,
678 MtpConstants.PROPERTY_DISPLAY_NAME,
679 MtpConstants.PROPERTY_DATE_ADDED,
680
681 // video specific properties
682 MtpConstants.PROPERTY_ARTIST,
683 MtpConstants.PROPERTY_ALBUM_NAME,
684 MtpConstants.PROPERTY_DURATION,
685 MtpConstants.PROPERTY_DESCRIPTION,
686 };
687
688 static final int[] IMAGE_PROPERTIES = {
689 // NOTE must match FILE_PROPERTIES above
690 MtpConstants.PROPERTY_STORAGE_ID,
691 MtpConstants.PROPERTY_OBJECT_FORMAT,
692 MtpConstants.PROPERTY_PROTECTION_STATUS,
693 MtpConstants.PROPERTY_OBJECT_SIZE,
694 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
695 MtpConstants.PROPERTY_DATE_MODIFIED,
696 MtpConstants.PROPERTY_PARENT_OBJECT,
697 MtpConstants.PROPERTY_PERSISTENT_UID,
698 MtpConstants.PROPERTY_NAME,
699 MtpConstants.PROPERTY_DISPLAY_NAME,
700 MtpConstants.PROPERTY_DATE_ADDED,
701
702 // image specific properties
703 MtpConstants.PROPERTY_DESCRIPTION,
704 };
705
706 private int[] getSupportedObjectProperties(int format) {
707 switch (format) {
708 case MtpConstants.FORMAT_MP3:
709 case MtpConstants.FORMAT_WAV:
710 case MtpConstants.FORMAT_WMA:
711 case MtpConstants.FORMAT_OGG:
712 case MtpConstants.FORMAT_AAC:
713 return AUDIO_PROPERTIES;
714 case MtpConstants.FORMAT_MPEG:
715 case MtpConstants.FORMAT_3GP_CONTAINER:
716 case MtpConstants.FORMAT_WMV:
717 return VIDEO_PROPERTIES;
718 case MtpConstants.FORMAT_EXIF_JPEG:
719 case MtpConstants.FORMAT_GIF:
720 case MtpConstants.FORMAT_PNG:
721 case MtpConstants.FORMAT_BMP:
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100722 case MtpConstants.FORMAT_DNG:
Mike Lockwoodae078f72010-09-26 12:35:51 -0400723 return IMAGE_PROPERTIES;
724 default:
725 return FILE_PROPERTIES;
726 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400727 }
728
729 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400730 return new int[] {
731 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
732 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800733 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood56c85242014-03-07 13:29:08 -0800734 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800735 MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400736 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400737 }
738
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900739 private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400740 int groupCode, int depth) {
741 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400742 if (groupCode != 0) {
743 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
744 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400745
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500746 MtpPropertyGroup propertyGroup;
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900747 if (property == 0xffffffff) {
748 if (format == 0 && handle != 0 && handle != 0xffffffff) {
Mike Lockwood71827742015-01-23 10:50:08 -0800749 // return properties based on the object's format
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900750 format = getObjectFormat(handle);
Mike Lockwood71827742015-01-23 10:50:08 -0800751 }
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900752 propertyGroup = mPropertyGroupsByFormat.get(format);
753 if (propertyGroup == null) {
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500754 int[] propertyList = getSupportedObjectProperties(format);
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700755 propertyGroup = new MtpPropertyGroup(this, mMediaProvider,
Dianne Hackborn35654b62013-01-14 17:38:02 -0800756 mVolumeName, propertyList);
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900757 mPropertyGroupsByFormat.put(format, propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400758 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500759 } else {
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900760 propertyGroup = mPropertyGroupsByProperty.get(property);
761 if (propertyGroup == null) {
762 final int[] propertyList = new int[] { property };
763 propertyGroup = new MtpPropertyGroup(
764 this, mMediaProvider, mVolumeName, propertyList);
765 mPropertyGroupsByProperty.put(property, propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400766 }
767 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500768
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900769 return propertyGroup.getPropertyList(handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400770 }
771
Mike Lockwood5ebac832010-10-12 11:33:47 -0400772 private int renameFile(int handle, String newName) {
773 Cursor c = null;
774
775 // first compute current path
776 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400777 String[] whereArgs = new String[] { Integer.toString(handle) };
778 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700779 c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE,
Dianne Hackborn35654b62013-01-14 17:38:02 -0800780 whereArgs, null, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400781 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500782 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400783 }
784 } catch (RemoteException e) {
785 Log.e(TAG, "RemoteException in getObjectFilePath", e);
786 return MtpConstants.RESPONSE_GENERAL_ERROR;
787 } finally {
788 if (c != null) {
789 c.close();
790 }
791 }
792 if (path == null) {
793 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
794 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400795
Mike Lockwood73e56d92011-12-01 16:58:41 -0500796 // do not allow renaming any of the special subdirectories
797 if (isStorageSubDirectory(path)) {
798 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
799 }
800
Mike Lockwood5ebac832010-10-12 11:33:47 -0400801 // now rename the file. make sure this succeeds before updating database
802 File oldFile = new File(path);
803 int lastSlash = path.lastIndexOf('/');
804 if (lastSlash <= 1) {
805 return MtpConstants.RESPONSE_GENERAL_ERROR;
806 }
807 String newPath = path.substring(0, lastSlash + 1) + newName;
808 File newFile = new File(newPath);
809 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400810 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800811 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400812 return MtpConstants.RESPONSE_GENERAL_ERROR;
813 }
814
815 // finally update database
816 ContentValues values = new ContentValues();
817 values.put(Files.FileColumns.DATA, newPath);
818 int updated = 0;
819 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400820 // note - we are relying on a special case in MediaProvider.update() to update
821 // the paths for all children in the case where this is a directory.
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700822 updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400823 } catch (RemoteException e) {
824 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
825 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400826 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400827 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
828 // this shouldn't happen, but if it does we need to rename the file to its original name
829 newFile.renameTo(oldFile);
830 return MtpConstants.RESPONSE_GENERAL_ERROR;
831 }
832
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800833 // check if nomedia status changed
834 if (newFile.isDirectory()) {
835 // for directories, check if renamed from something hidden to something non-hidden
836 if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
837 // directory was unhidden
838 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700839 mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800840 } catch (RemoteException e) {
841 Log.e(TAG, "failed to unhide/rescan for " + newPath);
842 }
843 }
844 } else {
845 // for files, check if renamed from .nomedia to something else
846 if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
847 && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
848 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700849 mMediaProvider.call(MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800850 } catch (RemoteException e) {
851 Log.e(TAG, "failed to unhide/rescan for " + newPath);
852 }
853 }
854 }
855
Mike Lockwood5ebac832010-10-12 11:33:47 -0400856 return MtpConstants.RESPONSE_OK;
857 }
858
Jerry Zhangdef7b1932017-10-17 13:47:51 -0700859 private int moveObject(int handle, int newParent, int newStorage, String newPath) {
Jerry Zhang952558d42017-09-26 17:49:52 -0700860 String[] whereArgs = new String[] { Integer.toString(handle) };
861
862 // do not allow renaming any of the special subdirectories
863 if (isStorageSubDirectory(newPath)) {
864 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
865 }
866
867 // update database
868 ContentValues values = new ContentValues();
869 values.put(Files.FileColumns.DATA, newPath);
870 values.put(Files.FileColumns.PARENT, newParent);
Jerry Zhangdef7b1932017-10-17 13:47:51 -0700871 values.put(Files.FileColumns.STORAGE_ID, newStorage);
Jerry Zhang952558d42017-09-26 17:49:52 -0700872 int updated = 0;
873 try {
874 // note - we are relying on a special case in MediaProvider.update() to update
875 // the paths for all children in the case where this is a directory.
876 updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
877 } catch (RemoteException e) {
878 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
879 }
880 if (updated == 0) {
881 Log.e(TAG, "Unable to update path for " + handle + " to " + newPath);
882 return MtpConstants.RESPONSE_GENERAL_ERROR;
883 }
884 return MtpConstants.RESPONSE_OK;
885 }
886
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400887 private int setObjectProperty(int handle, int property,
888 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400889 switch (property) {
890 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
891 return renameFile(handle, stringValue);
892
893 default:
894 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
895 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400896 }
897
898 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400899 switch (property) {
900 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
901 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500902 // writable string properties kept in shared preferences
903 String value = mDeviceProperties.getString(Integer.toString(property), "");
904 int length = value.length();
905 if (length > 255) {
906 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400907 }
Mike Lockwood775de952011-03-05 17:34:11 -0500908 value.getChars(0, length, outStringValue, 0);
909 outStringValue[length] = 0;
910 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400911
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800912 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
913 // use screen size as max image size
914 Display display = ((WindowManager)mContext.getSystemService(
915 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700916 int width = display.getMaximumSizeDimension();
917 int height = display.getMaximumSizeDimension();
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800918 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
919 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
920 outStringValue[imageSize.length()] = 0;
921 return MtpConstants.RESPONSE_OK;
922
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800923 case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
924 outIntValue[0] = mDeviceType;
925 return MtpConstants.RESPONSE_OK;
926
Mike Lockwood56c85242014-03-07 13:29:08 -0800927 // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
928
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800929 default:
930 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
931 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400932 }
933
934 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400935 switch (property) {
936 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
937 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500938 // writable string properties kept in shared prefs
939 SharedPreferences.Editor e = mDeviceProperties.edit();
940 e.putString(Integer.toString(property), stringValue);
941 return (e.commit() ? MtpConstants.RESPONSE_OK
942 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400943 }
944
945 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
946 }
947
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400948 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700949 char[] outName, long[] outCreatedModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400950 Cursor c = null;
951 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700952 c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800953 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400954 if (c != null && c.moveToNext()) {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400955 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400956 outStorageFormatParent[1] = c.getInt(2);
957 outStorageFormatParent[2] = c.getInt(3);
958
959 // extract name from path
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400960 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400961 int lastSlash = path.lastIndexOf('/');
962 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
963 int end = path.length();
964 if (end - start > 255) {
965 end = start + 255;
966 }
967 path.getChars(start, end, outName, 0);
968 outName[end - start] = 0;
969
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700970 outCreatedModified[0] = c.getLong(5);
971 outCreatedModified[1] = c.getLong(6);
972 // use modification date as creation date if date added is not set
973 if (outCreatedModified[0] == 0) {
974 outCreatedModified[0] = outCreatedModified[1];
975 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400976 return true;
977 }
978 } catch (RemoteException e) {
Mike Lockwood2b5f9ad12010-10-29 19:16:27 -0400979 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400980 } finally {
981 if (c != null) {
982 c.close();
983 }
984 }
985 return false;
986 }
987
Mike Lockwood365e03e2010-12-08 16:08:01 -0800988 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400989 if (handle == 0) {
990 // special case root directory
991 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
992 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800993 outFileLengthFormat[0] = 0;
994 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400995 return MtpConstants.RESPONSE_OK;
996 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400997 Cursor c = null;
998 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700999 c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -08001000 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001001 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -05001002 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001003 path.getChars(0, path.length(), outFilePath, 0);
1004 outFilePath[path.length()] = 0;
Mike Lockwoodf6f16612012-09-12 15:50:59 -07001005 // File transfers from device to host will likely fail if the size is incorrect.
1006 // So to be safe, use the actual file size here.
1007 outFileLengthFormat[0] = new File(path).length();
1008 outFileLengthFormat[1] = c.getLong(2);
Mike Lockwood5367ab62010-08-30 13:23:02 -04001009 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001010 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001011 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001012 }
1013 } catch (RemoteException e) {
1014 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -04001015 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001016 } finally {
1017 if (c != null) {
1018 c.close();
1019 }
1020 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001021 }
1022
Mike Lockwood71827742015-01-23 10:50:08 -08001023 private int getObjectFormat(int handle) {
1024 Cursor c = null;
1025 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001026 c = mMediaProvider.query(mObjectsUri, FORMAT_PROJECTION,
Daichi Hirono486ad2e2016-02-29 17:28:47 +09001027 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood71827742015-01-23 10:50:08 -08001028 if (c != null && c.moveToNext()) {
1029 return c.getInt(1);
1030 } else {
1031 return -1;
1032 }
1033 } catch (RemoteException e) {
1034 Log.e(TAG, "RemoteException in getObjectFilePath", e);
1035 return -1;
1036 } finally {
1037 if (c != null) {
1038 c.close();
1039 }
1040 }
1041 }
1042
Mike Lockwood59c777a2010-08-02 10:37:41 -04001043 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001044 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -08001045 String path = null;
1046 int format = 0;
1047
1048 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001049 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001050 c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -08001051 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -08001052 if (c != null && c.moveToNext()) {
1053 // don't convert to media path here, since we will be matching
1054 // against paths in the database matching /data/media
1055 path = c.getString(1);
Mike Lockwoodf6f16612012-09-12 15:50:59 -07001056 format = c.getInt(2);
Mike Lockwood55f808c2010-12-14 13:14:29 -08001057 } else {
1058 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
1059 }
1060
1061 if (path == null || format == 0) {
1062 return MtpConstants.RESPONSE_GENERAL_ERROR;
1063 }
1064
Mike Lockwood73e56d92011-12-01 16:58:41 -05001065 // do not allow deleting any of the special subdirectories
1066 if (isStorageSubDirectory(path)) {
1067 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
1068 }
1069
Mike Lockwood55f808c2010-12-14 13:14:29 -08001070 if (format == MtpConstants.FORMAT_ASSOCIATION) {
1071 // recursive case - delete all children first
1072 Uri uri = Files.getMtpObjectsUri(mVolumeName);
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001073 int count = mMediaProvider.delete(uri,
Mike Lockwood1e855d92012-06-26 16:31:41 -07001074 // the 'like' makes it use the index, the 'lower()' makes it correct
1075 // when the path contains sqlite wildcard characters
1076 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
1077 new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -08001078 }
1079
1080 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001081 if (mMediaProvider.delete(uri, null, null) > 0) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001082 if (format != MtpConstants.FORMAT_ASSOCIATION
1083 && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
1084 try {
1085 String parentPath = path.substring(0, path.lastIndexOf("/"));
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001086 mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -08001087 } catch (RemoteException e) {
1088 Log.e(TAG, "failed to unhide/rescan for " + path);
1089 }
1090 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001091 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001092 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001093 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -04001094 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001095 } catch (RemoteException e) {
1096 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -04001097 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -08001098 } finally {
1099 if (c != null) {
1100 c.close();
1101 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001102 }
1103 }
1104
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001105 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -04001106 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001107 Cursor c = null;
1108 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001109 c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001110 if (c == null) {
1111 return null;
1112 }
1113 int count = c.getCount();
1114 if (count > 0) {
1115 int[] result = new int[count];
1116 for (int i = 0; i < count; i++) {
1117 c.moveToNext();
1118 result[i] = c.getInt(0);
1119 }
1120 return result;
1121 }
1122 } catch (RemoteException e) {
1123 Log.e(TAG, "RemoteException in getObjectList", e);
1124 } finally {
1125 if (c != null) {
1126 c.close();
1127 }
1128 }
1129 return null;
1130 }
1131
1132 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001133 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -04001134 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001135 int count = references.length;
1136 ContentValues[] valuesList = new ContentValues[count];
1137 for (int i = 0; i < count; i++) {
1138 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -04001139 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001140 valuesList[i] = values;
1141 }
1142 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -07001143 if (mMediaProvider.bulkInsert(uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001144 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001145 }
1146 } catch (RemoteException e) {
1147 Log.e(TAG, "RemoteException in setObjectReferences", e);
1148 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001149 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001150 }
1151
Mike Lockwood2837eef2010-08-31 16:25:12 -04001152 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001153 mDatabaseModified = false;
1154 }
1155
1156 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001157 if (mDatabaseModified) {
Mike Lockwooda3156052010-11-20 12:28:27 -05001158 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -04001159 mDatabaseModified = false;
1160 }
1161 }
1162
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001163 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +00001164 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001165
1166 private native final void native_setup();
1167 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001168}