blob: 1244d7f040b428eebf95582b7c67f895089b1eb4 [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;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070033import android.os.storage.StorageVolume;
Mike Lockwooda3156052010-11-20 12:28:27 -050034import android.provider.MediaStore;
Mike Lockwood9a2046f2010-08-03 15:30:09 -040035import android.provider.MediaStore.Audio;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040036import android.provider.MediaStore.Files;
Mike Lockwoodae078f72010-09-26 12:35:51 -040037import android.provider.MediaStore.MediaColumns;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040038import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080039import android.view.Display;
40import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040041
Jeff Sharkey60cfad82016-01-05 17:30:57 -070042import dalvik.system.CloseGuard;
43
Jerry Zhangf9c5c252017-08-16 18:07:51 -070044import com.google.android.collect.Sets;
45
Mike Lockwood5ebac832010-10-12 11:33:47 -040046import java.io.File;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070047import java.nio.file.Path;
48import java.nio.file.Paths;
49import java.util.ArrayList;
50import java.util.Arrays;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050051import java.util.HashMap;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070052import java.util.Iterator;
dujin.chafe464a72011-11-22 12:13:33 +090053import java.util.Locale;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070054import java.util.concurrent.atomic.AtomicBoolean;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070055import java.util.stream.IntStream;
56import java.util.stream.Stream;
Mike Lockwood5ebac832010-10-12 11:33:47 -040057
Mike Lockwoodd21eac92010-07-03 00:44:05 -040058/**
Jerry Zhangf9c5c252017-08-16 18:07:51 -070059 * MtpDatabase provides an interface for MTP operations that MtpServer can use. To do this, it uses
60 * MtpStorageManager for filesystem operations and MediaProvider to get media metadata. File
61 * operations are also reflected in MediaProvider if possible.
62 * operations
Mike Lockwoodd21eac92010-07-03 00:44:05 -040063 * {@hide}
64 */
Jeff Sharkey60cfad82016-01-05 17:30:57 -070065public class MtpDatabase implements AutoCloseable {
Jerry Zhangf9c5c252017-08-16 18:07:51 -070066 private static final String TAG = MtpDatabase.class.getSimpleName();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040067
Mike Lockwood2837eef2010-08-31 16:25:12 -040068 private final Context mContext;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070069 private final ContentProviderClient mMediaProvider;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040070 private final String mVolumeName;
71 private final Uri mObjectsUri;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070072 private final MediaScanner mMediaScanner;
73
74 private final AtomicBoolean mClosed = new AtomicBoolean();
75 private final CloseGuard mCloseGuard = CloseGuard.get();
76
Jerry Zhangf9c5c252017-08-16 18:07:51 -070077 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040078
Mike Lockwood7d7fb632010-12-01 18:46:23 -050079 // cached property groups for single properties
Jerry Zhangf9c5c252017-08-16 18:07:51 -070080 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty = new HashMap<>();
Mike Lockwood7d7fb632010-12-01 18:46:23 -050081
82 // cached property groups for all properties for a given format
Jerry Zhangf9c5c252017-08-16 18:07:51 -070083 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat = new HashMap<>();
Mike Lockwood2837eef2010-08-31 16:25:12 -040084
Mike Lockwood775de952011-03-05 17:34:11 -050085 // SharedPreferences for writable MTP device properties
86 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040087
Jerry Zhangf9c5c252017-08-16 18:07:51 -070088 // Cached device properties
Mike Lockwood56c85242014-03-07 13:29:08 -080089 private int mBatteryLevel;
90 private int mBatteryScale;
Jerry Zhang13bb2f42016-12-14 15:39:29 -080091 private int mDeviceType;
92
Jerry Zhangf9c5c252017-08-16 18:07:51 -070093 private MtpServer mServer;
94 private MtpStorageManager mManager;
95
96 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
97 private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
98 private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
99 private static final String NO_MEDIA = ".nomedia";
100
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400101 static {
102 System.loadLibrary("media_jni");
103 }
104
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700105 private static final int[] PLAYBACK_FORMATS = {
106 // allow transferring arbitrary files
Mike Lockwoode5211692010-09-08 13:50:45 -0400107 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400108
Mike Lockwood792ec842010-09-09 15:30:10 -0400109 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400110 MtpConstants.FORMAT_TEXT,
111 MtpConstants.FORMAT_HTML,
112 MtpConstants.FORMAT_WAV,
113 MtpConstants.FORMAT_MP3,
114 MtpConstants.FORMAT_MPEG,
115 MtpConstants.FORMAT_EXIF_JPEG,
116 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800117 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400118 MtpConstants.FORMAT_GIF,
119 MtpConstants.FORMAT_JFIF,
120 MtpConstants.FORMAT_PNG,
121 MtpConstants.FORMAT_TIFF,
122 MtpConstants.FORMAT_WMA,
123 MtpConstants.FORMAT_OGG,
124 MtpConstants.FORMAT_AAC,
125 MtpConstants.FORMAT_MP4_CONTAINER,
126 MtpConstants.FORMAT_MP2,
127 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400128 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400129 MtpConstants.FORMAT_WPL_PLAYLIST,
130 MtpConstants.FORMAT_M3U_PLAYLIST,
131 MtpConstants.FORMAT_PLS_PLAYLIST,
132 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800133 MtpConstants.FORMAT_FLAC,
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100134 MtpConstants.FORMAT_DNG,
Chong Zhang6e18cce2017-08-16 11:57:02 -0700135 MtpConstants.FORMAT_HEIF,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700136 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400137
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700138 private static final int[] FILE_PROPERTIES = {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400139 MtpConstants.PROPERTY_STORAGE_ID,
140 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400141 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400142 MtpConstants.PROPERTY_OBJECT_SIZE,
143 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400144 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400145 MtpConstants.PROPERTY_PERSISTENT_UID,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700146 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400147 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800148 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400149 MtpConstants.PROPERTY_DATE_ADDED,
150 };
151
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700152 private static final int[] AUDIO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400153 MtpConstants.PROPERTY_ARTIST,
154 MtpConstants.PROPERTY_ALBUM_NAME,
155 MtpConstants.PROPERTY_ALBUM_ARTIST,
156 MtpConstants.PROPERTY_TRACK,
157 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
158 MtpConstants.PROPERTY_DURATION,
159 MtpConstants.PROPERTY_GENRE,
160 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700161 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
162 MtpConstants.PROPERTY_BITRATE_TYPE,
163 MtpConstants.PROPERTY_AUDIO_BITRATE,
164 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
165 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400166 };
167
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700168 private static final int[] VIDEO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400169 MtpConstants.PROPERTY_ARTIST,
170 MtpConstants.PROPERTY_ALBUM_NAME,
171 MtpConstants.PROPERTY_DURATION,
172 MtpConstants.PROPERTY_DESCRIPTION,
173 };
174
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700175 private static final int[] IMAGE_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400176 MtpConstants.PROPERTY_DESCRIPTION,
177 };
178
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700179 private static final int[] DEVICE_PROPERTIES = {
180 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
181 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
182 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
183 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
184 MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
185 };
186
Mike Lockwoodae078f72010-09-26 12:35:51 -0400187 private int[] getSupportedObjectProperties(int format) {
188 switch (format) {
189 case MtpConstants.FORMAT_MP3:
190 case MtpConstants.FORMAT_WAV:
191 case MtpConstants.FORMAT_WMA:
192 case MtpConstants.FORMAT_OGG:
193 case MtpConstants.FORMAT_AAC:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700194 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
195 Arrays.stream(AUDIO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400196 case MtpConstants.FORMAT_MPEG:
197 case MtpConstants.FORMAT_3GP_CONTAINER:
198 case MtpConstants.FORMAT_WMV:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700199 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
200 Arrays.stream(VIDEO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400201 case MtpConstants.FORMAT_EXIF_JPEG:
202 case MtpConstants.FORMAT_GIF:
203 case MtpConstants.FORMAT_PNG:
204 case MtpConstants.FORMAT_BMP:
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100205 case MtpConstants.FORMAT_DNG:
Chong Zhang6e18cce2017-08-16 11:57:02 -0700206 case MtpConstants.FORMAT_HEIF:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700207 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
208 Arrays.stream(IMAGE_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400209 default:
210 return FILE_PROPERTIES;
211 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400212 }
213
214 private int[] getSupportedDeviceProperties() {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700215 return DEVICE_PROPERTIES;
216 }
217
218 private int[] getSupportedPlaybackFormats() {
219 return PLAYBACK_FORMATS;
220 }
221
222 private int[] getSupportedCaptureFormats() {
223 // no capture formats yet
224 return null;
225 }
226
227 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
228 @Override
229 public void onReceive(Context context, Intent intent) {
230 String action = intent.getAction();
231 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
232 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
233 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
234 if (newLevel != mBatteryLevel) {
235 mBatteryLevel = newLevel;
236 if (mServer != null) {
237 // send device property changed event
238 mServer.sendDevicePropertyChanged(
239 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
240 }
241 }
242 }
243 }
244 };
245
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800246 public MtpDatabase(Context context, String volumeName,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700247 String[] subDirectories) {
248 native_setup();
249 mContext = context;
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800250 mMediaProvider = context.getContentResolver()
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700251 .acquireContentProviderClient(MediaStore.AUTHORITY);
252 mVolumeName = volumeName;
253 mObjectsUri = Files.getMtpObjectsUri(volumeName);
254 mMediaScanner = new MediaScanner(context, mVolumeName);
255 mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
256 @Override
257 public void sendObjectAdded(int id) {
258 if (MtpDatabase.this.mServer != null)
259 MtpDatabase.this.mServer.sendObjectAdded(id);
260 }
261
262 @Override
263 public void sendObjectRemoved(int id) {
264 if (MtpDatabase.this.mServer != null)
265 MtpDatabase.this.mServer.sendObjectRemoved(id);
266 }
267 }, subDirectories == null ? null : Sets.newHashSet(subDirectories));
268
269 initDeviceProperties(context);
270 mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
271 mCloseGuard.open("close");
272 }
273
274 public void setServer(MtpServer server) {
275 mServer = server;
276 // always unregister before registering
277 try {
278 mContext.unregisterReceiver(mBatteryReceiver);
279 } catch (IllegalArgumentException e) {
280 // wasn't previously registered, ignore
281 }
282 // register for battery notifications when we are connected
283 if (server != null) {
284 mContext.registerReceiver(mBatteryReceiver,
285 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
286 }
287 }
288
289 @Override
290 public void close() {
291 mManager.close();
292 mCloseGuard.close();
293 if (mClosed.compareAndSet(false, true)) {
294 mMediaScanner.close();
Jerry Zhang484ea672018-03-02 15:40:03 -0800295 if (mMediaProvider != null) {
296 mMediaProvider.close();
297 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700298 native_finalize();
299 }
300 }
301
302 @Override
303 protected void finalize() throws Throwable {
304 try {
305 if (mCloseGuard != null) {
306 mCloseGuard.warnIfOpen();
307 }
308 close();
309 } finally {
310 super.finalize();
311 }
312 }
313
314 public void addStorage(StorageVolume storage) {
315 MtpStorage mtpStorage = mManager.addMtpStorage(storage);
316 mStorageMap.put(storage.getPath(), mtpStorage);
317 mServer.addStorage(mtpStorage);
318 }
319
320 public void removeStorage(StorageVolume storage) {
321 MtpStorage mtpStorage = mStorageMap.get(storage.getPath());
322 if (mtpStorage == null) {
323 return;
324 }
325 mServer.removeStorage(mtpStorage);
326 mManager.removeMtpStorage(mtpStorage);
327 mStorageMap.remove(storage.getPath());
328 }
329
330 private void initDeviceProperties(Context context) {
331 final String devicePropertiesName = "device-properties";
332 mDeviceProperties = context.getSharedPreferences(devicePropertiesName,
333 Context.MODE_PRIVATE);
334 File databaseFile = context.getDatabasePath(devicePropertiesName);
335
336 if (databaseFile.exists()) {
337 // for backward compatibility - read device properties from sqlite database
338 // and migrate them to shared prefs
339 SQLiteDatabase db = null;
340 Cursor c = null;
341 try {
342 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
343 if (db != null) {
344 c = db.query("properties", new String[]{"_id", "code", "value"},
345 null, null, null, null, null);
346 if (c != null) {
347 SharedPreferences.Editor e = mDeviceProperties.edit();
348 while (c.moveToNext()) {
349 String name = c.getString(1);
350 String value = c.getString(2);
351 e.putString(name, value);
352 }
353 e.commit();
354 }
355 }
356 } catch (Exception e) {
357 Log.e(TAG, "failed to migrate device properties", e);
358 } finally {
359 if (c != null) c.close();
360 if (db != null) db.close();
361 }
362 context.deleteDatabase(devicePropertiesName);
363 }
364 }
365
366 private int beginSendObject(String path, int format, int parent, int storageId) {
367 MtpStorageManager.MtpObject parentObj =
368 parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
369 if (parentObj == null) {
370 return -1;
371 }
372
373 Path objPath = Paths.get(path);
374 return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format);
375 }
376
377 private void endSendObject(int handle, boolean succeeded) {
378 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
379 if (obj == null || !mManager.endSendObject(obj, succeeded)) {
380 Log.e(TAG, "Failed to successfully end send object");
381 return;
382 }
383 // Add the new file to MediaProvider
384 if (succeeded) {
385 String path = obj.getPath().toString();
386 int format = obj.getFormat();
387 // Get parent info from MediaProvider, since the id is different from MTP's
388 ContentValues values = new ContentValues();
389 values.put(Files.FileColumns.DATA, path);
390 values.put(Files.FileColumns.FORMAT, format);
391 values.put(Files.FileColumns.SIZE, obj.getSize());
392 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
393 try {
394 if (obj.getParent().isRoot()) {
395 values.put(Files.FileColumns.PARENT, 0);
396 } else {
397 int parentId = findInMedia(obj.getParent().getPath());
398 if (parentId != -1) {
399 values.put(Files.FileColumns.PARENT, parentId);
400 } else {
401 // The parent isn't in MediaProvider. Don't add the new file.
402 return;
403 }
404 }
405
406 Uri uri = mMediaProvider.insert(mObjectsUri, values);
407 if (uri != null) {
408 rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
409 }
410 } catch (RemoteException e) {
411 Log.e(TAG, "RemoteException in beginSendObject", e);
412 }
413 }
414 }
415
416 private void rescanFile(String path, int handle, int format) {
417 // handle abstract playlists separately
418 // they do not exist in the file system so don't use the media scanner here
419 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
420 // extract name from path
421 String name = path;
422 int lastSlash = name.lastIndexOf('/');
423 if (lastSlash >= 0) {
424 name = name.substring(lastSlash + 1);
425 }
426 // strip trailing ".pla" from the name
427 if (name.endsWith(".pla")) {
428 name = name.substring(0, name.length() - 4);
429 }
430
431 ContentValues values = new ContentValues(1);
432 values.put(Audio.Playlists.DATA, path);
433 values.put(Audio.Playlists.NAME, name);
434 values.put(Files.FileColumns.FORMAT, format);
435 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
436 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
437 try {
438 mMediaProvider.insert(
439 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
440 } catch (RemoteException e) {
441 Log.e(TAG, "RemoteException in endSendObject", e);
442 }
443 } else {
444 mMediaScanner.scanMtpFile(path, handle, format);
445 }
446 }
447
448 private int[] getObjectList(int storageID, int format, int parent) {
449 Stream<MtpStorageManager.MtpObject> objectStream = mManager.getObjects(parent,
450 format, storageID);
451 if (objectStream == null) {
452 return null;
453 }
454 return objectStream.mapToInt(MtpStorageManager.MtpObject::getId).toArray();
455 }
456
457 private int getNumObjects(int storageID, int format, int parent) {
458 Stream<MtpStorageManager.MtpObject> objectStream = mManager.getObjects(parent,
459 format, storageID);
460 if (objectStream == null) {
461 return -1;
462 }
463 return (int) objectStream.count();
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400464 }
465
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900466 private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700467 int groupCode, int depth) {
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400468 // FIXME - implement group support
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700469 if (property == 0) {
470 if (groupCode == 0) {
471 return new MtpPropertyList(MtpConstants.RESPONSE_PARAMETER_NOT_SUPPORTED);
472 }
473 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
474 }
475 if (depth == 0xFFFFFFFF && (handle == 0 || handle == 0xFFFFFFFF)) {
476 // request all objects starting at root
477 handle = 0xFFFFFFFF;
478 depth = 0;
479 }
480 if (!(depth == 0 || depth == 1)) {
481 // we only support depth 0 and 1
482 // depth 0: single object, depth 1: immediate children
483 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
484 }
485 Stream<MtpStorageManager.MtpObject> objectStream = Stream.of();
486 if (handle == 0xFFFFFFFF) {
487 // All objects are requested
488 objectStream = mManager.getObjects(0, format, 0xFFFFFFFF);
489 if (objectStream == null) {
490 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
491 }
492 } else if (handle != 0) {
493 // Add the requested object if format matches
494 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
495 if (obj == null) {
496 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
497 }
498 if (obj.getFormat() == format || format == 0) {
499 objectStream = Stream.of(obj);
500 }
501 }
502 if (handle == 0 || depth == 1) {
503 if (handle == 0) {
504 handle = 0xFFFFFFFF;
505 }
506 // Get the direct children of root or this object.
507 Stream<MtpStorageManager.MtpObject> childStream = mManager.getObjects(handle, format,
508 0xFFFFFFFF);
509 if (childStream == null) {
510 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
511 }
512 objectStream = Stream.concat(objectStream, childStream);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400513 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400514
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700515 MtpPropertyList ret = new MtpPropertyList(MtpConstants.RESPONSE_OK);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500516 MtpPropertyGroup propertyGroup;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700517 Iterator<MtpStorageManager.MtpObject> iter = objectStream.iterator();
518 while (iter.hasNext()) {
519 MtpStorageManager.MtpObject obj = iter.next();
520 if (property == 0xffffffff) {
521 // Get all properties supported by this object
522 propertyGroup = mPropertyGroupsByFormat.get(obj.getFormat());
523 if (propertyGroup == null) {
524 int[] propertyList = getSupportedObjectProperties(format);
525 propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
526 propertyList);
527 mPropertyGroupsByFormat.put(format, propertyGroup);
528 }
529 } else {
530 // Get this property value
531 final int[] propertyList = new int[]{property};
532 propertyGroup = mPropertyGroupsByProperty.get(property);
533 if (propertyGroup == null) {
534 propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
535 propertyList);
536 mPropertyGroupsByProperty.put(property, propertyGroup);
537 }
Mike Lockwood71827742015-01-23 10:50:08 -0800538 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700539 int err = propertyGroup.getPropertyList(obj, ret);
540 if (err != MtpConstants.RESPONSE_OK) {
541 return new MtpPropertyList(err);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400542 }
543 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700544 return ret;
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400545 }
546
Mike Lockwood5ebac832010-10-12 11:33:47 -0400547 private int renameFile(int handle, String newName) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700548 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
549 if (obj == null) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400550 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
551 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700552 Path oldPath = obj.getPath();
Mike Lockwood73e56d92011-12-01 16:58:41 -0500553
Mike Lockwood5ebac832010-10-12 11:33:47 -0400554 // now rename the file. make sure this succeeds before updating database
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700555 if (!mManager.beginRenameObject(obj, newName))
Mike Lockwood5ebac832010-10-12 11:33:47 -0400556 return MtpConstants.RESPONSE_GENERAL_ERROR;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700557 Path newPath = obj.getPath();
558 boolean success = oldPath.toFile().renameTo(newPath.toFile());
559 if (!mManager.endRenameObject(obj, oldPath.getFileName().toString(), success)) {
560 Log.e(TAG, "Failed to end rename object");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400561 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400562 if (!success) {
563 return MtpConstants.RESPONSE_GENERAL_ERROR;
564 }
565
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700566 // finally update MediaProvider
Mike Lockwood5ebac832010-10-12 11:33:47 -0400567 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700568 values.put(Files.FileColumns.DATA, newPath.toString());
569 String[] whereArgs = new String[]{oldPath.toString()};
Mike Lockwood5ebac832010-10-12 11:33:47 -0400570 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400571 // note - we are relying on a special case in MediaProvider.update() to update
572 // the paths for all children in the case where this is a directory.
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700573 mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400574 } catch (RemoteException e) {
575 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
576 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400577
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800578 // check if nomedia status changed
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700579 if (obj.isDir()) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800580 // for directories, check if renamed from something hidden to something non-hidden
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700581 if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800582 // directory was unhidden
583 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700584 mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath.toString(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800585 } catch (RemoteException e) {
586 Log.e(TAG, "failed to unhide/rescan for " + newPath);
587 }
588 }
589 } else {
590 // for files, check if renamed from .nomedia to something else
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700591 if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
592 && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800593 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700594 mMediaProvider.call(MediaStore.UNHIDE_CALL,
595 oldPath.getParent().toString(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800596 } catch (RemoteException e) {
597 Log.e(TAG, "failed to unhide/rescan for " + newPath);
598 }
599 }
600 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400601 return MtpConstants.RESPONSE_OK;
602 }
603
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700604 private int beginMoveObject(int handle, int newParent, int newStorage) {
605 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
606 MtpStorageManager.MtpObject parent = newParent == 0 ?
607 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
608 if (obj == null || parent == null)
609 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Jerry Zhang952558d42017-09-26 17:49:52 -0700610
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700611 boolean allowed = mManager.beginMoveObject(obj, parent);
612 return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR;
613 }
614
615 private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage,
616 int objId, boolean success) {
617 MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ?
618 mManager.getStorageRoot(oldStorage) : mManager.getObject(oldParent);
619 MtpStorageManager.MtpObject newParentObj = newParent == 0 ?
620 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
621 MtpStorageManager.MtpObject obj = mManager.getObject(objId);
622 String name = obj.getName();
623 if (newParentObj == null || oldParentObj == null
624 ||!mManager.endMoveObject(oldParentObj, newParentObj, name, success)) {
625 Log.e(TAG, "Failed to end move object");
626 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700627 }
628
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700629 obj = mManager.getObject(objId);
630 if (!success || obj == null)
631 return;
632 // Get parent info from MediaProvider, since the id is different from MTP's
Jerry Zhang952558d42017-09-26 17:49:52 -0700633 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700634 Path path = newParentObj.getPath().resolve(name);
635 Path oldPath = oldParentObj.getPath().resolve(name);
636 values.put(Files.FileColumns.DATA, path.toString());
637 if (obj.getParent().isRoot()) {
638 values.put(Files.FileColumns.PARENT, 0);
639 } else {
640 int parentId = findInMedia(path.getParent());
641 if (parentId != -1) {
642 values.put(Files.FileColumns.PARENT, parentId);
643 } else {
644 // The new parent isn't in MediaProvider, so delete the object instead
645 deleteFromMedia(oldPath, obj.isDir());
646 return;
647 }
648 }
649 // update MediaProvider
650 Cursor c = null;
651 String[] whereArgs = new String[]{oldPath.toString()};
Jerry Zhang952558d42017-09-26 17:49:52 -0700652 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700653 int parentId = -1;
654 if (!oldParentObj.isRoot()) {
655 parentId = findInMedia(oldPath.getParent());
656 }
657 if (oldParentObj.isRoot() || parentId != -1) {
658 // Old parent exists in MediaProvider - perform a move
659 // note - we are relying on a special case in MediaProvider.update() to update
660 // the paths for all children in the case where this is a directory.
661 mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
662 } else {
663 // Old parent doesn't exist - add the object
664 values.put(Files.FileColumns.FORMAT, obj.getFormat());
665 values.put(Files.FileColumns.SIZE, obj.getSize());
666 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
667 Uri uri = mMediaProvider.insert(mObjectsUri, values);
668 if (uri != null) {
669 rescanFile(path.toString(),
670 Integer.parseInt(uri.getPathSegments().get(2)), obj.getFormat());
671 }
672 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700673 } catch (RemoteException e) {
674 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
675 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700676 }
677
678 private int beginCopyObject(int handle, int newParent, int newStorage) {
679 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
680 MtpStorageManager.MtpObject parent = newParent == 0 ?
681 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
682 if (obj == null || parent == null)
683 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
684 return mManager.beginCopyObject(obj, parent);
685 }
686
687 private void endCopyObject(int handle, boolean success) {
688 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
689 if (obj == null || !mManager.endCopyObject(obj, success)) {
690 Log.e(TAG, "Failed to end copy object");
691 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700692 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700693 if (!success) {
694 return;
695 }
696 String path = obj.getPath().toString();
697 int format = obj.getFormat();
698 // Get parent info from MediaProvider, since the id is different from MTP's
699 ContentValues values = new ContentValues();
700 values.put(Files.FileColumns.DATA, path);
701 values.put(Files.FileColumns.FORMAT, format);
702 values.put(Files.FileColumns.SIZE, obj.getSize());
703 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
704 try {
705 if (obj.getParent().isRoot()) {
706 values.put(Files.FileColumns.PARENT, 0);
707 } else {
708 int parentId = findInMedia(obj.getParent().getPath());
709 if (parentId != -1) {
710 values.put(Files.FileColumns.PARENT, parentId);
711 } else {
712 // The parent isn't in MediaProvider. Don't add the new file.
713 return;
714 }
715 }
716 if (obj.isDir()) {
717 mMediaScanner.scanDirectories(new String[]{path});
718 } else {
719 Uri uri = mMediaProvider.insert(mObjectsUri, values);
720 if (uri != null) {
721 rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
722 }
723 }
724 } catch (RemoteException e) {
725 Log.e(TAG, "RemoteException in beginSendObject", e);
726 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700727 }
728
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400729 private int setObjectProperty(int handle, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700730 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400731 switch (property) {
732 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
733 return renameFile(handle, stringValue);
734
735 default:
736 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
737 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400738 }
739
740 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400741 switch (property) {
742 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
743 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500744 // writable string properties kept in shared preferences
745 String value = mDeviceProperties.getString(Integer.toString(property), "");
746 int length = value.length();
747 if (length > 255) {
748 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400749 }
Mike Lockwood775de952011-03-05 17:34:11 -0500750 value.getChars(0, length, outStringValue, 0);
751 outStringValue[length] = 0;
752 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800753 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
754 // use screen size as max image size
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700755 Display display = ((WindowManager) mContext.getSystemService(
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800756 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700757 int width = display.getMaximumSizeDimension();
758 int height = display.getMaximumSizeDimension();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700759 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800760 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
761 outStringValue[imageSize.length()] = 0;
762 return MtpConstants.RESPONSE_OK;
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800763 case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
764 outIntValue[0] = mDeviceType;
765 return MtpConstants.RESPONSE_OK;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700766 case MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL:
767 outIntValue[0] = mBatteryLevel;
768 outIntValue[1] = mBatteryScale;
769 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800770 default:
771 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
772 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400773 }
774
775 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400776 switch (property) {
777 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
778 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500779 // writable string properties kept in shared prefs
780 SharedPreferences.Editor e = mDeviceProperties.edit();
781 e.putString(Integer.toString(property), stringValue);
782 return (e.commit() ? MtpConstants.RESPONSE_OK
783 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400784 }
785
786 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
787 }
788
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400789 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700790 char[] outName, long[] outCreatedModified) {
791 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
792 if (obj == null) {
793 return false;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400794 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700795 outStorageFormatParent[0] = obj.getStorageId();
796 outStorageFormatParent[1] = obj.getFormat();
797 outStorageFormatParent[2] = obj.getParent().isRoot() ? 0 : obj.getParent().getId();
798
799 int nameLen = Integer.min(obj.getName().length(), 255);
800 obj.getName().getChars(0, nameLen, outName, 0);
801 outName[nameLen] = 0;
802
803 outCreatedModified[0] = obj.getModifiedTime();
804 outCreatedModified[1] = obj.getModifiedTime();
805 return true;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400806 }
807
Mike Lockwood365e03e2010-12-08 16:08:01 -0800808 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700809 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
810 if (obj == null) {
811 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood01788562010-10-11 11:22:19 -0400812 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700813
814 String path = obj.getPath().toString();
815 int pathLen = Integer.min(path.length(), 4096);
816 path.getChars(0, pathLen, outFilePath, 0);
817 outFilePath[pathLen] = 0;
818
819 outFileLengthFormat[0] = obj.getSize();
820 outFileLengthFormat[1] = obj.getFormat();
821 return MtpConstants.RESPONSE_OK;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400822 }
823
Mike Lockwood71827742015-01-23 10:50:08 -0800824 private int getObjectFormat(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700825 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
826 if (obj == null) {
Mike Lockwood71827742015-01-23 10:50:08 -0800827 return -1;
Mike Lockwood71827742015-01-23 10:50:08 -0800828 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700829 return obj.getFormat();
Mike Lockwood71827742015-01-23 10:50:08 -0800830 }
831
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700832 private int beginDeleteObject(int handle) {
833 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
834 if (obj == null) {
835 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
836 }
837 if (!mManager.beginRemoveObject(obj)) {
838 return MtpConstants.RESPONSE_GENERAL_ERROR;
839 }
840 return MtpConstants.RESPONSE_OK;
841 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800842
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700843 private void endDeleteObject(int handle, boolean success) {
844 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
845 if (obj == null) {
846 return;
847 }
848 if (!mManager.endRemoveObject(obj, success))
849 Log.e(TAG, "Failed to end remove object");
850 if (success)
851 deleteFromMedia(obj.getPath(), obj.isDir());
852 }
853
854 private int findInMedia(Path path) {
855 int ret = -1;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800856 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400857 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700858 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
859 new String[]{path.toString()}, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800860 if (c != null && c.moveToNext()) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700861 ret = c.getInt(0);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800862 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700863 } catch (RemoteException e) {
864 Log.e(TAG, "Error finding " + path + " in MediaProvider");
865 } finally {
866 if (c != null)
867 c.close();
868 }
869 return ret;
870 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800871
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700872 private void deleteFromMedia(Path path, boolean isDir) {
873 try {
874 // Delete the object(s) from MediaProvider, but ignore errors.
875 if (isDir) {
Mike Lockwood55f808c2010-12-14 13:14:29 -0800876 // recursive case - delete all children first
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700877 mMediaProvider.delete(mObjectsUri,
878 // the 'like' makes it use the index, the 'lower()' makes it correct
879 // when the path contains sqlite wildcard characters
880 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
881 new String[]{path + "/%", Integer.toString(path.toString().length() + 1),
882 path.toString() + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800883 }
884
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700885 String[] whereArgs = new String[]{path.toString()};
886 if (mMediaProvider.delete(mObjectsUri, PATH_WHERE, whereArgs) > 0) {
887 if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800888 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700889 String parentPath = path.getParent().toString();
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700890 mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800891 } catch (RemoteException e) {
892 Log.e(TAG, "failed to unhide/rescan for " + path);
893 }
894 }
Mike Lockwood59c777a2010-08-02 10:37:41 -0400895 } else {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700896 Log.i(TAG, "Mediaprovider didn't delete " + path);
Mike Lockwood59c777a2010-08-02 10:37:41 -0400897 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700898 } catch (Exception e) {
899 Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400900 }
901 }
902
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400903 private int[] getObjectReferences(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700904 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
905 if (obj == null)
906 return null;
907 // Translate this handle to the MediaProvider Handle
908 handle = findInMedia(obj.getPath());
909 if (handle == -1)
910 return null;
Mike Lockwood8490e662010-09-09 14:16:22 -0400911 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400912 Cursor c = null;
913 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700914 c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400915 if (c == null) {
916 return null;
917 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700918 ArrayList<Integer> result = new ArrayList<>();
919 while (c.moveToNext()) {
920 // Translate result handles back into handles for this session.
921 String refPath = c.getString(0);
922 MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath);
923 if (refObj != null) {
924 result.add(refObj.getId());
925 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400926 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700927 return result.stream().mapToInt(Integer::intValue).toArray();
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400928 } catch (RemoteException e) {
929 Log.e(TAG, "RemoteException in getObjectList", e);
930 } finally {
931 if (c != null) {
932 c.close();
933 }
934 }
935 return null;
936 }
937
938 private int setObjectReferences(int handle, int[] references) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700939 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
940 if (obj == null)
941 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
942 // Translate this handle to the MediaProvider Handle
943 handle = findInMedia(obj.getPath());
944 if (handle == -1)
945 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood8490e662010-09-09 14:16:22 -0400946 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700947 ArrayList<ContentValues> valuesList = new ArrayList<>();
948 for (int id : references) {
949 // Translate each reference id to the MediaProvider Id
950 MtpStorageManager.MtpObject refObj = mManager.getObject(id);
951 if (refObj == null)
952 continue;
953 int refHandle = findInMedia(refObj.getPath());
954 if (refHandle == -1)
955 continue;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400956 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700957 values.put(Files.FileColumns._ID, refHandle);
958 valuesList.add(values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400959 }
960 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700961 if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400962 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400963 }
964 } catch (RemoteException e) {
965 Log.e(TAG, "RemoteException in setObjectReferences", e);
966 }
Mike Lockwood5367ab62010-08-30 13:23:02 -0400967 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400968 }
969
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400970 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +0000971 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400972
973 private native final void native_setup();
974 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400975}