blob: 474b671c0c2b0b06c325ca0c84b1c7c9516bafc1 [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;
Jerry Zhangd470a1e2018-05-14 12:19:08 -070038import android.system.ErrnoException;
39import android.system.Os;
40import android.system.OsConstants;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040041import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080042import android.view.Display;
43import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040044
Jeff Sharkey60cfad82016-01-05 17:30:57 -070045import dalvik.system.CloseGuard;
46
Jerry Zhangf9c5c252017-08-16 18:07:51 -070047import com.google.android.collect.Sets;
48
Mike Lockwood5ebac832010-10-12 11:33:47 -040049import java.io.File;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070050import java.nio.file.Path;
51import java.nio.file.Paths;
52import java.util.ArrayList;
53import java.util.Arrays;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050054import java.util.HashMap;
Jerry Zhang9a018742018-05-10 18:27:13 -070055import java.util.List;
dujin.chafe464a72011-11-22 12:13:33 +090056import java.util.Locale;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070057import java.util.concurrent.atomic.AtomicBoolean;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070058import java.util.stream.IntStream;
Mike Lockwood5ebac832010-10-12 11:33:47 -040059
Mike Lockwoodd21eac92010-07-03 00:44:05 -040060/**
Jerry Zhangf9c5c252017-08-16 18:07:51 -070061 * MtpDatabase provides an interface for MTP operations that MtpServer can use. To do this, it uses
62 * MtpStorageManager for filesystem operations and MediaProvider to get media metadata. File
63 * operations are also reflected in MediaProvider if possible.
64 * operations
Mike Lockwoodd21eac92010-07-03 00:44:05 -040065 * {@hide}
66 */
Jeff Sharkey60cfad82016-01-05 17:30:57 -070067public class MtpDatabase implements AutoCloseable {
Jerry Zhangf9c5c252017-08-16 18:07:51 -070068 private static final String TAG = MtpDatabase.class.getSimpleName();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040069
Mike Lockwood2837eef2010-08-31 16:25:12 -040070 private final Context mContext;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070071 private final ContentProviderClient mMediaProvider;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040072 private final String mVolumeName;
73 private final Uri mObjectsUri;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070074 private final MediaScanner mMediaScanner;
75
76 private final AtomicBoolean mClosed = new AtomicBoolean();
77 private final CloseGuard mCloseGuard = CloseGuard.get();
78
Jerry Zhangf9c5c252017-08-16 18:07:51 -070079 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040080
Mike Lockwood7d7fb632010-12-01 18:46:23 -050081 // cached property groups for single properties
Jerry Zhangf9c5c252017-08-16 18:07:51 -070082 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty = new HashMap<>();
Mike Lockwood7d7fb632010-12-01 18:46:23 -050083
84 // cached property groups for all properties for a given format
Jerry Zhangf9c5c252017-08-16 18:07:51 -070085 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat = new HashMap<>();
Mike Lockwood2837eef2010-08-31 16:25:12 -040086
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
Jerry Zhangf9c5c252017-08-16 18:07:51 -070090 // Cached device properties
Mike Lockwood56c85242014-03-07 13:29:08 -080091 private int mBatteryLevel;
92 private int mBatteryScale;
Jerry Zhang13bb2f42016-12-14 15:39:29 -080093 private int mDeviceType;
94
Jerry Zhangf9c5c252017-08-16 18:07:51 -070095 private MtpServer mServer;
96 private MtpStorageManager mManager;
97
98 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
99 private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
100 private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
101 private static final String NO_MEDIA = ".nomedia";
102
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400103 static {
104 System.loadLibrary("media_jni");
105 }
106
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700107 private static final int[] PLAYBACK_FORMATS = {
108 // allow transferring arbitrary files
Mike Lockwoode5211692010-09-08 13:50:45 -0400109 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400110
Mike Lockwood792ec842010-09-09 15:30:10 -0400111 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400112 MtpConstants.FORMAT_TEXT,
113 MtpConstants.FORMAT_HTML,
114 MtpConstants.FORMAT_WAV,
115 MtpConstants.FORMAT_MP3,
116 MtpConstants.FORMAT_MPEG,
117 MtpConstants.FORMAT_EXIF_JPEG,
118 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800119 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400120 MtpConstants.FORMAT_GIF,
121 MtpConstants.FORMAT_JFIF,
122 MtpConstants.FORMAT_PNG,
123 MtpConstants.FORMAT_TIFF,
124 MtpConstants.FORMAT_WMA,
125 MtpConstants.FORMAT_OGG,
126 MtpConstants.FORMAT_AAC,
127 MtpConstants.FORMAT_MP4_CONTAINER,
128 MtpConstants.FORMAT_MP2,
129 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400130 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400131 MtpConstants.FORMAT_WPL_PLAYLIST,
132 MtpConstants.FORMAT_M3U_PLAYLIST,
133 MtpConstants.FORMAT_PLS_PLAYLIST,
134 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800135 MtpConstants.FORMAT_FLAC,
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100136 MtpConstants.FORMAT_DNG,
Chong Zhang6e18cce2017-08-16 11:57:02 -0700137 MtpConstants.FORMAT_HEIF,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700138 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400139
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700140 private static final int[] FILE_PROPERTIES = {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400141 MtpConstants.PROPERTY_STORAGE_ID,
142 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400143 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400144 MtpConstants.PROPERTY_OBJECT_SIZE,
145 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400146 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400147 MtpConstants.PROPERTY_PERSISTENT_UID,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700148 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400149 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800150 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400151 MtpConstants.PROPERTY_DATE_ADDED,
152 };
153
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700154 private static final int[] AUDIO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400155 MtpConstants.PROPERTY_ARTIST,
156 MtpConstants.PROPERTY_ALBUM_NAME,
157 MtpConstants.PROPERTY_ALBUM_ARTIST,
158 MtpConstants.PROPERTY_TRACK,
159 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
160 MtpConstants.PROPERTY_DURATION,
161 MtpConstants.PROPERTY_GENRE,
162 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700163 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
164 MtpConstants.PROPERTY_BITRATE_TYPE,
165 MtpConstants.PROPERTY_AUDIO_BITRATE,
166 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
167 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400168 };
169
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700170 private static final int[] VIDEO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400171 MtpConstants.PROPERTY_ARTIST,
172 MtpConstants.PROPERTY_ALBUM_NAME,
173 MtpConstants.PROPERTY_DURATION,
174 MtpConstants.PROPERTY_DESCRIPTION,
175 };
176
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700177 private static final int[] IMAGE_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400178 MtpConstants.PROPERTY_DESCRIPTION,
179 };
180
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700181 private static final int[] DEVICE_PROPERTIES = {
182 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
183 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
184 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
185 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
186 MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
187 };
188
Mike Lockwoodae078f72010-09-26 12:35:51 -0400189 private int[] getSupportedObjectProperties(int format) {
190 switch (format) {
191 case MtpConstants.FORMAT_MP3:
192 case MtpConstants.FORMAT_WAV:
193 case MtpConstants.FORMAT_WMA:
194 case MtpConstants.FORMAT_OGG:
195 case MtpConstants.FORMAT_AAC:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700196 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
197 Arrays.stream(AUDIO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400198 case MtpConstants.FORMAT_MPEG:
199 case MtpConstants.FORMAT_3GP_CONTAINER:
200 case MtpConstants.FORMAT_WMV:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700201 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
202 Arrays.stream(VIDEO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400203 case MtpConstants.FORMAT_EXIF_JPEG:
204 case MtpConstants.FORMAT_GIF:
205 case MtpConstants.FORMAT_PNG:
206 case MtpConstants.FORMAT_BMP:
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100207 case MtpConstants.FORMAT_DNG:
Chong Zhang6e18cce2017-08-16 11:57:02 -0700208 case MtpConstants.FORMAT_HEIF:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700209 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
210 Arrays.stream(IMAGE_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400211 default:
212 return FILE_PROPERTIES;
213 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400214 }
215
216 private int[] getSupportedDeviceProperties() {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700217 return DEVICE_PROPERTIES;
218 }
219
220 private int[] getSupportedPlaybackFormats() {
221 return PLAYBACK_FORMATS;
222 }
223
224 private int[] getSupportedCaptureFormats() {
225 // no capture formats yet
226 return null;
227 }
228
229 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
230 @Override
231 public void onReceive(Context context, Intent intent) {
232 String action = intent.getAction();
233 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
234 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
235 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
236 if (newLevel != mBatteryLevel) {
237 mBatteryLevel = newLevel;
238 if (mServer != null) {
239 // send device property changed event
240 mServer.sendDevicePropertyChanged(
241 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
242 }
243 }
244 }
245 }
246 };
247
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800248 public MtpDatabase(Context context, String volumeName,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700249 String[] subDirectories) {
250 native_setup();
251 mContext = context;
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800252 mMediaProvider = context.getContentResolver()
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700253 .acquireContentProviderClient(MediaStore.AUTHORITY);
254 mVolumeName = volumeName;
255 mObjectsUri = Files.getMtpObjectsUri(volumeName);
256 mMediaScanner = new MediaScanner(context, mVolumeName);
257 mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
258 @Override
259 public void sendObjectAdded(int id) {
260 if (MtpDatabase.this.mServer != null)
261 MtpDatabase.this.mServer.sendObjectAdded(id);
262 }
263
264 @Override
265 public void sendObjectRemoved(int id) {
266 if (MtpDatabase.this.mServer != null)
267 MtpDatabase.this.mServer.sendObjectRemoved(id);
268 }
Jamese4f680e2018-07-02 17:42:07 +0800269
270 @Override
271 public void sendObjectInfoChanged(int id) {
272 if (MtpDatabase.this.mServer != null)
273 MtpDatabase.this.mServer.sendObjectInfoChanged(id);
274 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700275 }, subDirectories == null ? null : Sets.newHashSet(subDirectories));
276
277 initDeviceProperties(context);
278 mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
279 mCloseGuard.open("close");
280 }
281
282 public void setServer(MtpServer server) {
283 mServer = server;
284 // always unregister before registering
285 try {
286 mContext.unregisterReceiver(mBatteryReceiver);
287 } catch (IllegalArgumentException e) {
288 // wasn't previously registered, ignore
289 }
290 // register for battery notifications when we are connected
291 if (server != null) {
292 mContext.registerReceiver(mBatteryReceiver,
293 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
294 }
295 }
296
297 @Override
298 public void close() {
299 mManager.close();
300 mCloseGuard.close();
301 if (mClosed.compareAndSet(false, true)) {
302 mMediaScanner.close();
Jerry Zhang484ea672018-03-02 15:40:03 -0800303 if (mMediaProvider != null) {
304 mMediaProvider.close();
305 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700306 native_finalize();
307 }
308 }
309
310 @Override
311 protected void finalize() throws Throwable {
312 try {
313 if (mCloseGuard != null) {
314 mCloseGuard.warnIfOpen();
315 }
316 close();
317 } finally {
318 super.finalize();
319 }
320 }
321
322 public void addStorage(StorageVolume storage) {
323 MtpStorage mtpStorage = mManager.addMtpStorage(storage);
324 mStorageMap.put(storage.getPath(), mtpStorage);
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700325 if (mServer != null) {
326 mServer.addStorage(mtpStorage);
327 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700328 }
329
330 public void removeStorage(StorageVolume storage) {
331 MtpStorage mtpStorage = mStorageMap.get(storage.getPath());
332 if (mtpStorage == null) {
333 return;
334 }
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700335 if (mServer != null) {
336 mServer.removeStorage(mtpStorage);
337 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700338 mManager.removeMtpStorage(mtpStorage);
339 mStorageMap.remove(storage.getPath());
340 }
341
342 private void initDeviceProperties(Context context) {
343 final String devicePropertiesName = "device-properties";
344 mDeviceProperties = context.getSharedPreferences(devicePropertiesName,
345 Context.MODE_PRIVATE);
346 File databaseFile = context.getDatabasePath(devicePropertiesName);
347
348 if (databaseFile.exists()) {
349 // for backward compatibility - read device properties from sqlite database
350 // and migrate them to shared prefs
351 SQLiteDatabase db = null;
352 Cursor c = null;
353 try {
354 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
355 if (db != null) {
356 c = db.query("properties", new String[]{"_id", "code", "value"},
357 null, null, null, null, null);
358 if (c != null) {
359 SharedPreferences.Editor e = mDeviceProperties.edit();
360 while (c.moveToNext()) {
361 String name = c.getString(1);
362 String value = c.getString(2);
363 e.putString(name, value);
364 }
365 e.commit();
366 }
367 }
368 } catch (Exception e) {
369 Log.e(TAG, "failed to migrate device properties", e);
370 } finally {
371 if (c != null) c.close();
372 if (db != null) db.close();
373 }
374 context.deleteDatabase(devicePropertiesName);
375 }
376 }
377
378 private int beginSendObject(String path, int format, int parent, int storageId) {
379 MtpStorageManager.MtpObject parentObj =
380 parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
381 if (parentObj == null) {
382 return -1;
383 }
384
385 Path objPath = Paths.get(path);
386 return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format);
387 }
388
389 private void endSendObject(int handle, boolean succeeded) {
390 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
391 if (obj == null || !mManager.endSendObject(obj, succeeded)) {
392 Log.e(TAG, "Failed to successfully end send object");
393 return;
394 }
395 // Add the new file to MediaProvider
396 if (succeeded) {
397 String path = obj.getPath().toString();
398 int format = obj.getFormat();
399 // Get parent info from MediaProvider, since the id is different from MTP's
400 ContentValues values = new ContentValues();
401 values.put(Files.FileColumns.DATA, path);
402 values.put(Files.FileColumns.FORMAT, format);
403 values.put(Files.FileColumns.SIZE, obj.getSize());
404 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
405 try {
406 if (obj.getParent().isRoot()) {
407 values.put(Files.FileColumns.PARENT, 0);
408 } else {
409 int parentId = findInMedia(obj.getParent().getPath());
410 if (parentId != -1) {
411 values.put(Files.FileColumns.PARENT, parentId);
412 } else {
413 // The parent isn't in MediaProvider. Don't add the new file.
414 return;
415 }
416 }
417
418 Uri uri = mMediaProvider.insert(mObjectsUri, values);
419 if (uri != null) {
420 rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
421 }
422 } catch (RemoteException e) {
423 Log.e(TAG, "RemoteException in beginSendObject", e);
424 }
425 }
426 }
427
428 private void rescanFile(String path, int handle, int format) {
429 // handle abstract playlists separately
430 // they do not exist in the file system so don't use the media scanner here
431 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
432 // extract name from path
433 String name = path;
434 int lastSlash = name.lastIndexOf('/');
435 if (lastSlash >= 0) {
436 name = name.substring(lastSlash + 1);
437 }
438 // strip trailing ".pla" from the name
439 if (name.endsWith(".pla")) {
440 name = name.substring(0, name.length() - 4);
441 }
442
443 ContentValues values = new ContentValues(1);
444 values.put(Audio.Playlists.DATA, path);
445 values.put(Audio.Playlists.NAME, name);
446 values.put(Files.FileColumns.FORMAT, format);
447 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
448 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
449 try {
450 mMediaProvider.insert(
451 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
452 } catch (RemoteException e) {
453 Log.e(TAG, "RemoteException in endSendObject", e);
454 }
455 } else {
456 mMediaScanner.scanMtpFile(path, handle, format);
457 }
458 }
459
460 private int[] getObjectList(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700461 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700462 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700463 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700464 return null;
465 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700466 int[] ret = new int[objs.size()];
467 for (int i = 0; i < objs.size(); i++) {
468 ret[i] = objs.get(i).getId();
469 }
470 return ret;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700471 }
472
473 private int getNumObjects(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700474 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700475 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700476 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700477 return -1;
478 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700479 return objs.size();
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400480 }
481
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900482 private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700483 int groupCode, int depth) {
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400484 // FIXME - implement group support
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700485 if (property == 0) {
486 if (groupCode == 0) {
487 return new MtpPropertyList(MtpConstants.RESPONSE_PARAMETER_NOT_SUPPORTED);
488 }
489 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
490 }
491 if (depth == 0xFFFFFFFF && (handle == 0 || handle == 0xFFFFFFFF)) {
492 // request all objects starting at root
493 handle = 0xFFFFFFFF;
494 depth = 0;
495 }
496 if (!(depth == 0 || depth == 1)) {
497 // we only support depth 0 and 1
498 // depth 0: single object, depth 1: immediate children
499 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
500 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700501 List<MtpStorageManager.MtpObject> objs = null;
502 MtpStorageManager.MtpObject thisObj = null;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700503 if (handle == 0xFFFFFFFF) {
504 // All objects are requested
Jerry Zhang9a018742018-05-10 18:27:13 -0700505 objs = mManager.getObjects(0, format, 0xFFFFFFFF);
506 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700507 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
508 }
509 } else if (handle != 0) {
510 // Add the requested object if format matches
511 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
512 if (obj == null) {
513 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
514 }
515 if (obj.getFormat() == format || format == 0) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700516 thisObj = obj;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700517 }
518 }
519 if (handle == 0 || depth == 1) {
520 if (handle == 0) {
521 handle = 0xFFFFFFFF;
522 }
523 // Get the direct children of root or this object.
Jerry Zhang9a018742018-05-10 18:27:13 -0700524 objs = mManager.getObjects(handle, format,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700525 0xFFFFFFFF);
Jerry Zhang9a018742018-05-10 18:27:13 -0700526 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700527 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
528 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700529 }
530 if (objs == null) {
531 objs = new ArrayList<>();
532 }
533 if (thisObj != null) {
534 objs.add(thisObj);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400535 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400536
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700537 MtpPropertyList ret = new MtpPropertyList(MtpConstants.RESPONSE_OK);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500538 MtpPropertyGroup propertyGroup;
Jerry Zhang9a018742018-05-10 18:27:13 -0700539 for (MtpStorageManager.MtpObject obj : objs) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700540 if (property == 0xffffffff) {
James Weif7f608c2018-08-15 22:23:12 +0800541 if (format == 0 && handle != 0 && handle != 0xffffffff) {
542 // return properties based on the object's format
543 format = obj.getFormat();
544 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700545 // Get all properties supported by this object
James Weif7f608c2018-08-15 22:23:12 +0800546 // format should be the same between get & put
547 propertyGroup = mPropertyGroupsByFormat.get(format);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700548 if (propertyGroup == null) {
549 int[] propertyList = getSupportedObjectProperties(format);
550 propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
551 propertyList);
552 mPropertyGroupsByFormat.put(format, propertyGroup);
553 }
554 } else {
555 // Get this property value
556 final int[] propertyList = new int[]{property};
557 propertyGroup = mPropertyGroupsByProperty.get(property);
558 if (propertyGroup == null) {
559 propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
560 propertyList);
561 mPropertyGroupsByProperty.put(property, propertyGroup);
562 }
Mike Lockwood71827742015-01-23 10:50:08 -0800563 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700564 int err = propertyGroup.getPropertyList(obj, ret);
565 if (err != MtpConstants.RESPONSE_OK) {
566 return new MtpPropertyList(err);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400567 }
568 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700569 return ret;
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400570 }
571
Mike Lockwood5ebac832010-10-12 11:33:47 -0400572 private int renameFile(int handle, String newName) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700573 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
574 if (obj == null) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400575 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
576 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700577 Path oldPath = obj.getPath();
Mike Lockwood73e56d92011-12-01 16:58:41 -0500578
Mike Lockwood5ebac832010-10-12 11:33:47 -0400579 // now rename the file. make sure this succeeds before updating database
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700580 if (!mManager.beginRenameObject(obj, newName))
Mike Lockwood5ebac832010-10-12 11:33:47 -0400581 return MtpConstants.RESPONSE_GENERAL_ERROR;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700582 Path newPath = obj.getPath();
583 boolean success = oldPath.toFile().renameTo(newPath.toFile());
Jerry Zhangd470a1e2018-05-14 12:19:08 -0700584 try {
585 Os.access(oldPath.toString(), OsConstants.F_OK);
586 Os.access(newPath.toString(), OsConstants.F_OK);
587 } catch (ErrnoException e) {
588 // Ignore. Could fail if the metadata was already updated.
589 }
590
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700591 if (!mManager.endRenameObject(obj, oldPath.getFileName().toString(), success)) {
592 Log.e(TAG, "Failed to end rename object");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400593 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400594 if (!success) {
595 return MtpConstants.RESPONSE_GENERAL_ERROR;
596 }
597
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700598 // finally update MediaProvider
Mike Lockwood5ebac832010-10-12 11:33:47 -0400599 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700600 values.put(Files.FileColumns.DATA, newPath.toString());
601 String[] whereArgs = new String[]{oldPath.toString()};
Mike Lockwood5ebac832010-10-12 11:33:47 -0400602 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400603 // note - we are relying on a special case in MediaProvider.update() to update
604 // the paths for all children in the case where this is a directory.
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700605 mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400606 } catch (RemoteException e) {
607 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
608 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400609
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800610 // check if nomedia status changed
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700611 if (obj.isDir()) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800612 // for directories, check if renamed from something hidden to something non-hidden
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700613 if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800614 // directory was unhidden
615 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700616 mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath.toString(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800617 } catch (RemoteException e) {
618 Log.e(TAG, "failed to unhide/rescan for " + newPath);
619 }
620 }
621 } else {
622 // for files, check if renamed from .nomedia to something else
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700623 if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
624 && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800625 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700626 mMediaProvider.call(MediaStore.UNHIDE_CALL,
627 oldPath.getParent().toString(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800628 } catch (RemoteException e) {
629 Log.e(TAG, "failed to unhide/rescan for " + newPath);
630 }
631 }
632 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400633 return MtpConstants.RESPONSE_OK;
634 }
635
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700636 private int beginMoveObject(int handle, int newParent, int newStorage) {
637 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
638 MtpStorageManager.MtpObject parent = newParent == 0 ?
639 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
640 if (obj == null || parent == null)
641 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Jerry Zhang952558d42017-09-26 17:49:52 -0700642
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700643 boolean allowed = mManager.beginMoveObject(obj, parent);
644 return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR;
645 }
646
647 private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage,
648 int objId, boolean success) {
649 MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ?
650 mManager.getStorageRoot(oldStorage) : mManager.getObject(oldParent);
651 MtpStorageManager.MtpObject newParentObj = newParent == 0 ?
652 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
653 MtpStorageManager.MtpObject obj = mManager.getObject(objId);
654 String name = obj.getName();
655 if (newParentObj == null || oldParentObj == null
656 ||!mManager.endMoveObject(oldParentObj, newParentObj, name, success)) {
657 Log.e(TAG, "Failed to end move object");
658 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700659 }
660
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700661 obj = mManager.getObject(objId);
662 if (!success || obj == null)
663 return;
664 // Get parent info from MediaProvider, since the id is different from MTP's
Jerry Zhang952558d42017-09-26 17:49:52 -0700665 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700666 Path path = newParentObj.getPath().resolve(name);
667 Path oldPath = oldParentObj.getPath().resolve(name);
668 values.put(Files.FileColumns.DATA, path.toString());
669 if (obj.getParent().isRoot()) {
670 values.put(Files.FileColumns.PARENT, 0);
671 } else {
672 int parentId = findInMedia(path.getParent());
673 if (parentId != -1) {
674 values.put(Files.FileColumns.PARENT, parentId);
675 } else {
676 // The new parent isn't in MediaProvider, so delete the object instead
677 deleteFromMedia(oldPath, obj.isDir());
678 return;
679 }
680 }
681 // update MediaProvider
682 Cursor c = null;
683 String[] whereArgs = new String[]{oldPath.toString()};
Jerry Zhang952558d42017-09-26 17:49:52 -0700684 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700685 int parentId = -1;
686 if (!oldParentObj.isRoot()) {
687 parentId = findInMedia(oldPath.getParent());
688 }
689 if (oldParentObj.isRoot() || parentId != -1) {
690 // Old parent exists in MediaProvider - perform a move
691 // note - we are relying on a special case in MediaProvider.update() to update
692 // the paths for all children in the case where this is a directory.
693 mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
694 } else {
695 // Old parent doesn't exist - add the object
696 values.put(Files.FileColumns.FORMAT, obj.getFormat());
697 values.put(Files.FileColumns.SIZE, obj.getSize());
698 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
699 Uri uri = mMediaProvider.insert(mObjectsUri, values);
700 if (uri != null) {
701 rescanFile(path.toString(),
702 Integer.parseInt(uri.getPathSegments().get(2)), obj.getFormat());
703 }
704 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700705 } catch (RemoteException e) {
706 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
707 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700708 }
709
710 private int beginCopyObject(int handle, int newParent, int newStorage) {
711 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
712 MtpStorageManager.MtpObject parent = newParent == 0 ?
713 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
714 if (obj == null || parent == null)
715 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
716 return mManager.beginCopyObject(obj, parent);
717 }
718
719 private void endCopyObject(int handle, boolean success) {
720 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
721 if (obj == null || !mManager.endCopyObject(obj, success)) {
722 Log.e(TAG, "Failed to end copy object");
723 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700724 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700725 if (!success) {
726 return;
727 }
728 String path = obj.getPath().toString();
729 int format = obj.getFormat();
730 // Get parent info from MediaProvider, since the id is different from MTP's
731 ContentValues values = new ContentValues();
732 values.put(Files.FileColumns.DATA, path);
733 values.put(Files.FileColumns.FORMAT, format);
734 values.put(Files.FileColumns.SIZE, obj.getSize());
735 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
736 try {
737 if (obj.getParent().isRoot()) {
738 values.put(Files.FileColumns.PARENT, 0);
739 } else {
740 int parentId = findInMedia(obj.getParent().getPath());
741 if (parentId != -1) {
742 values.put(Files.FileColumns.PARENT, parentId);
743 } else {
744 // The parent isn't in MediaProvider. Don't add the new file.
745 return;
746 }
747 }
748 if (obj.isDir()) {
749 mMediaScanner.scanDirectories(new String[]{path});
750 } else {
751 Uri uri = mMediaProvider.insert(mObjectsUri, values);
752 if (uri != null) {
753 rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
754 }
755 }
756 } catch (RemoteException e) {
757 Log.e(TAG, "RemoteException in beginSendObject", e);
758 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700759 }
760
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400761 private int setObjectProperty(int handle, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700762 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400763 switch (property) {
764 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
765 return renameFile(handle, stringValue);
766
767 default:
768 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
769 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400770 }
771
772 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400773 switch (property) {
774 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
775 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500776 // writable string properties kept in shared preferences
777 String value = mDeviceProperties.getString(Integer.toString(property), "");
778 int length = value.length();
779 if (length > 255) {
780 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400781 }
Mike Lockwood775de952011-03-05 17:34:11 -0500782 value.getChars(0, length, outStringValue, 0);
783 outStringValue[length] = 0;
784 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800785 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
786 // use screen size as max image size
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700787 Display display = ((WindowManager) mContext.getSystemService(
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800788 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700789 int width = display.getMaximumSizeDimension();
790 int height = display.getMaximumSizeDimension();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700791 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800792 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
793 outStringValue[imageSize.length()] = 0;
794 return MtpConstants.RESPONSE_OK;
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800795 case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
796 outIntValue[0] = mDeviceType;
797 return MtpConstants.RESPONSE_OK;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700798 case MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL:
799 outIntValue[0] = mBatteryLevel;
800 outIntValue[1] = mBatteryScale;
801 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800802 default:
803 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
804 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400805 }
806
807 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400808 switch (property) {
809 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
810 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500811 // writable string properties kept in shared prefs
812 SharedPreferences.Editor e = mDeviceProperties.edit();
813 e.putString(Integer.toString(property), stringValue);
814 return (e.commit() ? MtpConstants.RESPONSE_OK
815 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400816 }
817
818 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
819 }
820
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400821 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700822 char[] outName, long[] outCreatedModified) {
823 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
824 if (obj == null) {
825 return false;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400826 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700827 outStorageFormatParent[0] = obj.getStorageId();
828 outStorageFormatParent[1] = obj.getFormat();
829 outStorageFormatParent[2] = obj.getParent().isRoot() ? 0 : obj.getParent().getId();
830
831 int nameLen = Integer.min(obj.getName().length(), 255);
832 obj.getName().getChars(0, nameLen, outName, 0);
833 outName[nameLen] = 0;
834
835 outCreatedModified[0] = obj.getModifiedTime();
836 outCreatedModified[1] = obj.getModifiedTime();
837 return true;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400838 }
839
Mike Lockwood365e03e2010-12-08 16:08:01 -0800840 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700841 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
842 if (obj == null) {
843 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood01788562010-10-11 11:22:19 -0400844 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700845
846 String path = obj.getPath().toString();
847 int pathLen = Integer.min(path.length(), 4096);
848 path.getChars(0, pathLen, outFilePath, 0);
849 outFilePath[pathLen] = 0;
850
851 outFileLengthFormat[0] = obj.getSize();
852 outFileLengthFormat[1] = obj.getFormat();
853 return MtpConstants.RESPONSE_OK;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400854 }
855
Mike Lockwood71827742015-01-23 10:50:08 -0800856 private int getObjectFormat(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700857 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
858 if (obj == null) {
Mike Lockwood71827742015-01-23 10:50:08 -0800859 return -1;
Mike Lockwood71827742015-01-23 10:50:08 -0800860 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700861 return obj.getFormat();
Mike Lockwood71827742015-01-23 10:50:08 -0800862 }
863
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700864 private int beginDeleteObject(int handle) {
865 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
866 if (obj == null) {
867 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
868 }
869 if (!mManager.beginRemoveObject(obj)) {
870 return MtpConstants.RESPONSE_GENERAL_ERROR;
871 }
872 return MtpConstants.RESPONSE_OK;
873 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800874
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700875 private void endDeleteObject(int handle, boolean success) {
876 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
877 if (obj == null) {
878 return;
879 }
880 if (!mManager.endRemoveObject(obj, success))
881 Log.e(TAG, "Failed to end remove object");
882 if (success)
883 deleteFromMedia(obj.getPath(), obj.isDir());
884 }
885
886 private int findInMedia(Path path) {
887 int ret = -1;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800888 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400889 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700890 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
891 new String[]{path.toString()}, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800892 if (c != null && c.moveToNext()) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700893 ret = c.getInt(0);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800894 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700895 } catch (RemoteException e) {
896 Log.e(TAG, "Error finding " + path + " in MediaProvider");
897 } finally {
898 if (c != null)
899 c.close();
900 }
901 return ret;
902 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800903
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700904 private void deleteFromMedia(Path path, boolean isDir) {
905 try {
906 // Delete the object(s) from MediaProvider, but ignore errors.
907 if (isDir) {
Mike Lockwood55f808c2010-12-14 13:14:29 -0800908 // recursive case - delete all children first
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700909 mMediaProvider.delete(mObjectsUri,
910 // the 'like' makes it use the index, the 'lower()' makes it correct
911 // when the path contains sqlite wildcard characters
912 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
913 new String[]{path + "/%", Integer.toString(path.toString().length() + 1),
914 path.toString() + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800915 }
916
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700917 String[] whereArgs = new String[]{path.toString()};
918 if (mMediaProvider.delete(mObjectsUri, PATH_WHERE, whereArgs) > 0) {
919 if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800920 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700921 String parentPath = path.getParent().toString();
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700922 mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800923 } catch (RemoteException e) {
924 Log.e(TAG, "failed to unhide/rescan for " + path);
925 }
926 }
Mike Lockwood59c777a2010-08-02 10:37:41 -0400927 } else {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700928 Log.i(TAG, "Mediaprovider didn't delete " + path);
Mike Lockwood59c777a2010-08-02 10:37:41 -0400929 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700930 } catch (Exception e) {
931 Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400932 }
933 }
934
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400935 private int[] getObjectReferences(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700936 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
937 if (obj == null)
938 return null;
939 // Translate this handle to the MediaProvider Handle
940 handle = findInMedia(obj.getPath());
941 if (handle == -1)
942 return null;
Mike Lockwood8490e662010-09-09 14:16:22 -0400943 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400944 Cursor c = null;
945 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700946 c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400947 if (c == null) {
948 return null;
949 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700950 ArrayList<Integer> result = new ArrayList<>();
951 while (c.moveToNext()) {
952 // Translate result handles back into handles for this session.
953 String refPath = c.getString(0);
954 MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath);
955 if (refObj != null) {
956 result.add(refObj.getId());
957 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400958 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700959 return result.stream().mapToInt(Integer::intValue).toArray();
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400960 } catch (RemoteException e) {
961 Log.e(TAG, "RemoteException in getObjectList", e);
962 } finally {
963 if (c != null) {
964 c.close();
965 }
966 }
967 return null;
968 }
969
970 private int setObjectReferences(int handle, int[] references) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700971 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
972 if (obj == null)
973 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
974 // Translate this handle to the MediaProvider Handle
975 handle = findInMedia(obj.getPath());
976 if (handle == -1)
977 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood8490e662010-09-09 14:16:22 -0400978 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700979 ArrayList<ContentValues> valuesList = new ArrayList<>();
980 for (int id : references) {
981 // Translate each reference id to the MediaProvider Id
982 MtpStorageManager.MtpObject refObj = mManager.getObject(id);
983 if (refObj == null)
984 continue;
985 int refHandle = findInMedia(refObj.getPath());
986 if (refHandle == -1)
987 continue;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400988 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700989 values.put(Files.FileColumns._ID, refHandle);
990 valuesList.add(values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400991 }
992 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700993 if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400994 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400995 }
996 } catch (RemoteException e) {
997 Log.e(TAG, "RemoteException in setObjectReferences", e);
998 }
Mike Lockwood5367ab62010-08-30 13:23:02 -0400999 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001000 }
1001
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001002 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +00001003 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001004
1005 private native final void native_setup();
1006 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001007}