blob: 6361fd81d2af4f7baec48d7b80f4af60b2c0b121 [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 Lockwoodd21eac92010-07-03 00:44:05 -040028import android.net.Uri;
Mike Lockwood56c85242014-03-07 13:29:08 -080029import android.os.BatteryManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040030import android.os.RemoteException;
Jerry Zhang13bb2f42016-12-14 15:39:29 -080031import android.os.SystemProperties;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070032import android.os.storage.StorageVolume;
Mike Lockwooda3156052010-11-20 12:28:27 -050033import android.provider.MediaStore;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040034import android.provider.MediaStore.Files;
Jerry Zhangd470a1e2018-05-14 12:19:08 -070035import android.system.ErrnoException;
36import android.system.Os;
37import android.system.OsConstants;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040038import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080039import android.view.Display;
40import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040041
Jeff Sharkeyff200952019-03-24 12:50:51 -060042import com.android.internal.annotations.VisibleForNative;
43
Jeff Sharkey60cfad82016-01-05 17:30:57 -070044import dalvik.system.CloseGuard;
45
Jerry Zhangf9c5c252017-08-16 18:07:51 -070046import com.google.android.collect.Sets;
47
Mike Lockwood5ebac832010-10-12 11:33:47 -040048import java.io.File;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070049import java.nio.file.Path;
50import java.nio.file.Paths;
51import java.util.ArrayList;
52import java.util.Arrays;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050053import java.util.HashMap;
Jerry Zhang9a018742018-05-10 18:27:13 -070054import java.util.List;
dujin.chafe464a72011-11-22 12:13:33 +090055import java.util.Locale;
James Wei9c968fd2018-11-12 21:43:15 +080056import java.util.Objects;
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
75 private final AtomicBoolean mClosed = new AtomicBoolean();
76 private final CloseGuard mCloseGuard = CloseGuard.get();
77
Jerry Zhangf9c5c252017-08-16 18:07:51 -070078 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040079
Mike Lockwood7d7fb632010-12-01 18:46:23 -050080 // cached property groups for single properties
Jerry Zhangf9c5c252017-08-16 18:07:51 -070081 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty = new HashMap<>();
Mike Lockwood7d7fb632010-12-01 18:46:23 -050082
83 // cached property groups for all properties for a given format
Jerry Zhangf9c5c252017-08-16 18:07:51 -070084 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat = new HashMap<>();
Mike Lockwood2837eef2010-08-31 16:25:12 -040085
Mike Lockwood775de952011-03-05 17:34:11 -050086 // SharedPreferences for writable MTP device properties
87 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040088
Jerry Zhangf9c5c252017-08-16 18:07:51 -070089 // Cached device properties
Mike Lockwood56c85242014-03-07 13:29:08 -080090 private int mBatteryLevel;
91 private int mBatteryScale;
Jerry Zhang13bb2f42016-12-14 15:39:29 -080092 private int mDeviceType;
93
Jerry Zhangf9c5c252017-08-16 18:07:51 -070094 private MtpServer mServer;
95 private MtpStorageManager mManager;
96
97 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
98 private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
99 private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
100 private static final String NO_MEDIA = ".nomedia";
101
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400102 static {
103 System.loadLibrary("media_jni");
104 }
105
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700106 private static final int[] PLAYBACK_FORMATS = {
107 // allow transferring arbitrary files
Mike Lockwoode5211692010-09-08 13:50:45 -0400108 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400109
Mike Lockwood792ec842010-09-09 15:30:10 -0400110 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400111 MtpConstants.FORMAT_TEXT,
112 MtpConstants.FORMAT_HTML,
113 MtpConstants.FORMAT_WAV,
114 MtpConstants.FORMAT_MP3,
115 MtpConstants.FORMAT_MPEG,
116 MtpConstants.FORMAT_EXIF_JPEG,
117 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800118 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400119 MtpConstants.FORMAT_GIF,
120 MtpConstants.FORMAT_JFIF,
121 MtpConstants.FORMAT_PNG,
122 MtpConstants.FORMAT_TIFF,
123 MtpConstants.FORMAT_WMA,
124 MtpConstants.FORMAT_OGG,
125 MtpConstants.FORMAT_AAC,
126 MtpConstants.FORMAT_MP4_CONTAINER,
127 MtpConstants.FORMAT_MP2,
128 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400129 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400130 MtpConstants.FORMAT_WPL_PLAYLIST,
131 MtpConstants.FORMAT_M3U_PLAYLIST,
132 MtpConstants.FORMAT_PLS_PLAYLIST,
133 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800134 MtpConstants.FORMAT_FLAC,
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100135 MtpConstants.FORMAT_DNG,
Chong Zhang6e18cce2017-08-16 11:57:02 -0700136 MtpConstants.FORMAT_HEIF,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700137 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400138
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700139 private static final int[] FILE_PROPERTIES = {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400140 MtpConstants.PROPERTY_STORAGE_ID,
141 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400142 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400143 MtpConstants.PROPERTY_OBJECT_SIZE,
144 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400145 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400146 MtpConstants.PROPERTY_PERSISTENT_UID,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700147 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400148 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800149 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400150 MtpConstants.PROPERTY_DATE_ADDED,
151 };
152
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700153 private static final int[] AUDIO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400154 MtpConstants.PROPERTY_ARTIST,
155 MtpConstants.PROPERTY_ALBUM_NAME,
156 MtpConstants.PROPERTY_ALBUM_ARTIST,
157 MtpConstants.PROPERTY_TRACK,
158 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
159 MtpConstants.PROPERTY_DURATION,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400160 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700161 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
162 MtpConstants.PROPERTY_BITRATE_TYPE,
163 MtpConstants.PROPERTY_AUDIO_BITRATE,
164 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
165 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400166 };
167
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700168 private static final int[] VIDEO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400169 MtpConstants.PROPERTY_ARTIST,
170 MtpConstants.PROPERTY_ALBUM_NAME,
171 MtpConstants.PROPERTY_DURATION,
172 MtpConstants.PROPERTY_DESCRIPTION,
173 };
174
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700175 private static final int[] IMAGE_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400176 MtpConstants.PROPERTY_DESCRIPTION,
177 };
178
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700179 private static final int[] DEVICE_PROPERTIES = {
180 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
181 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
182 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
183 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
184 MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
185 };
186
Jeff Sharkeyff200952019-03-24 12:50:51 -0600187 @VisibleForNative
Mike Lockwoodae078f72010-09-26 12:35:51 -0400188 private int[] getSupportedObjectProperties(int format) {
189 switch (format) {
190 case MtpConstants.FORMAT_MP3:
191 case MtpConstants.FORMAT_WAV:
192 case MtpConstants.FORMAT_WMA:
193 case MtpConstants.FORMAT_OGG:
194 case MtpConstants.FORMAT_AAC:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700195 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
196 Arrays.stream(AUDIO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400197 case MtpConstants.FORMAT_MPEG:
198 case MtpConstants.FORMAT_3GP_CONTAINER:
199 case MtpConstants.FORMAT_WMV:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700200 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
201 Arrays.stream(VIDEO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400202 case MtpConstants.FORMAT_EXIF_JPEG:
203 case MtpConstants.FORMAT_GIF:
204 case MtpConstants.FORMAT_PNG:
205 case MtpConstants.FORMAT_BMP:
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100206 case MtpConstants.FORMAT_DNG:
Chong Zhang6e18cce2017-08-16 11:57:02 -0700207 case MtpConstants.FORMAT_HEIF:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700208 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
209 Arrays.stream(IMAGE_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400210 default:
211 return FILE_PROPERTIES;
212 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400213 }
214
Jeff Sharkeyff200952019-03-24 12:50:51 -0600215 public static Uri getObjectPropertiesUri(int format, String volumeName) {
216 switch (format) {
217 case MtpConstants.FORMAT_MP3:
218 case MtpConstants.FORMAT_WAV:
219 case MtpConstants.FORMAT_WMA:
220 case MtpConstants.FORMAT_OGG:
221 case MtpConstants.FORMAT_AAC:
222 return MediaStore.Audio.Media.getContentUri(volumeName);
223 case MtpConstants.FORMAT_MPEG:
224 case MtpConstants.FORMAT_3GP_CONTAINER:
225 case MtpConstants.FORMAT_WMV:
226 return MediaStore.Video.Media.getContentUri(volumeName);
227 case MtpConstants.FORMAT_EXIF_JPEG:
228 case MtpConstants.FORMAT_GIF:
229 case MtpConstants.FORMAT_PNG:
230 case MtpConstants.FORMAT_BMP:
231 case MtpConstants.FORMAT_DNG:
232 case MtpConstants.FORMAT_HEIF:
233 return MediaStore.Images.Media.getContentUri(volumeName);
234 default:
235 return MediaStore.Files.getContentUri(volumeName);
236 }
237 }
238
239 @VisibleForNative
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400240 private int[] getSupportedDeviceProperties() {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700241 return DEVICE_PROPERTIES;
242 }
243
Jeff Sharkeyff200952019-03-24 12:50:51 -0600244 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700245 private int[] getSupportedPlaybackFormats() {
246 return PLAYBACK_FORMATS;
247 }
248
Jeff Sharkeyff200952019-03-24 12:50:51 -0600249 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700250 private int[] getSupportedCaptureFormats() {
251 // no capture formats yet
252 return null;
253 }
254
255 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
256 @Override
257 public void onReceive(Context context, Intent intent) {
258 String action = intent.getAction();
259 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
260 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
261 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
262 if (newLevel != mBatteryLevel) {
263 mBatteryLevel = newLevel;
264 if (mServer != null) {
265 // send device property changed event
266 mServer.sendDevicePropertyChanged(
267 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
268 }
269 }
270 }
271 }
272 };
273
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800274 public MtpDatabase(Context context, String volumeName,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700275 String[] subDirectories) {
276 native_setup();
James Wei9c968fd2018-11-12 21:43:15 +0800277 mContext = Objects.requireNonNull(context);
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800278 mMediaProvider = context.getContentResolver()
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700279 .acquireContentProviderClient(MediaStore.AUTHORITY);
280 mVolumeName = volumeName;
281 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700282 mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
283 @Override
284 public void sendObjectAdded(int id) {
285 if (MtpDatabase.this.mServer != null)
286 MtpDatabase.this.mServer.sendObjectAdded(id);
287 }
288
289 @Override
290 public void sendObjectRemoved(int id) {
291 if (MtpDatabase.this.mServer != null)
292 MtpDatabase.this.mServer.sendObjectRemoved(id);
293 }
Jamese4f680e2018-07-02 17:42:07 +0800294
295 @Override
296 public void sendObjectInfoChanged(int id) {
297 if (MtpDatabase.this.mServer != null)
298 MtpDatabase.this.mServer.sendObjectInfoChanged(id);
299 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700300 }, subDirectories == null ? null : Sets.newHashSet(subDirectories));
301
302 initDeviceProperties(context);
303 mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
304 mCloseGuard.open("close");
305 }
306
307 public void setServer(MtpServer server) {
308 mServer = server;
309 // always unregister before registering
310 try {
311 mContext.unregisterReceiver(mBatteryReceiver);
312 } catch (IllegalArgumentException e) {
313 // wasn't previously registered, ignore
314 }
315 // register for battery notifications when we are connected
316 if (server != null) {
317 mContext.registerReceiver(mBatteryReceiver,
318 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
319 }
320 }
321
James Wei9c968fd2018-11-12 21:43:15 +0800322 public Context getContext() {
323 return mContext;
324 }
325
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700326 @Override
327 public void close() {
328 mManager.close();
329 mCloseGuard.close();
330 if (mClosed.compareAndSet(false, true)) {
Jerry Zhang484ea672018-03-02 15:40:03 -0800331 if (mMediaProvider != null) {
332 mMediaProvider.close();
333 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700334 native_finalize();
335 }
336 }
337
338 @Override
339 protected void finalize() throws Throwable {
340 try {
341 if (mCloseGuard != null) {
342 mCloseGuard.warnIfOpen();
343 }
344 close();
345 } finally {
346 super.finalize();
347 }
348 }
349
350 public void addStorage(StorageVolume storage) {
351 MtpStorage mtpStorage = mManager.addMtpStorage(storage);
352 mStorageMap.put(storage.getPath(), mtpStorage);
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700353 if (mServer != null) {
354 mServer.addStorage(mtpStorage);
355 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700356 }
357
358 public void removeStorage(StorageVolume storage) {
359 MtpStorage mtpStorage = mStorageMap.get(storage.getPath());
360 if (mtpStorage == null) {
361 return;
362 }
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700363 if (mServer != null) {
364 mServer.removeStorage(mtpStorage);
365 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700366 mManager.removeMtpStorage(mtpStorage);
367 mStorageMap.remove(storage.getPath());
368 }
369
370 private void initDeviceProperties(Context context) {
371 final String devicePropertiesName = "device-properties";
372 mDeviceProperties = context.getSharedPreferences(devicePropertiesName,
373 Context.MODE_PRIVATE);
374 File databaseFile = context.getDatabasePath(devicePropertiesName);
375
376 if (databaseFile.exists()) {
377 // for backward compatibility - read device properties from sqlite database
378 // and migrate them to shared prefs
379 SQLiteDatabase db = null;
380 Cursor c = null;
381 try {
382 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
383 if (db != null) {
384 c = db.query("properties", new String[]{"_id", "code", "value"},
385 null, null, null, null, null);
386 if (c != null) {
387 SharedPreferences.Editor e = mDeviceProperties.edit();
388 while (c.moveToNext()) {
389 String name = c.getString(1);
390 String value = c.getString(2);
391 e.putString(name, value);
392 }
393 e.commit();
394 }
395 }
396 } catch (Exception e) {
397 Log.e(TAG, "failed to migrate device properties", e);
398 } finally {
399 if (c != null) c.close();
400 if (db != null) db.close();
401 }
402 context.deleteDatabase(devicePropertiesName);
403 }
404 }
405
Jeff Sharkeyff200952019-03-24 12:50:51 -0600406 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700407 private int beginSendObject(String path, int format, int parent, int storageId) {
408 MtpStorageManager.MtpObject parentObj =
409 parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
410 if (parentObj == null) {
411 return -1;
412 }
413
414 Path objPath = Paths.get(path);
415 return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format);
416 }
417
Jeff Sharkeyff200952019-03-24 12:50:51 -0600418 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700419 private void endSendObject(int handle, boolean succeeded) {
420 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
421 if (obj == null || !mManager.endSendObject(obj, succeeded)) {
422 Log.e(TAG, "Failed to successfully end send object");
423 return;
424 }
425 // Add the new file to MediaProvider
426 if (succeeded) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600427 MediaStore.scanFile(mContext, obj.getPath().toFile());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700428 }
429 }
430
Jeff Sharkeyff200952019-03-24 12:50:51 -0600431 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700432 private void rescanFile(String path, int handle, int format) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600433 MediaStore.scanFile(mContext, new File(path));
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700434 }
435
Jeff Sharkeyff200952019-03-24 12:50:51 -0600436 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700437 private int[] getObjectList(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700438 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700439 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700440 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700441 return null;
442 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700443 int[] ret = new int[objs.size()];
444 for (int i = 0; i < objs.size(); i++) {
445 ret[i] = objs.get(i).getId();
446 }
447 return ret;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700448 }
449
Jeff Sharkeyff200952019-03-24 12:50:51 -0600450 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700451 private int getNumObjects(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700452 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700453 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700454 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700455 return -1;
456 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700457 return objs.size();
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400458 }
459
Jeff Sharkeyff200952019-03-24 12:50:51 -0600460 @VisibleForNative
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900461 private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700462 int groupCode, int depth) {
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400463 // FIXME - implement group support
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700464 if (property == 0) {
465 if (groupCode == 0) {
466 return new MtpPropertyList(MtpConstants.RESPONSE_PARAMETER_NOT_SUPPORTED);
467 }
468 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
469 }
470 if (depth == 0xFFFFFFFF && (handle == 0 || handle == 0xFFFFFFFF)) {
471 // request all objects starting at root
472 handle = 0xFFFFFFFF;
473 depth = 0;
474 }
475 if (!(depth == 0 || depth == 1)) {
476 // we only support depth 0 and 1
477 // depth 0: single object, depth 1: immediate children
478 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
479 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700480 List<MtpStorageManager.MtpObject> objs = null;
481 MtpStorageManager.MtpObject thisObj = null;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700482 if (handle == 0xFFFFFFFF) {
483 // All objects are requested
Jerry Zhang9a018742018-05-10 18:27:13 -0700484 objs = mManager.getObjects(0, format, 0xFFFFFFFF);
485 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700486 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
487 }
488 } else if (handle != 0) {
489 // Add the requested object if format matches
490 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
491 if (obj == null) {
492 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
493 }
494 if (obj.getFormat() == format || format == 0) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700495 thisObj = obj;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700496 }
497 }
498 if (handle == 0 || depth == 1) {
499 if (handle == 0) {
500 handle = 0xFFFFFFFF;
501 }
502 // Get the direct children of root or this object.
Jerry Zhang9a018742018-05-10 18:27:13 -0700503 objs = mManager.getObjects(handle, format,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700504 0xFFFFFFFF);
Jerry Zhang9a018742018-05-10 18:27:13 -0700505 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700506 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
507 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700508 }
509 if (objs == null) {
510 objs = new ArrayList<>();
511 }
512 if (thisObj != null) {
513 objs.add(thisObj);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400514 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400515
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700516 MtpPropertyList ret = new MtpPropertyList(MtpConstants.RESPONSE_OK);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500517 MtpPropertyGroup propertyGroup;
Jerry Zhang9a018742018-05-10 18:27:13 -0700518 for (MtpStorageManager.MtpObject obj : objs) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700519 if (property == 0xffffffff) {
James Weif7f608c2018-08-15 22:23:12 +0800520 if (format == 0 && handle != 0 && handle != 0xffffffff) {
521 // return properties based on the object's format
522 format = obj.getFormat();
523 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700524 // Get all properties supported by this object
James Weif7f608c2018-08-15 22:23:12 +0800525 // format should be the same between get & put
526 propertyGroup = mPropertyGroupsByFormat.get(format);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700527 if (propertyGroup == null) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600528 final int[] propertyList = getSupportedObjectProperties(format);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700529 propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
530 propertyList);
531 mPropertyGroupsByFormat.put(format, propertyGroup);
532 }
533 } else {
534 // Get this property value
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700535 propertyGroup = mPropertyGroupsByProperty.get(property);
536 if (propertyGroup == null) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600537 final int[] propertyList = new int[]{property};
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700538 propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
539 propertyList);
540 mPropertyGroupsByProperty.put(property, propertyGroup);
541 }
Mike Lockwood71827742015-01-23 10:50:08 -0800542 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700543 int err = propertyGroup.getPropertyList(obj, ret);
544 if (err != MtpConstants.RESPONSE_OK) {
545 return new MtpPropertyList(err);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400546 }
547 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700548 return ret;
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400549 }
550
Mike Lockwood5ebac832010-10-12 11:33:47 -0400551 private int renameFile(int handle, String newName) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700552 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
553 if (obj == null) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400554 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
555 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700556 Path oldPath = obj.getPath();
Mike Lockwood73e56d92011-12-01 16:58:41 -0500557
Mike Lockwood5ebac832010-10-12 11:33:47 -0400558 // now rename the file. make sure this succeeds before updating database
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700559 if (!mManager.beginRenameObject(obj, newName))
Mike Lockwood5ebac832010-10-12 11:33:47 -0400560 return MtpConstants.RESPONSE_GENERAL_ERROR;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700561 Path newPath = obj.getPath();
562 boolean success = oldPath.toFile().renameTo(newPath.toFile());
Jerry Zhangd470a1e2018-05-14 12:19:08 -0700563 try {
564 Os.access(oldPath.toString(), OsConstants.F_OK);
565 Os.access(newPath.toString(), OsConstants.F_OK);
566 } catch (ErrnoException e) {
567 // Ignore. Could fail if the metadata was already updated.
568 }
569
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700570 if (!mManager.endRenameObject(obj, oldPath.getFileName().toString(), success)) {
571 Log.e(TAG, "Failed to end rename object");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400572 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400573 if (!success) {
574 return MtpConstants.RESPONSE_GENERAL_ERROR;
575 }
576
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700577 // finally update MediaProvider
Mike Lockwood5ebac832010-10-12 11:33:47 -0400578 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700579 values.put(Files.FileColumns.DATA, newPath.toString());
580 String[] whereArgs = new String[]{oldPath.toString()};
Mike Lockwood5ebac832010-10-12 11:33:47 -0400581 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400582 // note - we are relying on a special case in MediaProvider.update() to update
583 // the paths for all children in the case where this is a directory.
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700584 mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400585 } catch (RemoteException e) {
586 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
587 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400588
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800589 // check if nomedia status changed
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700590 if (obj.isDir()) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800591 // for directories, check if renamed from something hidden to something non-hidden
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700592 if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600593 MediaStore.scanFile(mContext, newPath.toFile());
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800594 }
595 } else {
596 // for files, check if renamed from .nomedia to something else
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700597 if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
598 && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600599 MediaStore.scanFile(mContext, newPath.getParent().toFile());
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800600 }
601 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400602 return MtpConstants.RESPONSE_OK;
603 }
604
Jeff Sharkeyff200952019-03-24 12:50:51 -0600605 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700606 private int beginMoveObject(int handle, int newParent, int newStorage) {
607 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
608 MtpStorageManager.MtpObject parent = newParent == 0 ?
609 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
610 if (obj == null || parent == null)
611 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Jerry Zhang952558d42017-09-26 17:49:52 -0700612
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700613 boolean allowed = mManager.beginMoveObject(obj, parent);
614 return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR;
615 }
616
Jeff Sharkeyff200952019-03-24 12:50:51 -0600617 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700618 private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage,
619 int objId, boolean success) {
620 MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ?
621 mManager.getStorageRoot(oldStorage) : mManager.getObject(oldParent);
622 MtpStorageManager.MtpObject newParentObj = newParent == 0 ?
623 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
624 MtpStorageManager.MtpObject obj = mManager.getObject(objId);
625 String name = obj.getName();
626 if (newParentObj == null || oldParentObj == null
627 ||!mManager.endMoveObject(oldParentObj, newParentObj, name, success)) {
628 Log.e(TAG, "Failed to end move object");
629 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700630 }
631
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700632 obj = mManager.getObject(objId);
633 if (!success || obj == null)
634 return;
635 // Get parent info from MediaProvider, since the id is different from MTP's
Jerry Zhang952558d42017-09-26 17:49:52 -0700636 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700637 Path path = newParentObj.getPath().resolve(name);
638 Path oldPath = oldParentObj.getPath().resolve(name);
639 values.put(Files.FileColumns.DATA, path.toString());
640 if (obj.getParent().isRoot()) {
641 values.put(Files.FileColumns.PARENT, 0);
642 } else {
643 int parentId = findInMedia(path.getParent());
644 if (parentId != -1) {
645 values.put(Files.FileColumns.PARENT, parentId);
646 } else {
647 // The new parent isn't in MediaProvider, so delete the object instead
648 deleteFromMedia(oldPath, obj.isDir());
649 return;
650 }
651 }
652 // update MediaProvider
653 Cursor c = null;
654 String[] whereArgs = new String[]{oldPath.toString()};
Jerry Zhang952558d42017-09-26 17:49:52 -0700655 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700656 int parentId = -1;
657 if (!oldParentObj.isRoot()) {
658 parentId = findInMedia(oldPath.getParent());
659 }
660 if (oldParentObj.isRoot() || parentId != -1) {
661 // Old parent exists in MediaProvider - perform a move
662 // note - we are relying on a special case in MediaProvider.update() to update
663 // the paths for all children in the case where this is a directory.
664 mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
665 } else {
666 // Old parent doesn't exist - add the object
Jeff Sharkeyff200952019-03-24 12:50:51 -0600667 MediaStore.scanFile(mContext, path.toFile());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700668 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700669 } catch (RemoteException e) {
670 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
671 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700672 }
673
Jeff Sharkeyff200952019-03-24 12:50:51 -0600674 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700675 private int beginCopyObject(int handle, int newParent, int newStorage) {
676 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
677 MtpStorageManager.MtpObject parent = newParent == 0 ?
678 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
679 if (obj == null || parent == null)
680 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
681 return mManager.beginCopyObject(obj, parent);
682 }
683
Jeff Sharkeyff200952019-03-24 12:50:51 -0600684 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700685 private void endCopyObject(int handle, boolean success) {
686 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
687 if (obj == null || !mManager.endCopyObject(obj, success)) {
688 Log.e(TAG, "Failed to end copy object");
689 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700690 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700691 if (!success) {
692 return;
693 }
Jeff Sharkeyff200952019-03-24 12:50:51 -0600694 MediaStore.scanFile(mContext, obj.getPath().toFile());
Jerry Zhang952558d42017-09-26 17:49:52 -0700695 }
696
Jeff Sharkeyff200952019-03-24 12:50:51 -0600697 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400698 private int setObjectProperty(int handle, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700699 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400700 switch (property) {
701 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
702 return renameFile(handle, stringValue);
703
704 default:
705 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
706 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400707 }
708
Jeff Sharkeyff200952019-03-24 12:50:51 -0600709 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400710 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400711 switch (property) {
712 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
713 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500714 // writable string properties kept in shared preferences
715 String value = mDeviceProperties.getString(Integer.toString(property), "");
716 int length = value.length();
717 if (length > 255) {
718 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400719 }
Mike Lockwood775de952011-03-05 17:34:11 -0500720 value.getChars(0, length, outStringValue, 0);
721 outStringValue[length] = 0;
722 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800723 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
724 // use screen size as max image size
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700725 Display display = ((WindowManager) mContext.getSystemService(
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800726 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700727 int width = display.getMaximumSizeDimension();
728 int height = display.getMaximumSizeDimension();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700729 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800730 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
731 outStringValue[imageSize.length()] = 0;
732 return MtpConstants.RESPONSE_OK;
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800733 case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
734 outIntValue[0] = mDeviceType;
735 return MtpConstants.RESPONSE_OK;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700736 case MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL:
737 outIntValue[0] = mBatteryLevel;
738 outIntValue[1] = mBatteryScale;
739 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800740 default:
741 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
742 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400743 }
744
Jeff Sharkeyff200952019-03-24 12:50:51 -0600745 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400746 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400747 switch (property) {
748 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
749 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500750 // writable string properties kept in shared prefs
751 SharedPreferences.Editor e = mDeviceProperties.edit();
752 e.putString(Integer.toString(property), stringValue);
753 return (e.commit() ? MtpConstants.RESPONSE_OK
754 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400755 }
756
757 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
758 }
759
Jeff Sharkeyff200952019-03-24 12:50:51 -0600760 @VisibleForNative
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400761 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700762 char[] outName, long[] outCreatedModified) {
763 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
764 if (obj == null) {
765 return false;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400766 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700767 outStorageFormatParent[0] = obj.getStorageId();
768 outStorageFormatParent[1] = obj.getFormat();
769 outStorageFormatParent[2] = obj.getParent().isRoot() ? 0 : obj.getParent().getId();
770
771 int nameLen = Integer.min(obj.getName().length(), 255);
772 obj.getName().getChars(0, nameLen, outName, 0);
773 outName[nameLen] = 0;
774
775 outCreatedModified[0] = obj.getModifiedTime();
776 outCreatedModified[1] = obj.getModifiedTime();
777 return true;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400778 }
779
Jeff Sharkeyff200952019-03-24 12:50:51 -0600780 @VisibleForNative
Mike Lockwood365e03e2010-12-08 16:08:01 -0800781 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700782 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
783 if (obj == null) {
784 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood01788562010-10-11 11:22:19 -0400785 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700786
787 String path = obj.getPath().toString();
788 int pathLen = Integer.min(path.length(), 4096);
789 path.getChars(0, pathLen, outFilePath, 0);
790 outFilePath[pathLen] = 0;
791
792 outFileLengthFormat[0] = obj.getSize();
793 outFileLengthFormat[1] = obj.getFormat();
794 return MtpConstants.RESPONSE_OK;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400795 }
796
Mike Lockwood71827742015-01-23 10:50:08 -0800797 private int getObjectFormat(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700798 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
799 if (obj == null) {
Mike Lockwood71827742015-01-23 10:50:08 -0800800 return -1;
Mike Lockwood71827742015-01-23 10:50:08 -0800801 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700802 return obj.getFormat();
Mike Lockwood71827742015-01-23 10:50:08 -0800803 }
804
Jeff Sharkeyff200952019-03-24 12:50:51 -0600805 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700806 private int beginDeleteObject(int handle) {
807 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
808 if (obj == null) {
809 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
810 }
811 if (!mManager.beginRemoveObject(obj)) {
812 return MtpConstants.RESPONSE_GENERAL_ERROR;
813 }
814 return MtpConstants.RESPONSE_OK;
815 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800816
Jeff Sharkeyff200952019-03-24 12:50:51 -0600817 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700818 private void endDeleteObject(int handle, boolean success) {
819 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
820 if (obj == null) {
821 return;
822 }
823 if (!mManager.endRemoveObject(obj, success))
824 Log.e(TAG, "Failed to end remove object");
825 if (success)
826 deleteFromMedia(obj.getPath(), obj.isDir());
827 }
828
829 private int findInMedia(Path path) {
830 int ret = -1;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800831 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400832 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700833 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
834 new String[]{path.toString()}, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800835 if (c != null && c.moveToNext()) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700836 ret = c.getInt(0);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800837 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700838 } catch (RemoteException e) {
839 Log.e(TAG, "Error finding " + path + " in MediaProvider");
840 } finally {
841 if (c != null)
842 c.close();
843 }
844 return ret;
845 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800846
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700847 private void deleteFromMedia(Path path, boolean isDir) {
848 try {
849 // Delete the object(s) from MediaProvider, but ignore errors.
850 if (isDir) {
Mike Lockwood55f808c2010-12-14 13:14:29 -0800851 // recursive case - delete all children first
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700852 mMediaProvider.delete(mObjectsUri,
853 // the 'like' makes it use the index, the 'lower()' makes it correct
854 // when the path contains sqlite wildcard characters
855 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
856 new String[]{path + "/%", Integer.toString(path.toString().length() + 1),
857 path.toString() + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800858 }
859
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700860 String[] whereArgs = new String[]{path.toString()};
861 if (mMediaProvider.delete(mObjectsUri, PATH_WHERE, whereArgs) > 0) {
862 if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600863 MediaStore.scanFile(mContext, path.getParent().toFile());
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800864 }
Mike Lockwood59c777a2010-08-02 10:37:41 -0400865 } else {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700866 Log.i(TAG, "Mediaprovider didn't delete " + path);
Mike Lockwood59c777a2010-08-02 10:37:41 -0400867 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700868 } catch (Exception e) {
869 Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400870 }
871 }
872
Jeff Sharkeyff200952019-03-24 12:50:51 -0600873 @VisibleForNative
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400874 private int[] getObjectReferences(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700875 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
876 if (obj == null)
877 return null;
878 // Translate this handle to the MediaProvider Handle
879 handle = findInMedia(obj.getPath());
880 if (handle == -1)
881 return null;
Mike Lockwood8490e662010-09-09 14:16:22 -0400882 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400883 Cursor c = null;
884 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700885 c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400886 if (c == null) {
887 return null;
888 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700889 ArrayList<Integer> result = new ArrayList<>();
890 while (c.moveToNext()) {
891 // Translate result handles back into handles for this session.
892 String refPath = c.getString(0);
893 MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath);
894 if (refObj != null) {
895 result.add(refObj.getId());
896 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400897 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700898 return result.stream().mapToInt(Integer::intValue).toArray();
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400899 } catch (RemoteException e) {
900 Log.e(TAG, "RemoteException in getObjectList", e);
901 } finally {
902 if (c != null) {
903 c.close();
904 }
905 }
906 return null;
907 }
908
Jeff Sharkeyff200952019-03-24 12:50:51 -0600909 @VisibleForNative
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400910 private int setObjectReferences(int handle, int[] references) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700911 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
912 if (obj == null)
913 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
914 // Translate this handle to the MediaProvider Handle
915 handle = findInMedia(obj.getPath());
916 if (handle == -1)
917 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood8490e662010-09-09 14:16:22 -0400918 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700919 ArrayList<ContentValues> valuesList = new ArrayList<>();
920 for (int id : references) {
921 // Translate each reference id to the MediaProvider Id
922 MtpStorageManager.MtpObject refObj = mManager.getObject(id);
923 if (refObj == null)
924 continue;
925 int refHandle = findInMedia(refObj.getPath());
926 if (refHandle == -1)
927 continue;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400928 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700929 values.put(Files.FileColumns._ID, refHandle);
930 valuesList.add(values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400931 }
932 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700933 if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400934 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400935 }
936 } catch (RemoteException e) {
937 Log.e(TAG, "RemoteException in setObjectReferences", e);
938 }
Mike Lockwood5367ab62010-08-30 13:23:02 -0400939 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400940 }
941
Jeff Sharkeyff200952019-03-24 12:50:51 -0600942 @VisibleForNative
Ashok Bhate2e59322013-12-17 19:04:19 +0000943 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400944
945 private native final void native_setup();
946 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400947}