blob: 4ac6d35e351f43f04aa5602efb85b90e94a1cf9a [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;
Jeff Sharkey42bf6d82019-04-17 11:16:12 -060039import android.util.SparseArray;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080040import android.view.Display;
41import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040042
Jeff Sharkeyff200952019-03-24 12:50:51 -060043import com.android.internal.annotations.VisibleForNative;
44
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;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070073
74 private final AtomicBoolean mClosed = new AtomicBoolean();
75 private final CloseGuard mCloseGuard = CloseGuard.get();
76
Jerry Zhangf9c5c252017-08-16 18:07:51 -070077 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040078
Mike Lockwood7d7fb632010-12-01 18:46:23 -050079 // cached property groups for single properties
Jeff Sharkey42bf6d82019-04-17 11:16:12 -060080 private final SparseArray<MtpPropertyGroup> mPropertyGroupsByProperty = new SparseArray<>();
Mike Lockwood7d7fb632010-12-01 18:46:23 -050081
82 // cached property groups for all properties for a given format
Jeff Sharkey42bf6d82019-04-17 11:16:12 -060083 private final SparseArray<MtpPropertyGroup> mPropertyGroupsByFormat = new SparseArray<>();
Mike Lockwood2837eef2010-08-31 16:25:12 -040084
Mike Lockwood775de952011-03-05 17:34:11 -050085 // SharedPreferences for writable MTP device properties
86 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040087
Jerry Zhangf9c5c252017-08-16 18:07:51 -070088 // Cached device properties
Mike Lockwood56c85242014-03-07 13:29:08 -080089 private int mBatteryLevel;
90 private int mBatteryScale;
Jerry Zhang13bb2f42016-12-14 15:39:29 -080091 private int mDeviceType;
92
Jerry Zhangf9c5c252017-08-16 18:07:51 -070093 private MtpServer mServer;
94 private MtpStorageManager mManager;
95
96 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
97 private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
98 private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
99 private static final String NO_MEDIA = ".nomedia";
100
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400101 static {
102 System.loadLibrary("media_jni");
103 }
104
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700105 private static final int[] PLAYBACK_FORMATS = {
106 // allow transferring arbitrary files
Mike Lockwoode5211692010-09-08 13:50:45 -0400107 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400108
Mike Lockwood792ec842010-09-09 15:30:10 -0400109 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400110 MtpConstants.FORMAT_TEXT,
111 MtpConstants.FORMAT_HTML,
112 MtpConstants.FORMAT_WAV,
113 MtpConstants.FORMAT_MP3,
114 MtpConstants.FORMAT_MPEG,
115 MtpConstants.FORMAT_EXIF_JPEG,
116 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800117 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400118 MtpConstants.FORMAT_GIF,
119 MtpConstants.FORMAT_JFIF,
120 MtpConstants.FORMAT_PNG,
121 MtpConstants.FORMAT_TIFF,
122 MtpConstants.FORMAT_WMA,
123 MtpConstants.FORMAT_OGG,
124 MtpConstants.FORMAT_AAC,
125 MtpConstants.FORMAT_MP4_CONTAINER,
126 MtpConstants.FORMAT_MP2,
127 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400128 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400129 MtpConstants.FORMAT_WPL_PLAYLIST,
130 MtpConstants.FORMAT_M3U_PLAYLIST,
131 MtpConstants.FORMAT_PLS_PLAYLIST,
132 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800133 MtpConstants.FORMAT_FLAC,
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100134 MtpConstants.FORMAT_DNG,
Chong Zhang6e18cce2017-08-16 11:57:02 -0700135 MtpConstants.FORMAT_HEIF,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700136 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400137
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700138 private static final int[] FILE_PROPERTIES = {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400139 MtpConstants.PROPERTY_STORAGE_ID,
140 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400141 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400142 MtpConstants.PROPERTY_OBJECT_SIZE,
143 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400144 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400145 MtpConstants.PROPERTY_PERSISTENT_UID,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700146 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400147 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800148 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400149 MtpConstants.PROPERTY_DATE_ADDED,
150 };
151
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700152 private static final int[] AUDIO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400153 MtpConstants.PROPERTY_ARTIST,
154 MtpConstants.PROPERTY_ALBUM_NAME,
155 MtpConstants.PROPERTY_ALBUM_ARTIST,
156 MtpConstants.PROPERTY_TRACK,
157 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
158 MtpConstants.PROPERTY_DURATION,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400159 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700160 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
161 MtpConstants.PROPERTY_BITRATE_TYPE,
162 MtpConstants.PROPERTY_AUDIO_BITRATE,
163 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
164 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400165 };
166
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700167 private static final int[] VIDEO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400168 MtpConstants.PROPERTY_ARTIST,
169 MtpConstants.PROPERTY_ALBUM_NAME,
170 MtpConstants.PROPERTY_DURATION,
171 MtpConstants.PROPERTY_DESCRIPTION,
172 };
173
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700174 private static final int[] IMAGE_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400175 MtpConstants.PROPERTY_DESCRIPTION,
176 };
177
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700178 private static final int[] DEVICE_PROPERTIES = {
179 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
180 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
181 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
182 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
183 MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
184 };
185
Jeff Sharkeyff200952019-03-24 12:50:51 -0600186 @VisibleForNative
Mike Lockwoodae078f72010-09-26 12:35:51 -0400187 private int[] getSupportedObjectProperties(int format) {
188 switch (format) {
189 case MtpConstants.FORMAT_MP3:
190 case MtpConstants.FORMAT_WAV:
191 case MtpConstants.FORMAT_WMA:
192 case MtpConstants.FORMAT_OGG:
193 case MtpConstants.FORMAT_AAC:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700194 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
195 Arrays.stream(AUDIO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400196 case MtpConstants.FORMAT_MPEG:
197 case MtpConstants.FORMAT_3GP_CONTAINER:
198 case MtpConstants.FORMAT_WMV:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700199 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
200 Arrays.stream(VIDEO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400201 case MtpConstants.FORMAT_EXIF_JPEG:
202 case MtpConstants.FORMAT_GIF:
203 case MtpConstants.FORMAT_PNG:
204 case MtpConstants.FORMAT_BMP:
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100205 case MtpConstants.FORMAT_DNG:
Chong Zhang6e18cce2017-08-16 11:57:02 -0700206 case MtpConstants.FORMAT_HEIF:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700207 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
208 Arrays.stream(IMAGE_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400209 default:
210 return FILE_PROPERTIES;
211 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400212 }
213
Jeff Sharkeyff200952019-03-24 12:50:51 -0600214 public static Uri getObjectPropertiesUri(int format, String volumeName) {
215 switch (format) {
216 case MtpConstants.FORMAT_MP3:
217 case MtpConstants.FORMAT_WAV:
218 case MtpConstants.FORMAT_WMA:
219 case MtpConstants.FORMAT_OGG:
220 case MtpConstants.FORMAT_AAC:
221 return MediaStore.Audio.Media.getContentUri(volumeName);
222 case MtpConstants.FORMAT_MPEG:
223 case MtpConstants.FORMAT_3GP_CONTAINER:
224 case MtpConstants.FORMAT_WMV:
225 return MediaStore.Video.Media.getContentUri(volumeName);
226 case MtpConstants.FORMAT_EXIF_JPEG:
227 case MtpConstants.FORMAT_GIF:
228 case MtpConstants.FORMAT_PNG:
229 case MtpConstants.FORMAT_BMP:
230 case MtpConstants.FORMAT_DNG:
231 case MtpConstants.FORMAT_HEIF:
232 return MediaStore.Images.Media.getContentUri(volumeName);
233 default:
234 return MediaStore.Files.getContentUri(volumeName);
235 }
236 }
237
238 @VisibleForNative
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400239 private int[] getSupportedDeviceProperties() {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700240 return DEVICE_PROPERTIES;
241 }
242
Jeff Sharkeyff200952019-03-24 12:50:51 -0600243 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700244 private int[] getSupportedPlaybackFormats() {
245 return PLAYBACK_FORMATS;
246 }
247
Jeff Sharkeyff200952019-03-24 12:50:51 -0600248 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700249 private int[] getSupportedCaptureFormats() {
250 // no capture formats yet
251 return null;
252 }
253
254 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
255 @Override
256 public void onReceive(Context context, Intent intent) {
257 String action = intent.getAction();
258 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
259 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
260 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
261 if (newLevel != mBatteryLevel) {
262 mBatteryLevel = newLevel;
263 if (mServer != null) {
264 // send device property changed event
265 mServer.sendDevicePropertyChanged(
266 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
267 }
268 }
269 }
270 }
271 };
272
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600273 public MtpDatabase(Context context, String[] subDirectories) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700274 native_setup();
James Wei9c968fd2018-11-12 21:43:15 +0800275 mContext = Objects.requireNonNull(context);
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800276 mMediaProvider = context.getContentResolver()
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700277 .acquireContentProviderClient(MediaStore.AUTHORITY);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700278 mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
279 @Override
280 public void sendObjectAdded(int id) {
281 if (MtpDatabase.this.mServer != null)
282 MtpDatabase.this.mServer.sendObjectAdded(id);
283 }
284
285 @Override
286 public void sendObjectRemoved(int id) {
287 if (MtpDatabase.this.mServer != null)
288 MtpDatabase.this.mServer.sendObjectRemoved(id);
289 }
Jamese4f680e2018-07-02 17:42:07 +0800290
291 @Override
292 public void sendObjectInfoChanged(int id) {
293 if (MtpDatabase.this.mServer != null)
294 MtpDatabase.this.mServer.sendObjectInfoChanged(id);
295 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700296 }, subDirectories == null ? null : Sets.newHashSet(subDirectories));
297
298 initDeviceProperties(context);
299 mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
300 mCloseGuard.open("close");
301 }
302
303 public void setServer(MtpServer server) {
304 mServer = server;
305 // always unregister before registering
306 try {
307 mContext.unregisterReceiver(mBatteryReceiver);
308 } catch (IllegalArgumentException e) {
309 // wasn't previously registered, ignore
310 }
311 // register for battery notifications when we are connected
312 if (server != null) {
313 mContext.registerReceiver(mBatteryReceiver,
314 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
315 }
316 }
317
James Wei9c968fd2018-11-12 21:43:15 +0800318 public Context getContext() {
319 return mContext;
320 }
321
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700322 @Override
323 public void close() {
324 mManager.close();
325 mCloseGuard.close();
326 if (mClosed.compareAndSet(false, true)) {
Jerry Zhang484ea672018-03-02 15:40:03 -0800327 if (mMediaProvider != null) {
328 mMediaProvider.close();
329 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700330 native_finalize();
331 }
332 }
333
334 @Override
335 protected void finalize() throws Throwable {
336 try {
337 if (mCloseGuard != null) {
338 mCloseGuard.warnIfOpen();
339 }
340 close();
341 } finally {
342 super.finalize();
343 }
344 }
345
346 public void addStorage(StorageVolume storage) {
347 MtpStorage mtpStorage = mManager.addMtpStorage(storage);
348 mStorageMap.put(storage.getPath(), mtpStorage);
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700349 if (mServer != null) {
350 mServer.addStorage(mtpStorage);
351 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700352 }
353
354 public void removeStorage(StorageVolume storage) {
355 MtpStorage mtpStorage = mStorageMap.get(storage.getPath());
356 if (mtpStorage == null) {
357 return;
358 }
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700359 if (mServer != null) {
360 mServer.removeStorage(mtpStorage);
361 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700362 mManager.removeMtpStorage(mtpStorage);
363 mStorageMap.remove(storage.getPath());
364 }
365
366 private void initDeviceProperties(Context context) {
367 final String devicePropertiesName = "device-properties";
368 mDeviceProperties = context.getSharedPreferences(devicePropertiesName,
369 Context.MODE_PRIVATE);
370 File databaseFile = context.getDatabasePath(devicePropertiesName);
371
372 if (databaseFile.exists()) {
373 // for backward compatibility - read device properties from sqlite database
374 // and migrate them to shared prefs
375 SQLiteDatabase db = null;
376 Cursor c = null;
377 try {
378 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
379 if (db != null) {
380 c = db.query("properties", new String[]{"_id", "code", "value"},
381 null, null, null, null, null);
382 if (c != null) {
383 SharedPreferences.Editor e = mDeviceProperties.edit();
384 while (c.moveToNext()) {
385 String name = c.getString(1);
386 String value = c.getString(2);
387 e.putString(name, value);
388 }
389 e.commit();
390 }
391 }
392 } catch (Exception e) {
393 Log.e(TAG, "failed to migrate device properties", e);
394 } finally {
395 if (c != null) c.close();
396 if (db != null) db.close();
397 }
398 context.deleteDatabase(devicePropertiesName);
399 }
400 }
401
Jeff Sharkeyff200952019-03-24 12:50:51 -0600402 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700403 private int beginSendObject(String path, int format, int parent, int storageId) {
404 MtpStorageManager.MtpObject parentObj =
405 parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
406 if (parentObj == null) {
407 return -1;
408 }
409
410 Path objPath = Paths.get(path);
411 return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format);
412 }
413
Jeff Sharkeyff200952019-03-24 12:50:51 -0600414 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700415 private void endSendObject(int handle, boolean succeeded) {
416 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
417 if (obj == null || !mManager.endSendObject(obj, succeeded)) {
418 Log.e(TAG, "Failed to successfully end send object");
419 return;
420 }
421 // Add the new file to MediaProvider
422 if (succeeded) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600423 MediaStore.scanFile(mContext, obj.getPath().toFile());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700424 }
425 }
426
Jeff Sharkeyff200952019-03-24 12:50:51 -0600427 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700428 private void rescanFile(String path, int handle, int format) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600429 MediaStore.scanFile(mContext, new File(path));
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700430 }
431
Jeff Sharkeyff200952019-03-24 12:50:51 -0600432 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700433 private int[] getObjectList(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700434 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700435 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700436 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700437 return null;
438 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700439 int[] ret = new int[objs.size()];
440 for (int i = 0; i < objs.size(); i++) {
441 ret[i] = objs.get(i).getId();
442 }
443 return ret;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700444 }
445
Jeff Sharkeyff200952019-03-24 12:50:51 -0600446 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700447 private int getNumObjects(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700448 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700449 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700450 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700451 return -1;
452 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700453 return objs.size();
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400454 }
455
Jeff Sharkeyff200952019-03-24 12:50:51 -0600456 @VisibleForNative
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900457 private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700458 int groupCode, int depth) {
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400459 // FIXME - implement group support
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700460 if (property == 0) {
461 if (groupCode == 0) {
462 return new MtpPropertyList(MtpConstants.RESPONSE_PARAMETER_NOT_SUPPORTED);
463 }
464 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
465 }
466 if (depth == 0xFFFFFFFF && (handle == 0 || handle == 0xFFFFFFFF)) {
467 // request all objects starting at root
468 handle = 0xFFFFFFFF;
469 depth = 0;
470 }
471 if (!(depth == 0 || depth == 1)) {
472 // we only support depth 0 and 1
473 // depth 0: single object, depth 1: immediate children
474 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
475 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700476 List<MtpStorageManager.MtpObject> objs = null;
477 MtpStorageManager.MtpObject thisObj = null;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700478 if (handle == 0xFFFFFFFF) {
479 // All objects are requested
Jerry Zhang9a018742018-05-10 18:27:13 -0700480 objs = mManager.getObjects(0, format, 0xFFFFFFFF);
481 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700482 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
483 }
484 } else if (handle != 0) {
485 // Add the requested object if format matches
486 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
487 if (obj == null) {
488 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
489 }
490 if (obj.getFormat() == format || format == 0) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700491 thisObj = obj;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700492 }
493 }
494 if (handle == 0 || depth == 1) {
495 if (handle == 0) {
496 handle = 0xFFFFFFFF;
497 }
498 // Get the direct children of root or this object.
Jerry Zhang9a018742018-05-10 18:27:13 -0700499 objs = mManager.getObjects(handle, format,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700500 0xFFFFFFFF);
Jerry Zhang9a018742018-05-10 18:27:13 -0700501 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700502 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
503 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700504 }
505 if (objs == null) {
506 objs = new ArrayList<>();
507 }
508 if (thisObj != null) {
509 objs.add(thisObj);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400510 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400511
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700512 MtpPropertyList ret = new MtpPropertyList(MtpConstants.RESPONSE_OK);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500513 MtpPropertyGroup propertyGroup;
Jerry Zhang9a018742018-05-10 18:27:13 -0700514 for (MtpStorageManager.MtpObject obj : objs) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700515 if (property == 0xffffffff) {
James Weif7f608c2018-08-15 22:23:12 +0800516 if (format == 0 && handle != 0 && handle != 0xffffffff) {
517 // return properties based on the object's format
518 format = obj.getFormat();
519 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700520 // Get all properties supported by this object
James Weif7f608c2018-08-15 22:23:12 +0800521 // format should be the same between get & put
522 propertyGroup = mPropertyGroupsByFormat.get(format);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700523 if (propertyGroup == null) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600524 final int[] propertyList = getSupportedObjectProperties(format);
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600525 propertyGroup = new MtpPropertyGroup(propertyList);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700526 mPropertyGroupsByFormat.put(format, propertyGroup);
527 }
528 } else {
529 // Get this property value
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700530 propertyGroup = mPropertyGroupsByProperty.get(property);
531 if (propertyGroup == null) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600532 final int[] propertyList = new int[]{property};
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600533 propertyGroup = new MtpPropertyGroup(propertyList);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700534 mPropertyGroupsByProperty.put(property, propertyGroup);
535 }
Mike Lockwood71827742015-01-23 10:50:08 -0800536 }
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600537 int err = propertyGroup.getPropertyList(mMediaProvider, obj.getVolumeName(), obj, ret);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700538 if (err != MtpConstants.RESPONSE_OK) {
539 return new MtpPropertyList(err);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400540 }
541 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700542 return ret;
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400543 }
544
Mike Lockwood5ebac832010-10-12 11:33:47 -0400545 private int renameFile(int handle, String newName) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700546 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
547 if (obj == null) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400548 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
549 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700550 Path oldPath = obj.getPath();
Mike Lockwood73e56d92011-12-01 16:58:41 -0500551
Mike Lockwood5ebac832010-10-12 11:33:47 -0400552 // now rename the file. make sure this succeeds before updating database
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700553 if (!mManager.beginRenameObject(obj, newName))
Mike Lockwood5ebac832010-10-12 11:33:47 -0400554 return MtpConstants.RESPONSE_GENERAL_ERROR;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700555 Path newPath = obj.getPath();
556 boolean success = oldPath.toFile().renameTo(newPath.toFile());
Jerry Zhangd470a1e2018-05-14 12:19:08 -0700557 try {
558 Os.access(oldPath.toString(), OsConstants.F_OK);
559 Os.access(newPath.toString(), OsConstants.F_OK);
560 } catch (ErrnoException e) {
561 // Ignore. Could fail if the metadata was already updated.
562 }
563
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700564 if (!mManager.endRenameObject(obj, oldPath.getFileName().toString(), success)) {
565 Log.e(TAG, "Failed to end rename object");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400566 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400567 if (!success) {
568 return MtpConstants.RESPONSE_GENERAL_ERROR;
569 }
570
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700571 // finally update MediaProvider
Mike Lockwood5ebac832010-10-12 11:33:47 -0400572 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700573 values.put(Files.FileColumns.DATA, newPath.toString());
574 String[] whereArgs = new String[]{oldPath.toString()};
Mike Lockwood5ebac832010-10-12 11:33:47 -0400575 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400576 // note - we are relying on a special case in MediaProvider.update() to update
577 // the paths for all children in the case where this is a directory.
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600578 final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
579 mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400580 } catch (RemoteException e) {
581 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
582 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400583
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800584 // check if nomedia status changed
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700585 if (obj.isDir()) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800586 // for directories, check if renamed from something hidden to something non-hidden
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700587 if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600588 MediaStore.scanFile(mContext, newPath.toFile());
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800589 }
590 } else {
591 // for files, check if renamed from .nomedia to something else
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700592 if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
593 && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600594 MediaStore.scanFile(mContext, newPath.getParent().toFile());
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800595 }
596 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400597 return MtpConstants.RESPONSE_OK;
598 }
599
Jeff Sharkeyff200952019-03-24 12:50:51 -0600600 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700601 private int beginMoveObject(int handle, int newParent, int newStorage) {
602 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
603 MtpStorageManager.MtpObject parent = newParent == 0 ?
604 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
605 if (obj == null || parent == null)
606 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Jerry Zhang952558d42017-09-26 17:49:52 -0700607
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700608 boolean allowed = mManager.beginMoveObject(obj, parent);
609 return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR;
610 }
611
Jeff Sharkeyff200952019-03-24 12:50:51 -0600612 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700613 private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage,
614 int objId, boolean success) {
615 MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ?
616 mManager.getStorageRoot(oldStorage) : mManager.getObject(oldParent);
617 MtpStorageManager.MtpObject newParentObj = newParent == 0 ?
618 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
619 MtpStorageManager.MtpObject obj = mManager.getObject(objId);
620 String name = obj.getName();
621 if (newParentObj == null || oldParentObj == null
622 ||!mManager.endMoveObject(oldParentObj, newParentObj, name, success)) {
623 Log.e(TAG, "Failed to end move object");
624 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700625 }
626
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700627 obj = mManager.getObject(objId);
628 if (!success || obj == null)
629 return;
630 // Get parent info from MediaProvider, since the id is different from MTP's
Jerry Zhang952558d42017-09-26 17:49:52 -0700631 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700632 Path path = newParentObj.getPath().resolve(name);
633 Path oldPath = oldParentObj.getPath().resolve(name);
634 values.put(Files.FileColumns.DATA, path.toString());
635 if (obj.getParent().isRoot()) {
636 values.put(Files.FileColumns.PARENT, 0);
637 } else {
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600638 int parentId = findInMedia(newParentObj, path.getParent());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700639 if (parentId != -1) {
640 values.put(Files.FileColumns.PARENT, parentId);
641 } else {
642 // The new parent isn't in MediaProvider, so delete the object instead
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600643 deleteFromMedia(obj, oldPath, obj.isDir());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700644 return;
645 }
646 }
647 // update MediaProvider
648 Cursor c = null;
649 String[] whereArgs = new String[]{oldPath.toString()};
Jerry Zhang952558d42017-09-26 17:49:52 -0700650 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700651 int parentId = -1;
652 if (!oldParentObj.isRoot()) {
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600653 parentId = findInMedia(oldParentObj, oldPath.getParent());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700654 }
655 if (oldParentObj.isRoot() || parentId != -1) {
656 // Old parent exists in MediaProvider - perform a move
657 // note - we are relying on a special case in MediaProvider.update() to update
658 // the paths for all children in the case where this is a directory.
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600659 final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
660 mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700661 } else {
662 // Old parent doesn't exist - add the object
Jeff Sharkeyff200952019-03-24 12:50:51 -0600663 MediaStore.scanFile(mContext, path.toFile());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700664 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700665 } catch (RemoteException e) {
666 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
667 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700668 }
669
Jeff Sharkeyff200952019-03-24 12:50:51 -0600670 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700671 private int beginCopyObject(int handle, int newParent, int newStorage) {
672 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
673 MtpStorageManager.MtpObject parent = newParent == 0 ?
674 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
675 if (obj == null || parent == null)
676 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
677 return mManager.beginCopyObject(obj, parent);
678 }
679
Jeff Sharkeyff200952019-03-24 12:50:51 -0600680 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700681 private void endCopyObject(int handle, boolean success) {
682 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
683 if (obj == null || !mManager.endCopyObject(obj, success)) {
684 Log.e(TAG, "Failed to end copy object");
685 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700686 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700687 if (!success) {
688 return;
689 }
Jeff Sharkeyff200952019-03-24 12:50:51 -0600690 MediaStore.scanFile(mContext, obj.getPath().toFile());
Jerry Zhang952558d42017-09-26 17:49:52 -0700691 }
692
Jeff Sharkeyff200952019-03-24 12:50:51 -0600693 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400694 private int setObjectProperty(int handle, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700695 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400696 switch (property) {
697 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
698 return renameFile(handle, stringValue);
699
700 default:
701 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
702 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400703 }
704
Jeff Sharkeyff200952019-03-24 12:50:51 -0600705 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400706 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400707 switch (property) {
708 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
709 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500710 // writable string properties kept in shared preferences
711 String value = mDeviceProperties.getString(Integer.toString(property), "");
712 int length = value.length();
713 if (length > 255) {
714 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400715 }
Mike Lockwood775de952011-03-05 17:34:11 -0500716 value.getChars(0, length, outStringValue, 0);
717 outStringValue[length] = 0;
718 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800719 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
720 // use screen size as max image size
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700721 Display display = ((WindowManager) mContext.getSystemService(
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800722 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700723 int width = display.getMaximumSizeDimension();
724 int height = display.getMaximumSizeDimension();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700725 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800726 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
727 outStringValue[imageSize.length()] = 0;
728 return MtpConstants.RESPONSE_OK;
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800729 case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
730 outIntValue[0] = mDeviceType;
731 return MtpConstants.RESPONSE_OK;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700732 case MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL:
733 outIntValue[0] = mBatteryLevel;
734 outIntValue[1] = mBatteryScale;
735 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800736 default:
737 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
738 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400739 }
740
Jeff Sharkeyff200952019-03-24 12:50:51 -0600741 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400742 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400743 switch (property) {
744 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
745 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500746 // writable string properties kept in shared prefs
747 SharedPreferences.Editor e = mDeviceProperties.edit();
748 e.putString(Integer.toString(property), stringValue);
749 return (e.commit() ? MtpConstants.RESPONSE_OK
750 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400751 }
752
753 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
754 }
755
Jeff Sharkeyff200952019-03-24 12:50:51 -0600756 @VisibleForNative
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400757 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700758 char[] outName, long[] outCreatedModified) {
759 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
760 if (obj == null) {
761 return false;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400762 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700763 outStorageFormatParent[0] = obj.getStorageId();
764 outStorageFormatParent[1] = obj.getFormat();
765 outStorageFormatParent[2] = obj.getParent().isRoot() ? 0 : obj.getParent().getId();
766
767 int nameLen = Integer.min(obj.getName().length(), 255);
768 obj.getName().getChars(0, nameLen, outName, 0);
769 outName[nameLen] = 0;
770
771 outCreatedModified[0] = obj.getModifiedTime();
772 outCreatedModified[1] = obj.getModifiedTime();
773 return true;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400774 }
775
Jeff Sharkeyff200952019-03-24 12:50:51 -0600776 @VisibleForNative
Mike Lockwood365e03e2010-12-08 16:08:01 -0800777 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700778 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
779 if (obj == null) {
780 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood01788562010-10-11 11:22:19 -0400781 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700782
783 String path = obj.getPath().toString();
784 int pathLen = Integer.min(path.length(), 4096);
785 path.getChars(0, pathLen, outFilePath, 0);
786 outFilePath[pathLen] = 0;
787
788 outFileLengthFormat[0] = obj.getSize();
789 outFileLengthFormat[1] = obj.getFormat();
790 return MtpConstants.RESPONSE_OK;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400791 }
792
Mike Lockwood71827742015-01-23 10:50:08 -0800793 private int getObjectFormat(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700794 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
795 if (obj == null) {
Mike Lockwood71827742015-01-23 10:50:08 -0800796 return -1;
Mike Lockwood71827742015-01-23 10:50:08 -0800797 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700798 return obj.getFormat();
Mike Lockwood71827742015-01-23 10:50:08 -0800799 }
800
Jeff Sharkeyff200952019-03-24 12:50:51 -0600801 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700802 private int beginDeleteObject(int handle) {
803 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
804 if (obj == null) {
805 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
806 }
807 if (!mManager.beginRemoveObject(obj)) {
808 return MtpConstants.RESPONSE_GENERAL_ERROR;
809 }
810 return MtpConstants.RESPONSE_OK;
811 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800812
Jeff Sharkeyff200952019-03-24 12:50:51 -0600813 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700814 private void endDeleteObject(int handle, boolean success) {
815 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
816 if (obj == null) {
817 return;
818 }
819 if (!mManager.endRemoveObject(obj, success))
820 Log.e(TAG, "Failed to end remove object");
821 if (success)
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600822 deleteFromMedia(obj, obj.getPath(), obj.isDir());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700823 }
824
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600825 private int findInMedia(MtpStorageManager.MtpObject obj, Path path) {
826 final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
827
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700828 int ret = -1;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800829 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400830 try {
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600831 c = mMediaProvider.query(objectsUri, ID_PROJECTION, PATH_WHERE,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700832 new String[]{path.toString()}, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800833 if (c != null && c.moveToNext()) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700834 ret = c.getInt(0);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800835 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700836 } catch (RemoteException e) {
837 Log.e(TAG, "Error finding " + path + " in MediaProvider");
838 } finally {
839 if (c != null)
840 c.close();
841 }
842 return ret;
843 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800844
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600845 private void deleteFromMedia(MtpStorageManager.MtpObject obj, Path path, boolean isDir) {
846 final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700847 try {
848 // Delete the object(s) from MediaProvider, but ignore errors.
849 if (isDir) {
Mike Lockwood55f808c2010-12-14 13:14:29 -0800850 // recursive case - delete all children first
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600851 mMediaProvider.delete(objectsUri,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700852 // the 'like' makes it use the index, the 'lower()' makes it correct
853 // when the path contains sqlite wildcard characters
854 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
855 new String[]{path + "/%", Integer.toString(path.toString().length() + 1),
856 path.toString() + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800857 }
858
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700859 String[] whereArgs = new String[]{path.toString()};
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600860 if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700861 if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600862 MediaStore.scanFile(mContext, path.getParent().toFile());
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800863 }
Mike Lockwood59c777a2010-08-02 10:37:41 -0400864 } else {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700865 Log.i(TAG, "Mediaprovider didn't delete " + path);
Mike Lockwood59c777a2010-08-02 10:37:41 -0400866 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700867 } catch (Exception e) {
868 Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400869 }
870 }
871
Jeff Sharkeyff200952019-03-24 12:50:51 -0600872 @VisibleForNative
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400873 private int[] getObjectReferences(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700874 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
875 if (obj == null)
876 return null;
877 // Translate this handle to the MediaProvider Handle
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600878 handle = findInMedia(obj, obj.getPath());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700879 if (handle == -1)
880 return null;
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600881 Uri uri = Files.getMtpReferencesUri(obj.getVolumeName(), handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400882 Cursor c = null;
883 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700884 c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400885 if (c == null) {
886 return null;
887 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700888 ArrayList<Integer> result = new ArrayList<>();
889 while (c.moveToNext()) {
890 // Translate result handles back into handles for this session.
891 String refPath = c.getString(0);
892 MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath);
893 if (refObj != null) {
894 result.add(refObj.getId());
895 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400896 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700897 return result.stream().mapToInt(Integer::intValue).toArray();
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400898 } catch (RemoteException e) {
899 Log.e(TAG, "RemoteException in getObjectList", e);
900 } finally {
901 if (c != null) {
902 c.close();
903 }
904 }
905 return null;
906 }
907
Jeff Sharkeyff200952019-03-24 12:50:51 -0600908 @VisibleForNative
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400909 private int setObjectReferences(int handle, int[] references) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700910 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
911 if (obj == null)
912 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
913 // Translate this handle to the MediaProvider Handle
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600914 handle = findInMedia(obj, obj.getPath());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700915 if (handle == -1)
916 return MtpConstants.RESPONSE_GENERAL_ERROR;
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600917 Uri uri = Files.getMtpReferencesUri(obj.getVolumeName(), handle);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700918 ArrayList<ContentValues> valuesList = new ArrayList<>();
919 for (int id : references) {
920 // Translate each reference id to the MediaProvider Id
921 MtpStorageManager.MtpObject refObj = mManager.getObject(id);
922 if (refObj == null)
923 continue;
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600924 int refHandle = findInMedia(refObj, refObj.getPath());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700925 if (refHandle == -1)
926 continue;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400927 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700928 values.put(Files.FileColumns._ID, refHandle);
929 valuesList.add(values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400930 }
931 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700932 if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400933 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400934 }
935 } catch (RemoteException e) {
936 Log.e(TAG, "RemoteException in setObjectReferences", e);
937 }
Mike Lockwood5367ab62010-08-30 13:23:02 -0400938 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400939 }
940
Jeff Sharkeyff200952019-03-24 12:50:51 -0600941 @VisibleForNative
Ashok Bhate2e59322013-12-17 19:04:19 +0000942 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400943
944 private native final void native_setup();
945 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400946}