blob: dc2d17753b1d6bed4065ebb3727da2c59b8e1308 [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;
James Wei9c968fd2018-11-12 21:43:15 +080057import java.util.Objects;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070058import java.util.concurrent.atomic.AtomicBoolean;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070059import java.util.stream.IntStream;
Mike Lockwood5ebac832010-10-12 11:33:47 -040060
Mike Lockwoodd21eac92010-07-03 00:44:05 -040061/**
Jerry Zhangf9c5c252017-08-16 18:07:51 -070062 * MtpDatabase provides an interface for MTP operations that MtpServer can use. To do this, it uses
63 * MtpStorageManager for filesystem operations and MediaProvider to get media metadata. File
64 * operations are also reflected in MediaProvider if possible.
65 * operations
Mike Lockwoodd21eac92010-07-03 00:44:05 -040066 * {@hide}
67 */
Jeff Sharkey60cfad82016-01-05 17:30:57 -070068public class MtpDatabase implements AutoCloseable {
Jerry Zhangf9c5c252017-08-16 18:07:51 -070069 private static final String TAG = MtpDatabase.class.getSimpleName();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040070
Mike Lockwood2837eef2010-08-31 16:25:12 -040071 private final Context mContext;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070072 private final ContentProviderClient mMediaProvider;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040073 private final String mVolumeName;
74 private final Uri mObjectsUri;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070075 private final MediaScanner mMediaScanner;
76
77 private final AtomicBoolean mClosed = new AtomicBoolean();
78 private final CloseGuard mCloseGuard = CloseGuard.get();
79
Jerry Zhangf9c5c252017-08-16 18:07:51 -070080 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040081
Mike Lockwood7d7fb632010-12-01 18:46:23 -050082 // cached property groups for single properties
Jerry Zhangf9c5c252017-08-16 18:07:51 -070083 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty = new HashMap<>();
Mike Lockwood7d7fb632010-12-01 18:46:23 -050084
85 // cached property groups for all properties for a given format
Jerry Zhangf9c5c252017-08-16 18:07:51 -070086 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat = new HashMap<>();
Mike Lockwood2837eef2010-08-31 16:25:12 -040087
Mike Lockwood775de952011-03-05 17:34:11 -050088 // SharedPreferences for writable MTP device properties
89 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040090
Jerry Zhangf9c5c252017-08-16 18:07:51 -070091 // Cached device properties
Mike Lockwood56c85242014-03-07 13:29:08 -080092 private int mBatteryLevel;
93 private int mBatteryScale;
Jerry Zhang13bb2f42016-12-14 15:39:29 -080094 private int mDeviceType;
95
Jerry Zhangf9c5c252017-08-16 18:07:51 -070096 private MtpServer mServer;
97 private MtpStorageManager mManager;
98
99 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
100 private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
101 private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
102 private static final String NO_MEDIA = ".nomedia";
103
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400104 static {
105 System.loadLibrary("media_jni");
106 }
107
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700108 private static final int[] PLAYBACK_FORMATS = {
109 // allow transferring arbitrary files
Mike Lockwoode5211692010-09-08 13:50:45 -0400110 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400111
Mike Lockwood792ec842010-09-09 15:30:10 -0400112 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400113 MtpConstants.FORMAT_TEXT,
114 MtpConstants.FORMAT_HTML,
115 MtpConstants.FORMAT_WAV,
116 MtpConstants.FORMAT_MP3,
117 MtpConstants.FORMAT_MPEG,
118 MtpConstants.FORMAT_EXIF_JPEG,
119 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800120 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400121 MtpConstants.FORMAT_GIF,
122 MtpConstants.FORMAT_JFIF,
123 MtpConstants.FORMAT_PNG,
124 MtpConstants.FORMAT_TIFF,
125 MtpConstants.FORMAT_WMA,
126 MtpConstants.FORMAT_OGG,
127 MtpConstants.FORMAT_AAC,
128 MtpConstants.FORMAT_MP4_CONTAINER,
129 MtpConstants.FORMAT_MP2,
130 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400131 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400132 MtpConstants.FORMAT_WPL_PLAYLIST,
133 MtpConstants.FORMAT_M3U_PLAYLIST,
134 MtpConstants.FORMAT_PLS_PLAYLIST,
135 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800136 MtpConstants.FORMAT_FLAC,
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100137 MtpConstants.FORMAT_DNG,
Chong Zhang6e18cce2017-08-16 11:57:02 -0700138 MtpConstants.FORMAT_HEIF,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700139 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400140
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700141 private static final int[] FILE_PROPERTIES = {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400142 MtpConstants.PROPERTY_STORAGE_ID,
143 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400144 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400145 MtpConstants.PROPERTY_OBJECT_SIZE,
146 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400147 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400148 MtpConstants.PROPERTY_PERSISTENT_UID,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700149 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400150 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800151 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400152 MtpConstants.PROPERTY_DATE_ADDED,
153 };
154
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700155 private static final int[] AUDIO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400156 MtpConstants.PROPERTY_ARTIST,
157 MtpConstants.PROPERTY_ALBUM_NAME,
158 MtpConstants.PROPERTY_ALBUM_ARTIST,
159 MtpConstants.PROPERTY_TRACK,
160 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
161 MtpConstants.PROPERTY_DURATION,
162 MtpConstants.PROPERTY_GENRE,
163 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700164 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
165 MtpConstants.PROPERTY_BITRATE_TYPE,
166 MtpConstants.PROPERTY_AUDIO_BITRATE,
167 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
168 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400169 };
170
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700171 private static final int[] VIDEO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400172 MtpConstants.PROPERTY_ARTIST,
173 MtpConstants.PROPERTY_ALBUM_NAME,
174 MtpConstants.PROPERTY_DURATION,
175 MtpConstants.PROPERTY_DESCRIPTION,
176 };
177
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700178 private static final int[] IMAGE_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400179 MtpConstants.PROPERTY_DESCRIPTION,
180 };
181
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700182 private static final int[] DEVICE_PROPERTIES = {
183 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
184 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
185 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
186 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
187 MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
188 };
189
Mike Lockwoodae078f72010-09-26 12:35:51 -0400190 private int[] getSupportedObjectProperties(int format) {
191 switch (format) {
192 case MtpConstants.FORMAT_MP3:
193 case MtpConstants.FORMAT_WAV:
194 case MtpConstants.FORMAT_WMA:
195 case MtpConstants.FORMAT_OGG:
196 case MtpConstants.FORMAT_AAC:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700197 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
198 Arrays.stream(AUDIO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400199 case MtpConstants.FORMAT_MPEG:
200 case MtpConstants.FORMAT_3GP_CONTAINER:
201 case MtpConstants.FORMAT_WMV:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700202 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
203 Arrays.stream(VIDEO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400204 case MtpConstants.FORMAT_EXIF_JPEG:
205 case MtpConstants.FORMAT_GIF:
206 case MtpConstants.FORMAT_PNG:
207 case MtpConstants.FORMAT_BMP:
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100208 case MtpConstants.FORMAT_DNG:
Chong Zhang6e18cce2017-08-16 11:57:02 -0700209 case MtpConstants.FORMAT_HEIF:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700210 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
211 Arrays.stream(IMAGE_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400212 default:
213 return FILE_PROPERTIES;
214 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400215 }
216
217 private int[] getSupportedDeviceProperties() {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700218 return DEVICE_PROPERTIES;
219 }
220
221 private int[] getSupportedPlaybackFormats() {
222 return PLAYBACK_FORMATS;
223 }
224
225 private int[] getSupportedCaptureFormats() {
226 // no capture formats yet
227 return null;
228 }
229
230 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
231 @Override
232 public void onReceive(Context context, Intent intent) {
233 String action = intent.getAction();
234 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
235 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
236 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
237 if (newLevel != mBatteryLevel) {
238 mBatteryLevel = newLevel;
239 if (mServer != null) {
240 // send device property changed event
241 mServer.sendDevicePropertyChanged(
242 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
243 }
244 }
245 }
246 }
247 };
248
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800249 public MtpDatabase(Context context, String volumeName,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700250 String[] subDirectories) {
251 native_setup();
James Wei9c968fd2018-11-12 21:43:15 +0800252 mContext = Objects.requireNonNull(context);
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800253 mMediaProvider = context.getContentResolver()
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700254 .acquireContentProviderClient(MediaStore.AUTHORITY);
255 mVolumeName = volumeName;
256 mObjectsUri = Files.getMtpObjectsUri(volumeName);
257 mMediaScanner = new MediaScanner(context, mVolumeName);
258 mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
259 @Override
260 public void sendObjectAdded(int id) {
261 if (MtpDatabase.this.mServer != null)
262 MtpDatabase.this.mServer.sendObjectAdded(id);
263 }
264
265 @Override
266 public void sendObjectRemoved(int id) {
267 if (MtpDatabase.this.mServer != null)
268 MtpDatabase.this.mServer.sendObjectRemoved(id);
269 }
Jamese4f680e2018-07-02 17:42:07 +0800270
271 @Override
272 public void sendObjectInfoChanged(int id) {
273 if (MtpDatabase.this.mServer != null)
274 MtpDatabase.this.mServer.sendObjectInfoChanged(id);
275 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700276 }, subDirectories == null ? null : Sets.newHashSet(subDirectories));
277
278 initDeviceProperties(context);
279 mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
280 mCloseGuard.open("close");
281 }
282
283 public void setServer(MtpServer server) {
284 mServer = server;
285 // always unregister before registering
286 try {
287 mContext.unregisterReceiver(mBatteryReceiver);
288 } catch (IllegalArgumentException e) {
289 // wasn't previously registered, ignore
290 }
291 // register for battery notifications when we are connected
292 if (server != null) {
293 mContext.registerReceiver(mBatteryReceiver,
294 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
295 }
296 }
297
James Wei9c968fd2018-11-12 21:43:15 +0800298 public Context getContext() {
299 return mContext;
300 }
301
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700302 @Override
303 public void close() {
304 mManager.close();
305 mCloseGuard.close();
306 if (mClosed.compareAndSet(false, true)) {
307 mMediaScanner.close();
Jerry Zhang484ea672018-03-02 15:40:03 -0800308 if (mMediaProvider != null) {
309 mMediaProvider.close();
310 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700311 native_finalize();
312 }
313 }
314
315 @Override
316 protected void finalize() throws Throwable {
317 try {
318 if (mCloseGuard != null) {
319 mCloseGuard.warnIfOpen();
320 }
321 close();
322 } finally {
323 super.finalize();
324 }
325 }
326
327 public void addStorage(StorageVolume storage) {
328 MtpStorage mtpStorage = mManager.addMtpStorage(storage);
329 mStorageMap.put(storage.getPath(), mtpStorage);
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700330 if (mServer != null) {
331 mServer.addStorage(mtpStorage);
332 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700333 }
334
335 public void removeStorage(StorageVolume storage) {
336 MtpStorage mtpStorage = mStorageMap.get(storage.getPath());
337 if (mtpStorage == null) {
338 return;
339 }
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700340 if (mServer != null) {
341 mServer.removeStorage(mtpStorage);
342 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700343 mManager.removeMtpStorage(mtpStorage);
344 mStorageMap.remove(storage.getPath());
345 }
346
347 private void initDeviceProperties(Context context) {
348 final String devicePropertiesName = "device-properties";
349 mDeviceProperties = context.getSharedPreferences(devicePropertiesName,
350 Context.MODE_PRIVATE);
351 File databaseFile = context.getDatabasePath(devicePropertiesName);
352
353 if (databaseFile.exists()) {
354 // for backward compatibility - read device properties from sqlite database
355 // and migrate them to shared prefs
356 SQLiteDatabase db = null;
357 Cursor c = null;
358 try {
359 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
360 if (db != null) {
361 c = db.query("properties", new String[]{"_id", "code", "value"},
362 null, null, null, null, null);
363 if (c != null) {
364 SharedPreferences.Editor e = mDeviceProperties.edit();
365 while (c.moveToNext()) {
366 String name = c.getString(1);
367 String value = c.getString(2);
368 e.putString(name, value);
369 }
370 e.commit();
371 }
372 }
373 } catch (Exception e) {
374 Log.e(TAG, "failed to migrate device properties", e);
375 } finally {
376 if (c != null) c.close();
377 if (db != null) db.close();
378 }
379 context.deleteDatabase(devicePropertiesName);
380 }
381 }
382
383 private int beginSendObject(String path, int format, int parent, int storageId) {
384 MtpStorageManager.MtpObject parentObj =
385 parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
386 if (parentObj == null) {
387 return -1;
388 }
389
390 Path objPath = Paths.get(path);
391 return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format);
392 }
393
394 private void endSendObject(int handle, boolean succeeded) {
395 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
396 if (obj == null || !mManager.endSendObject(obj, succeeded)) {
397 Log.e(TAG, "Failed to successfully end send object");
398 return;
399 }
400 // Add the new file to MediaProvider
401 if (succeeded) {
402 String path = obj.getPath().toString();
403 int format = obj.getFormat();
404 // Get parent info from MediaProvider, since the id is different from MTP's
405 ContentValues values = new ContentValues();
406 values.put(Files.FileColumns.DATA, path);
407 values.put(Files.FileColumns.FORMAT, format);
408 values.put(Files.FileColumns.SIZE, obj.getSize());
409 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
410 try {
411 if (obj.getParent().isRoot()) {
412 values.put(Files.FileColumns.PARENT, 0);
413 } else {
414 int parentId = findInMedia(obj.getParent().getPath());
415 if (parentId != -1) {
416 values.put(Files.FileColumns.PARENT, parentId);
417 } else {
418 // The parent isn't in MediaProvider. Don't add the new file.
419 return;
420 }
421 }
422
423 Uri uri = mMediaProvider.insert(mObjectsUri, values);
424 if (uri != null) {
425 rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
426 }
427 } catch (RemoteException e) {
428 Log.e(TAG, "RemoteException in beginSendObject", e);
429 }
430 }
431 }
432
433 private void rescanFile(String path, int handle, int format) {
434 // handle abstract playlists separately
435 // they do not exist in the file system so don't use the media scanner here
436 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
437 // extract name from path
438 String name = path;
439 int lastSlash = name.lastIndexOf('/');
440 if (lastSlash >= 0) {
441 name = name.substring(lastSlash + 1);
442 }
443 // strip trailing ".pla" from the name
444 if (name.endsWith(".pla")) {
445 name = name.substring(0, name.length() - 4);
446 }
447
448 ContentValues values = new ContentValues(1);
449 values.put(Audio.Playlists.DATA, path);
450 values.put(Audio.Playlists.NAME, name);
451 values.put(Files.FileColumns.FORMAT, format);
452 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
453 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
454 try {
455 mMediaProvider.insert(
456 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
457 } catch (RemoteException e) {
458 Log.e(TAG, "RemoteException in endSendObject", e);
459 }
460 } else {
461 mMediaScanner.scanMtpFile(path, handle, format);
462 }
463 }
464
465 private int[] getObjectList(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700466 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700467 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700468 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700469 return null;
470 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700471 int[] ret = new int[objs.size()];
472 for (int i = 0; i < objs.size(); i++) {
473 ret[i] = objs.get(i).getId();
474 }
475 return ret;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700476 }
477
478 private int getNumObjects(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700479 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700480 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700481 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700482 return -1;
483 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700484 return objs.size();
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400485 }
486
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900487 private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700488 int groupCode, int depth) {
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400489 // FIXME - implement group support
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700490 if (property == 0) {
491 if (groupCode == 0) {
492 return new MtpPropertyList(MtpConstants.RESPONSE_PARAMETER_NOT_SUPPORTED);
493 }
494 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
495 }
496 if (depth == 0xFFFFFFFF && (handle == 0 || handle == 0xFFFFFFFF)) {
497 // request all objects starting at root
498 handle = 0xFFFFFFFF;
499 depth = 0;
500 }
501 if (!(depth == 0 || depth == 1)) {
502 // we only support depth 0 and 1
503 // depth 0: single object, depth 1: immediate children
504 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
505 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700506 List<MtpStorageManager.MtpObject> objs = null;
507 MtpStorageManager.MtpObject thisObj = null;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700508 if (handle == 0xFFFFFFFF) {
509 // All objects are requested
Jerry Zhang9a018742018-05-10 18:27:13 -0700510 objs = mManager.getObjects(0, format, 0xFFFFFFFF);
511 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700512 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
513 }
514 } else if (handle != 0) {
515 // Add the requested object if format matches
516 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
517 if (obj == null) {
518 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
519 }
520 if (obj.getFormat() == format || format == 0) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700521 thisObj = obj;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700522 }
523 }
524 if (handle == 0 || depth == 1) {
525 if (handle == 0) {
526 handle = 0xFFFFFFFF;
527 }
528 // Get the direct children of root or this object.
Jerry Zhang9a018742018-05-10 18:27:13 -0700529 objs = mManager.getObjects(handle, format,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700530 0xFFFFFFFF);
Jerry Zhang9a018742018-05-10 18:27:13 -0700531 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700532 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
533 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700534 }
535 if (objs == null) {
536 objs = new ArrayList<>();
537 }
538 if (thisObj != null) {
539 objs.add(thisObj);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400540 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400541
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700542 MtpPropertyList ret = new MtpPropertyList(MtpConstants.RESPONSE_OK);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500543 MtpPropertyGroup propertyGroup;
Jerry Zhang9a018742018-05-10 18:27:13 -0700544 for (MtpStorageManager.MtpObject obj : objs) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700545 if (property == 0xffffffff) {
James Weif7f608c2018-08-15 22:23:12 +0800546 if (format == 0 && handle != 0 && handle != 0xffffffff) {
547 // return properties based on the object's format
548 format = obj.getFormat();
549 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700550 // Get all properties supported by this object
James Weif7f608c2018-08-15 22:23:12 +0800551 // format should be the same between get & put
552 propertyGroup = mPropertyGroupsByFormat.get(format);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700553 if (propertyGroup == null) {
554 int[] propertyList = getSupportedObjectProperties(format);
555 propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
556 propertyList);
557 mPropertyGroupsByFormat.put(format, propertyGroup);
558 }
559 } else {
560 // Get this property value
561 final int[] propertyList = new int[]{property};
562 propertyGroup = mPropertyGroupsByProperty.get(property);
563 if (propertyGroup == null) {
564 propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
565 propertyList);
566 mPropertyGroupsByProperty.put(property, propertyGroup);
567 }
Mike Lockwood71827742015-01-23 10:50:08 -0800568 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700569 int err = propertyGroup.getPropertyList(obj, ret);
570 if (err != MtpConstants.RESPONSE_OK) {
571 return new MtpPropertyList(err);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400572 }
573 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700574 return ret;
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400575 }
576
Mike Lockwood5ebac832010-10-12 11:33:47 -0400577 private int renameFile(int handle, String newName) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700578 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
579 if (obj == null) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400580 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
581 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700582 Path oldPath = obj.getPath();
Mike Lockwood73e56d92011-12-01 16:58:41 -0500583
Mike Lockwood5ebac832010-10-12 11:33:47 -0400584 // now rename the file. make sure this succeeds before updating database
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700585 if (!mManager.beginRenameObject(obj, newName))
Mike Lockwood5ebac832010-10-12 11:33:47 -0400586 return MtpConstants.RESPONSE_GENERAL_ERROR;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700587 Path newPath = obj.getPath();
588 boolean success = oldPath.toFile().renameTo(newPath.toFile());
Jerry Zhangd470a1e2018-05-14 12:19:08 -0700589 try {
590 Os.access(oldPath.toString(), OsConstants.F_OK);
591 Os.access(newPath.toString(), OsConstants.F_OK);
592 } catch (ErrnoException e) {
593 // Ignore. Could fail if the metadata was already updated.
594 }
595
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700596 if (!mManager.endRenameObject(obj, oldPath.getFileName().toString(), success)) {
597 Log.e(TAG, "Failed to end rename object");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400598 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400599 if (!success) {
600 return MtpConstants.RESPONSE_GENERAL_ERROR;
601 }
602
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700603 // finally update MediaProvider
Mike Lockwood5ebac832010-10-12 11:33:47 -0400604 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700605 values.put(Files.FileColumns.DATA, newPath.toString());
606 String[] whereArgs = new String[]{oldPath.toString()};
Mike Lockwood5ebac832010-10-12 11:33:47 -0400607 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400608 // note - we are relying on a special case in MediaProvider.update() to update
609 // the paths for all children in the case where this is a directory.
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700610 mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400611 } catch (RemoteException e) {
612 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
613 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400614
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800615 // check if nomedia status changed
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700616 if (obj.isDir()) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800617 // for directories, check if renamed from something hidden to something non-hidden
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700618 if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800619 // directory was unhidden
620 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700621 mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath.toString(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800622 } catch (RemoteException e) {
623 Log.e(TAG, "failed to unhide/rescan for " + newPath);
624 }
625 }
626 } else {
627 // for files, check if renamed from .nomedia to something else
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700628 if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
629 && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800630 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700631 mMediaProvider.call(MediaStore.UNHIDE_CALL,
632 oldPath.getParent().toString(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800633 } catch (RemoteException e) {
634 Log.e(TAG, "failed to unhide/rescan for " + newPath);
635 }
636 }
637 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400638 return MtpConstants.RESPONSE_OK;
639 }
640
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700641 private int beginMoveObject(int handle, int newParent, int newStorage) {
642 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
643 MtpStorageManager.MtpObject parent = newParent == 0 ?
644 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
645 if (obj == null || parent == null)
646 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Jerry Zhang952558d42017-09-26 17:49:52 -0700647
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700648 boolean allowed = mManager.beginMoveObject(obj, parent);
649 return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR;
650 }
651
652 private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage,
653 int objId, boolean success) {
654 MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ?
655 mManager.getStorageRoot(oldStorage) : mManager.getObject(oldParent);
656 MtpStorageManager.MtpObject newParentObj = newParent == 0 ?
657 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
658 MtpStorageManager.MtpObject obj = mManager.getObject(objId);
659 String name = obj.getName();
660 if (newParentObj == null || oldParentObj == null
661 ||!mManager.endMoveObject(oldParentObj, newParentObj, name, success)) {
662 Log.e(TAG, "Failed to end move object");
663 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700664 }
665
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700666 obj = mManager.getObject(objId);
667 if (!success || obj == null)
668 return;
669 // Get parent info from MediaProvider, since the id is different from MTP's
Jerry Zhang952558d42017-09-26 17:49:52 -0700670 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700671 Path path = newParentObj.getPath().resolve(name);
672 Path oldPath = oldParentObj.getPath().resolve(name);
673 values.put(Files.FileColumns.DATA, path.toString());
674 if (obj.getParent().isRoot()) {
675 values.put(Files.FileColumns.PARENT, 0);
676 } else {
677 int parentId = findInMedia(path.getParent());
678 if (parentId != -1) {
679 values.put(Files.FileColumns.PARENT, parentId);
680 } else {
681 // The new parent isn't in MediaProvider, so delete the object instead
682 deleteFromMedia(oldPath, obj.isDir());
683 return;
684 }
685 }
686 // update MediaProvider
687 Cursor c = null;
688 String[] whereArgs = new String[]{oldPath.toString()};
Jerry Zhang952558d42017-09-26 17:49:52 -0700689 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700690 int parentId = -1;
691 if (!oldParentObj.isRoot()) {
692 parentId = findInMedia(oldPath.getParent());
693 }
694 if (oldParentObj.isRoot() || parentId != -1) {
695 // Old parent exists in MediaProvider - perform a move
696 // note - we are relying on a special case in MediaProvider.update() to update
697 // the paths for all children in the case where this is a directory.
698 mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
699 } else {
700 // Old parent doesn't exist - add the object
701 values.put(Files.FileColumns.FORMAT, obj.getFormat());
702 values.put(Files.FileColumns.SIZE, obj.getSize());
703 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
704 Uri uri = mMediaProvider.insert(mObjectsUri, values);
705 if (uri != null) {
706 rescanFile(path.toString(),
707 Integer.parseInt(uri.getPathSegments().get(2)), obj.getFormat());
708 }
709 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700710 } catch (RemoteException e) {
711 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
712 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700713 }
714
715 private int beginCopyObject(int handle, int newParent, int newStorage) {
716 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
717 MtpStorageManager.MtpObject parent = newParent == 0 ?
718 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
719 if (obj == null || parent == null)
720 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
721 return mManager.beginCopyObject(obj, parent);
722 }
723
724 private void endCopyObject(int handle, boolean success) {
725 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
726 if (obj == null || !mManager.endCopyObject(obj, success)) {
727 Log.e(TAG, "Failed to end copy object");
728 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700729 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700730 if (!success) {
731 return;
732 }
733 String path = obj.getPath().toString();
734 int format = obj.getFormat();
735 // Get parent info from MediaProvider, since the id is different from MTP's
736 ContentValues values = new ContentValues();
737 values.put(Files.FileColumns.DATA, path);
738 values.put(Files.FileColumns.FORMAT, format);
739 values.put(Files.FileColumns.SIZE, obj.getSize());
740 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
741 try {
742 if (obj.getParent().isRoot()) {
743 values.put(Files.FileColumns.PARENT, 0);
744 } else {
745 int parentId = findInMedia(obj.getParent().getPath());
746 if (parentId != -1) {
747 values.put(Files.FileColumns.PARENT, parentId);
748 } else {
749 // The parent isn't in MediaProvider. Don't add the new file.
750 return;
751 }
752 }
753 if (obj.isDir()) {
754 mMediaScanner.scanDirectories(new String[]{path});
755 } else {
756 Uri uri = mMediaProvider.insert(mObjectsUri, values);
757 if (uri != null) {
758 rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
759 }
760 }
761 } catch (RemoteException e) {
762 Log.e(TAG, "RemoteException in beginSendObject", e);
763 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700764 }
765
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400766 private int setObjectProperty(int handle, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700767 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400768 switch (property) {
769 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
770 return renameFile(handle, stringValue);
771
772 default:
773 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
774 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400775 }
776
777 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400778 switch (property) {
779 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
780 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500781 // writable string properties kept in shared preferences
782 String value = mDeviceProperties.getString(Integer.toString(property), "");
783 int length = value.length();
784 if (length > 255) {
785 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400786 }
Mike Lockwood775de952011-03-05 17:34:11 -0500787 value.getChars(0, length, outStringValue, 0);
788 outStringValue[length] = 0;
789 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800790 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
791 // use screen size as max image size
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700792 Display display = ((WindowManager) mContext.getSystemService(
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800793 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700794 int width = display.getMaximumSizeDimension();
795 int height = display.getMaximumSizeDimension();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700796 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800797 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
798 outStringValue[imageSize.length()] = 0;
799 return MtpConstants.RESPONSE_OK;
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800800 case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
801 outIntValue[0] = mDeviceType;
802 return MtpConstants.RESPONSE_OK;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700803 case MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL:
804 outIntValue[0] = mBatteryLevel;
805 outIntValue[1] = mBatteryScale;
806 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800807 default:
808 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
809 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400810 }
811
812 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400813 switch (property) {
814 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
815 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500816 // writable string properties kept in shared prefs
817 SharedPreferences.Editor e = mDeviceProperties.edit();
818 e.putString(Integer.toString(property), stringValue);
819 return (e.commit() ? MtpConstants.RESPONSE_OK
820 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400821 }
822
823 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
824 }
825
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400826 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700827 char[] outName, long[] outCreatedModified) {
828 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
829 if (obj == null) {
830 return false;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400831 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700832 outStorageFormatParent[0] = obj.getStorageId();
833 outStorageFormatParent[1] = obj.getFormat();
834 outStorageFormatParent[2] = obj.getParent().isRoot() ? 0 : obj.getParent().getId();
835
836 int nameLen = Integer.min(obj.getName().length(), 255);
837 obj.getName().getChars(0, nameLen, outName, 0);
838 outName[nameLen] = 0;
839
840 outCreatedModified[0] = obj.getModifiedTime();
841 outCreatedModified[1] = obj.getModifiedTime();
842 return true;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400843 }
844
Mike Lockwood365e03e2010-12-08 16:08:01 -0800845 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700846 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
847 if (obj == null) {
848 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood01788562010-10-11 11:22:19 -0400849 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700850
851 String path = obj.getPath().toString();
852 int pathLen = Integer.min(path.length(), 4096);
853 path.getChars(0, pathLen, outFilePath, 0);
854 outFilePath[pathLen] = 0;
855
856 outFileLengthFormat[0] = obj.getSize();
857 outFileLengthFormat[1] = obj.getFormat();
858 return MtpConstants.RESPONSE_OK;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400859 }
860
Mike Lockwood71827742015-01-23 10:50:08 -0800861 private int getObjectFormat(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700862 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
863 if (obj == null) {
Mike Lockwood71827742015-01-23 10:50:08 -0800864 return -1;
Mike Lockwood71827742015-01-23 10:50:08 -0800865 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700866 return obj.getFormat();
Mike Lockwood71827742015-01-23 10:50:08 -0800867 }
868
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700869 private int beginDeleteObject(int handle) {
870 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
871 if (obj == null) {
872 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
873 }
874 if (!mManager.beginRemoveObject(obj)) {
875 return MtpConstants.RESPONSE_GENERAL_ERROR;
876 }
877 return MtpConstants.RESPONSE_OK;
878 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800879
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700880 private void endDeleteObject(int handle, boolean success) {
881 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
882 if (obj == null) {
883 return;
884 }
885 if (!mManager.endRemoveObject(obj, success))
886 Log.e(TAG, "Failed to end remove object");
887 if (success)
888 deleteFromMedia(obj.getPath(), obj.isDir());
889 }
890
891 private int findInMedia(Path path) {
892 int ret = -1;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800893 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400894 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700895 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
896 new String[]{path.toString()}, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800897 if (c != null && c.moveToNext()) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700898 ret = c.getInt(0);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800899 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700900 } catch (RemoteException e) {
901 Log.e(TAG, "Error finding " + path + " in MediaProvider");
902 } finally {
903 if (c != null)
904 c.close();
905 }
906 return ret;
907 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800908
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700909 private void deleteFromMedia(Path path, boolean isDir) {
910 try {
911 // Delete the object(s) from MediaProvider, but ignore errors.
912 if (isDir) {
Mike Lockwood55f808c2010-12-14 13:14:29 -0800913 // recursive case - delete all children first
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700914 mMediaProvider.delete(mObjectsUri,
915 // the 'like' makes it use the index, the 'lower()' makes it correct
916 // when the path contains sqlite wildcard characters
917 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
918 new String[]{path + "/%", Integer.toString(path.toString().length() + 1),
919 path.toString() + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800920 }
921
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700922 String[] whereArgs = new String[]{path.toString()};
923 if (mMediaProvider.delete(mObjectsUri, PATH_WHERE, whereArgs) > 0) {
924 if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800925 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700926 String parentPath = path.getParent().toString();
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700927 mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800928 } catch (RemoteException e) {
929 Log.e(TAG, "failed to unhide/rescan for " + path);
930 }
931 }
Mike Lockwood59c777a2010-08-02 10:37:41 -0400932 } else {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700933 Log.i(TAG, "Mediaprovider didn't delete " + path);
Mike Lockwood59c777a2010-08-02 10:37:41 -0400934 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700935 } catch (Exception e) {
936 Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400937 }
938 }
939
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400940 private int[] getObjectReferences(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700941 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
942 if (obj == null)
943 return null;
944 // Translate this handle to the MediaProvider Handle
945 handle = findInMedia(obj.getPath());
946 if (handle == -1)
947 return null;
Mike Lockwood8490e662010-09-09 14:16:22 -0400948 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400949 Cursor c = null;
950 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700951 c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400952 if (c == null) {
953 return null;
954 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700955 ArrayList<Integer> result = new ArrayList<>();
956 while (c.moveToNext()) {
957 // Translate result handles back into handles for this session.
958 String refPath = c.getString(0);
959 MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath);
960 if (refObj != null) {
961 result.add(refObj.getId());
962 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400963 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700964 return result.stream().mapToInt(Integer::intValue).toArray();
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400965 } catch (RemoteException e) {
966 Log.e(TAG, "RemoteException in getObjectList", e);
967 } finally {
968 if (c != null) {
969 c.close();
970 }
971 }
972 return null;
973 }
974
975 private int setObjectReferences(int handle, int[] references) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700976 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
977 if (obj == null)
978 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
979 // Translate this handle to the MediaProvider Handle
980 handle = findInMedia(obj.getPath());
981 if (handle == -1)
982 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood8490e662010-09-09 14:16:22 -0400983 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700984 ArrayList<ContentValues> valuesList = new ArrayList<>();
985 for (int id : references) {
986 // Translate each reference id to the MediaProvider Id
987 MtpStorageManager.MtpObject refObj = mManager.getObject(id);
988 if (refObj == null)
989 continue;
990 int refHandle = findInMedia(refObj.getPath());
991 if (refHandle == -1)
992 continue;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400993 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700994 values.put(Files.FileColumns._ID, refHandle);
995 valuesList.add(values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400996 }
997 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700998 if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400999 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001000 }
1001 } catch (RemoteException e) {
1002 Log.e(TAG, "RemoteException in setObjectReferences", e);
1003 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001004 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001005 }
1006
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001007 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +00001008 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001009
1010 private native final void native_setup();
1011 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001012}