blob: 9fe700a41ab241452ed33757f8be78b7391fea3a [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
Jeff Sharkey04b4ba12019-12-15 22:42:42 -070019import android.annotation.NonNull;
Mike Lockwood56c85242014-03-07 13:29:08 -080020import android.content.BroadcastReceiver;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070021import android.content.ContentProviderClient;
Jeff Sharkey04b4ba12019-12-15 22:42:42 -070022import android.content.ContentUris;
Mike Lockwoodd815f792010-07-12 08:49:01 -040023import android.content.ContentValues;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070024import android.content.Context;
Mike Lockwood2837eef2010-08-31 16:25:12 -040025import android.content.Intent;
Mike Lockwood56c85242014-03-07 13:29:08 -080026import android.content.IntentFilter;
Mike Lockwood775de952011-03-05 17:34:11 -050027import android.content.SharedPreferences;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040028import android.database.Cursor;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040029import android.database.sqlite.SQLiteDatabase;
James Weibb3f5482019-12-24 19:12:29 +080030import android.graphics.Bitmap;
Marco Nelissenf7ec1682019-06-27 15:56:32 -070031import android.media.ExifInterface;
James Weibb3f5482019-12-24 19:12:29 +080032import android.media.ThumbnailUtils;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040033import android.net.Uri;
Mike Lockwood56c85242014-03-07 13:29:08 -080034import android.os.BatteryManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040035import android.os.RemoteException;
Jerry Zhang13bb2f42016-12-14 15:39:29 -080036import android.os.SystemProperties;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070037import android.os.storage.StorageVolume;
Mike Lockwooda3156052010-11-20 12:28:27 -050038import android.provider.MediaStore;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040039import android.provider.MediaStore.Files;
Jerry Zhangd470a1e2018-05-14 12:19:08 -070040import android.system.ErrnoException;
41import android.system.Os;
42import android.system.OsConstants;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040043import android.util.Log;
Jeff Sharkey42bf6d82019-04-17 11:16:12 -060044import android.util.SparseArray;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080045import android.view.Display;
46import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040047
Jeff Sharkeyff200952019-03-24 12:50:51 -060048import com.android.internal.annotations.VisibleForNative;
James Wei19ded222019-12-24 19:48:51 +080049import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkeyff200952019-03-24 12:50:51 -060050
Jeff Sharkey60cfad82016-01-05 17:30:57 -070051import dalvik.system.CloseGuard;
52
Jerry Zhangf9c5c252017-08-16 18:07:51 -070053import com.google.android.collect.Sets;
54
James Weibb3f5482019-12-24 19:12:29 +080055import java.io.ByteArrayOutputStream;
Mike Lockwood5ebac832010-10-12 11:33:47 -040056import java.io.File;
Marco Nelissenf7ec1682019-06-27 15:56:32 -070057import java.io.IOException;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070058import java.nio.file.Path;
59import java.nio.file.Paths;
60import java.util.ArrayList;
61import java.util.Arrays;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050062import java.util.HashMap;
Jerry Zhang9a018742018-05-10 18:27:13 -070063import java.util.List;
dujin.chafe464a72011-11-22 12:13:33 +090064import java.util.Locale;
James Wei9c968fd2018-11-12 21:43:15 +080065import java.util.Objects;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070066import java.util.concurrent.atomic.AtomicBoolean;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070067import java.util.stream.IntStream;
Mike Lockwood5ebac832010-10-12 11:33:47 -040068
Mike Lockwoodd21eac92010-07-03 00:44:05 -040069/**
Jerry Zhangf9c5c252017-08-16 18:07:51 -070070 * MtpDatabase provides an interface for MTP operations that MtpServer can use. To do this, it uses
71 * MtpStorageManager for filesystem operations and MediaProvider to get media metadata. File
72 * operations are also reflected in MediaProvider if possible.
73 * operations
Mike Lockwoodd21eac92010-07-03 00:44:05 -040074 * {@hide}
75 */
Jeff Sharkey60cfad82016-01-05 17:30:57 -070076public class MtpDatabase implements AutoCloseable {
Jerry Zhangf9c5c252017-08-16 18:07:51 -070077 private static final String TAG = MtpDatabase.class.getSimpleName();
James Weibb3f5482019-12-24 19:12:29 +080078 private static final int MAX_THUMB_SIZE = (200 * 1024);
Mike Lockwoodd21eac92010-07-03 00:44:05 -040079
Mike Lockwood2837eef2010-08-31 16:25:12 -040080 private final Context mContext;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070081 private final ContentProviderClient mMediaProvider;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070082
83 private final AtomicBoolean mClosed = new AtomicBoolean();
84 private final CloseGuard mCloseGuard = CloseGuard.get();
85
Jerry Zhangf9c5c252017-08-16 18:07:51 -070086 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040087
Mike Lockwood7d7fb632010-12-01 18:46:23 -050088 // cached property groups for single properties
Jeff Sharkey42bf6d82019-04-17 11:16:12 -060089 private final SparseArray<MtpPropertyGroup> mPropertyGroupsByProperty = new SparseArray<>();
Mike Lockwood7d7fb632010-12-01 18:46:23 -050090
91 // cached property groups for all properties for a given format
Jeff Sharkey42bf6d82019-04-17 11:16:12 -060092 private final SparseArray<MtpPropertyGroup> mPropertyGroupsByFormat = new SparseArray<>();
Mike Lockwood2837eef2010-08-31 16:25:12 -040093
Mike Lockwood775de952011-03-05 17:34:11 -050094 // SharedPreferences for writable MTP device properties
95 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040096
Jerry Zhangf9c5c252017-08-16 18:07:51 -070097 // Cached device properties
Mike Lockwood56c85242014-03-07 13:29:08 -080098 private int mBatteryLevel;
99 private int mBatteryScale;
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800100 private int mDeviceType;
101
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700102 private MtpServer mServer;
103 private MtpStorageManager mManager;
104
105 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
106 private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
107 private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
108 private static final String NO_MEDIA = ".nomedia";
109
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400110 static {
111 System.loadLibrary("media_jni");
112 }
113
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700114 private static final int[] PLAYBACK_FORMATS = {
115 // allow transferring arbitrary files
Mike Lockwoode5211692010-09-08 13:50:45 -0400116 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400117
Mike Lockwood792ec842010-09-09 15:30:10 -0400118 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400119 MtpConstants.FORMAT_TEXT,
120 MtpConstants.FORMAT_HTML,
121 MtpConstants.FORMAT_WAV,
122 MtpConstants.FORMAT_MP3,
123 MtpConstants.FORMAT_MPEG,
124 MtpConstants.FORMAT_EXIF_JPEG,
125 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800126 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400127 MtpConstants.FORMAT_GIF,
128 MtpConstants.FORMAT_JFIF,
129 MtpConstants.FORMAT_PNG,
130 MtpConstants.FORMAT_TIFF,
131 MtpConstants.FORMAT_WMA,
132 MtpConstants.FORMAT_OGG,
133 MtpConstants.FORMAT_AAC,
134 MtpConstants.FORMAT_MP4_CONTAINER,
135 MtpConstants.FORMAT_MP2,
136 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400137 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400138 MtpConstants.FORMAT_WPL_PLAYLIST,
139 MtpConstants.FORMAT_M3U_PLAYLIST,
140 MtpConstants.FORMAT_PLS_PLAYLIST,
141 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800142 MtpConstants.FORMAT_FLAC,
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100143 MtpConstants.FORMAT_DNG,
Chong Zhang6e18cce2017-08-16 11:57:02 -0700144 MtpConstants.FORMAT_HEIF,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700145 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400146
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700147 private static final int[] FILE_PROPERTIES = {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400148 MtpConstants.PROPERTY_STORAGE_ID,
149 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400150 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400151 MtpConstants.PROPERTY_OBJECT_SIZE,
152 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400153 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400154 MtpConstants.PROPERTY_PERSISTENT_UID,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700155 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400156 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800157 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400158 MtpConstants.PROPERTY_DATE_ADDED,
159 };
160
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700161 private static final int[] AUDIO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400162 MtpConstants.PROPERTY_ARTIST,
163 MtpConstants.PROPERTY_ALBUM_NAME,
164 MtpConstants.PROPERTY_ALBUM_ARTIST,
165 MtpConstants.PROPERTY_TRACK,
166 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
167 MtpConstants.PROPERTY_DURATION,
James Weif545a3a2020-05-01 15:07:23 +0800168 MtpConstants.PROPERTY_GENRE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400169 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700170 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
171 MtpConstants.PROPERTY_BITRATE_TYPE,
172 MtpConstants.PROPERTY_AUDIO_BITRATE,
173 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
174 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400175 };
176
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700177 private static final int[] VIDEO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400178 MtpConstants.PROPERTY_ARTIST,
179 MtpConstants.PROPERTY_ALBUM_NAME,
180 MtpConstants.PROPERTY_DURATION,
181 MtpConstants.PROPERTY_DESCRIPTION,
182 };
183
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700184 private static final int[] IMAGE_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400185 MtpConstants.PROPERTY_DESCRIPTION,
186 };
187
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700188 private static final int[] DEVICE_PROPERTIES = {
189 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
190 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
191 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
192 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
193 MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
194 };
195
Jeff Sharkeyff200952019-03-24 12:50:51 -0600196 @VisibleForNative
Mike Lockwoodae078f72010-09-26 12:35:51 -0400197 private int[] getSupportedObjectProperties(int format) {
198 switch (format) {
199 case MtpConstants.FORMAT_MP3:
200 case MtpConstants.FORMAT_WAV:
201 case MtpConstants.FORMAT_WMA:
202 case MtpConstants.FORMAT_OGG:
203 case MtpConstants.FORMAT_AAC:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700204 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
205 Arrays.stream(AUDIO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400206 case MtpConstants.FORMAT_MPEG:
207 case MtpConstants.FORMAT_3GP_CONTAINER:
208 case MtpConstants.FORMAT_WMV:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700209 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
210 Arrays.stream(VIDEO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400211 case MtpConstants.FORMAT_EXIF_JPEG:
212 case MtpConstants.FORMAT_GIF:
213 case MtpConstants.FORMAT_PNG:
214 case MtpConstants.FORMAT_BMP:
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100215 case MtpConstants.FORMAT_DNG:
Chong Zhang6e18cce2017-08-16 11:57:02 -0700216 case MtpConstants.FORMAT_HEIF:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700217 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
218 Arrays.stream(IMAGE_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400219 default:
220 return FILE_PROPERTIES;
221 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400222 }
223
Jeff Sharkeyff200952019-03-24 12:50:51 -0600224 public static Uri getObjectPropertiesUri(int format, String volumeName) {
225 switch (format) {
226 case MtpConstants.FORMAT_MP3:
227 case MtpConstants.FORMAT_WAV:
228 case MtpConstants.FORMAT_WMA:
229 case MtpConstants.FORMAT_OGG:
230 case MtpConstants.FORMAT_AAC:
231 return MediaStore.Audio.Media.getContentUri(volumeName);
232 case MtpConstants.FORMAT_MPEG:
233 case MtpConstants.FORMAT_3GP_CONTAINER:
234 case MtpConstants.FORMAT_WMV:
235 return MediaStore.Video.Media.getContentUri(volumeName);
236 case MtpConstants.FORMAT_EXIF_JPEG:
237 case MtpConstants.FORMAT_GIF:
238 case MtpConstants.FORMAT_PNG:
239 case MtpConstants.FORMAT_BMP:
240 case MtpConstants.FORMAT_DNG:
241 case MtpConstants.FORMAT_HEIF:
242 return MediaStore.Images.Media.getContentUri(volumeName);
243 default:
244 return MediaStore.Files.getContentUri(volumeName);
245 }
246 }
247
248 @VisibleForNative
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400249 private int[] getSupportedDeviceProperties() {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700250 return DEVICE_PROPERTIES;
251 }
252
Jeff Sharkeyff200952019-03-24 12:50:51 -0600253 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700254 private int[] getSupportedPlaybackFormats() {
255 return PLAYBACK_FORMATS;
256 }
257
Jeff Sharkeyff200952019-03-24 12:50:51 -0600258 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700259 private int[] getSupportedCaptureFormats() {
260 // no capture formats yet
261 return null;
262 }
263
264 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
265 @Override
266 public void onReceive(Context context, Intent intent) {
267 String action = intent.getAction();
268 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
269 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
270 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
271 if (newLevel != mBatteryLevel) {
272 mBatteryLevel = newLevel;
273 if (mServer != null) {
274 // send device property changed event
275 mServer.sendDevicePropertyChanged(
276 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
277 }
278 }
279 }
280 }
281 };
282
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600283 public MtpDatabase(Context context, String[] subDirectories) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700284 native_setup();
James Wei9c968fd2018-11-12 21:43:15 +0800285 mContext = Objects.requireNonNull(context);
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800286 mMediaProvider = context.getContentResolver()
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700287 .acquireContentProviderClient(MediaStore.AUTHORITY);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700288 mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
289 @Override
290 public void sendObjectAdded(int id) {
291 if (MtpDatabase.this.mServer != null)
292 MtpDatabase.this.mServer.sendObjectAdded(id);
293 }
294
295 @Override
296 public void sendObjectRemoved(int id) {
297 if (MtpDatabase.this.mServer != null)
298 MtpDatabase.this.mServer.sendObjectRemoved(id);
299 }
Jamese4f680e2018-07-02 17:42:07 +0800300
301 @Override
302 public void sendObjectInfoChanged(int id) {
303 if (MtpDatabase.this.mServer != null)
304 MtpDatabase.this.mServer.sendObjectInfoChanged(id);
305 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700306 }, subDirectories == null ? null : Sets.newHashSet(subDirectories));
307
308 initDeviceProperties(context);
309 mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
310 mCloseGuard.open("close");
311 }
312
313 public void setServer(MtpServer server) {
314 mServer = server;
315 // always unregister before registering
316 try {
317 mContext.unregisterReceiver(mBatteryReceiver);
318 } catch (IllegalArgumentException e) {
319 // wasn't previously registered, ignore
320 }
321 // register for battery notifications when we are connected
322 if (server != null) {
323 mContext.registerReceiver(mBatteryReceiver,
324 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
325 }
326 }
327
James Wei9c968fd2018-11-12 21:43:15 +0800328 public Context getContext() {
329 return mContext;
330 }
331
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700332 @Override
333 public void close() {
334 mManager.close();
335 mCloseGuard.close();
336 if (mClosed.compareAndSet(false, true)) {
Jerry Zhang484ea672018-03-02 15:40:03 -0800337 if (mMediaProvider != null) {
338 mMediaProvider.close();
339 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700340 native_finalize();
341 }
342 }
343
344 @Override
345 protected void finalize() throws Throwable {
346 try {
347 if (mCloseGuard != null) {
348 mCloseGuard.warnIfOpen();
349 }
350 close();
351 } finally {
352 super.finalize();
353 }
354 }
355
356 public void addStorage(StorageVolume storage) {
357 MtpStorage mtpStorage = mManager.addMtpStorage(storage);
358 mStorageMap.put(storage.getPath(), mtpStorage);
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700359 if (mServer != null) {
360 mServer.addStorage(mtpStorage);
361 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700362 }
363
364 public void removeStorage(StorageVolume storage) {
365 MtpStorage mtpStorage = mStorageMap.get(storage.getPath());
366 if (mtpStorage == null) {
367 return;
368 }
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700369 if (mServer != null) {
370 mServer.removeStorage(mtpStorage);
371 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700372 mManager.removeMtpStorage(mtpStorage);
373 mStorageMap.remove(storage.getPath());
374 }
375
376 private void initDeviceProperties(Context context) {
377 final String devicePropertiesName = "device-properties";
378 mDeviceProperties = context.getSharedPreferences(devicePropertiesName,
379 Context.MODE_PRIVATE);
380 File databaseFile = context.getDatabasePath(devicePropertiesName);
381
382 if (databaseFile.exists()) {
383 // for backward compatibility - read device properties from sqlite database
384 // and migrate them to shared prefs
385 SQLiteDatabase db = null;
386 Cursor c = null;
387 try {
388 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
389 if (db != null) {
390 c = db.query("properties", new String[]{"_id", "code", "value"},
391 null, null, null, null, null);
392 if (c != null) {
393 SharedPreferences.Editor e = mDeviceProperties.edit();
394 while (c.moveToNext()) {
395 String name = c.getString(1);
396 String value = c.getString(2);
397 e.putString(name, value);
398 }
399 e.commit();
400 }
401 }
402 } catch (Exception e) {
403 Log.e(TAG, "failed to migrate device properties", e);
404 } finally {
405 if (c != null) c.close();
406 if (db != null) db.close();
407 }
408 context.deleteDatabase(devicePropertiesName);
409 }
410 }
411
Jeff Sharkeyff200952019-03-24 12:50:51 -0600412 @VisibleForNative
James Wei19ded222019-12-24 19:48:51 +0800413 @VisibleForTesting
414 public int beginSendObject(String path, int format, int parent, int storageId) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700415 MtpStorageManager.MtpObject parentObj =
416 parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
417 if (parentObj == null) {
418 return -1;
419 }
420
421 Path objPath = Paths.get(path);
422 return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format);
423 }
424
Jeff Sharkeyff200952019-03-24 12:50:51 -0600425 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700426 private void endSendObject(int handle, boolean succeeded) {
427 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
428 if (obj == null || !mManager.endSendObject(obj, succeeded)) {
429 Log.e(TAG, "Failed to successfully end send object");
430 return;
431 }
432 // Add the new file to MediaProvider
433 if (succeeded) {
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700434 MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700435 }
436 }
437
Jeff Sharkeyff200952019-03-24 12:50:51 -0600438 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700439 private void rescanFile(String path, int handle, int format) {
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700440 MediaStore.scanFile(mContext.getContentResolver(), new File(path));
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700441 }
442
Jeff Sharkeyff200952019-03-24 12:50:51 -0600443 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700444 private int[] getObjectList(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700445 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700446 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700447 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700448 return null;
449 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700450 int[] ret = new int[objs.size()];
451 for (int i = 0; i < objs.size(); i++) {
452 ret[i] = objs.get(i).getId();
453 }
454 return ret;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700455 }
456
Jeff Sharkeyff200952019-03-24 12:50:51 -0600457 @VisibleForNative
James Wei82062b92020-04-20 21:40:50 +0800458 @VisibleForTesting
459 public int getNumObjects(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700460 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700461 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700462 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700463 return -1;
464 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700465 return objs.size();
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400466 }
467
Jeff Sharkeyff200952019-03-24 12:50:51 -0600468 @VisibleForNative
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900469 private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700470 int groupCode, int depth) {
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400471 // FIXME - implement group support
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700472 if (property == 0) {
473 if (groupCode == 0) {
474 return new MtpPropertyList(MtpConstants.RESPONSE_PARAMETER_NOT_SUPPORTED);
475 }
476 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
477 }
478 if (depth == 0xFFFFFFFF && (handle == 0 || handle == 0xFFFFFFFF)) {
479 // request all objects starting at root
480 handle = 0xFFFFFFFF;
481 depth = 0;
482 }
483 if (!(depth == 0 || depth == 1)) {
484 // we only support depth 0 and 1
485 // depth 0: single object, depth 1: immediate children
486 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
487 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700488 List<MtpStorageManager.MtpObject> objs = null;
489 MtpStorageManager.MtpObject thisObj = null;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700490 if (handle == 0xFFFFFFFF) {
491 // All objects are requested
Jerry Zhang9a018742018-05-10 18:27:13 -0700492 objs = mManager.getObjects(0, format, 0xFFFFFFFF);
493 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700494 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
495 }
496 } else if (handle != 0) {
497 // Add the requested object if format matches
498 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
499 if (obj == null) {
500 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
501 }
502 if (obj.getFormat() == format || format == 0) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700503 thisObj = obj;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700504 }
505 }
506 if (handle == 0 || depth == 1) {
507 if (handle == 0) {
508 handle = 0xFFFFFFFF;
509 }
510 // Get the direct children of root or this object.
Jerry Zhang9a018742018-05-10 18:27:13 -0700511 objs = mManager.getObjects(handle, format,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700512 0xFFFFFFFF);
Jerry Zhang9a018742018-05-10 18:27:13 -0700513 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700514 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
515 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700516 }
517 if (objs == null) {
518 objs = new ArrayList<>();
519 }
520 if (thisObj != null) {
521 objs.add(thisObj);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400522 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400523
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700524 MtpPropertyList ret = new MtpPropertyList(MtpConstants.RESPONSE_OK);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500525 MtpPropertyGroup propertyGroup;
Jerry Zhang9a018742018-05-10 18:27:13 -0700526 for (MtpStorageManager.MtpObject obj : objs) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700527 if (property == 0xffffffff) {
James Weif7f608c2018-08-15 22:23:12 +0800528 if (format == 0 && handle != 0 && handle != 0xffffffff) {
529 // return properties based on the object's format
530 format = obj.getFormat();
531 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700532 // Get all properties supported by this object
James Weif7f608c2018-08-15 22:23:12 +0800533 // format should be the same between get & put
534 propertyGroup = mPropertyGroupsByFormat.get(format);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700535 if (propertyGroup == null) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600536 final int[] propertyList = getSupportedObjectProperties(format);
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600537 propertyGroup = new MtpPropertyGroup(propertyList);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700538 mPropertyGroupsByFormat.put(format, propertyGroup);
539 }
540 } else {
541 // Get this property value
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700542 propertyGroup = mPropertyGroupsByProperty.get(property);
543 if (propertyGroup == null) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600544 final int[] propertyList = new int[]{property};
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600545 propertyGroup = new MtpPropertyGroup(propertyList);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700546 mPropertyGroupsByProperty.put(property, propertyGroup);
547 }
Mike Lockwood71827742015-01-23 10:50:08 -0800548 }
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600549 int err = propertyGroup.getPropertyList(mMediaProvider, obj.getVolumeName(), obj, ret);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700550 if (err != MtpConstants.RESPONSE_OK) {
551 return new MtpPropertyList(err);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400552 }
553 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700554 return ret;
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400555 }
556
Mike Lockwood5ebac832010-10-12 11:33:47 -0400557 private int renameFile(int handle, String newName) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700558 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
559 if (obj == null) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400560 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
561 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700562 Path oldPath = obj.getPath();
Mike Lockwood73e56d92011-12-01 16:58:41 -0500563
Mike Lockwood5ebac832010-10-12 11:33:47 -0400564 // now rename the file. make sure this succeeds before updating database
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700565 if (!mManager.beginRenameObject(obj, newName))
Mike Lockwood5ebac832010-10-12 11:33:47 -0400566 return MtpConstants.RESPONSE_GENERAL_ERROR;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700567 Path newPath = obj.getPath();
568 boolean success = oldPath.toFile().renameTo(newPath.toFile());
Jerry Zhangd470a1e2018-05-14 12:19:08 -0700569 try {
570 Os.access(oldPath.toString(), OsConstants.F_OK);
571 Os.access(newPath.toString(), OsConstants.F_OK);
572 } catch (ErrnoException e) {
573 // Ignore. Could fail if the metadata was already updated.
574 }
575
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700576 if (!mManager.endRenameObject(obj, oldPath.getFileName().toString(), success)) {
577 Log.e(TAG, "Failed to end rename object");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400578 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400579 if (!success) {
580 return MtpConstants.RESPONSE_GENERAL_ERROR;
581 }
582
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700583 // finally update MediaProvider
Mike Lockwood5ebac832010-10-12 11:33:47 -0400584 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700585 values.put(Files.FileColumns.DATA, newPath.toString());
586 String[] whereArgs = new String[]{oldPath.toString()};
Mike Lockwood5ebac832010-10-12 11:33:47 -0400587 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400588 // note - we are relying on a special case in MediaProvider.update() to update
589 // the paths for all children in the case where this is a directory.
Jeff Sharkey8f5cdbb2019-12-16 14:10:53 -0700590 final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600591 mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400592 } catch (RemoteException e) {
593 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
594 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400595
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800596 // check if nomedia status changed
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700597 if (obj.isDir()) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800598 // for directories, check if renamed from something hidden to something non-hidden
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700599 if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700600 MediaStore.scanFile(mContext.getContentResolver(), newPath.toFile());
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800601 }
602 } else {
603 // for files, check if renamed from .nomedia to something else
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700604 if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
605 && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700606 MediaStore.scanFile(mContext.getContentResolver(), newPath.getParent().toFile());
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800607 }
608 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400609 return MtpConstants.RESPONSE_OK;
610 }
611
Jeff Sharkeyff200952019-03-24 12:50:51 -0600612 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700613 private int beginMoveObject(int handle, int newParent, int newStorage) {
614 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
615 MtpStorageManager.MtpObject parent = newParent == 0 ?
616 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
617 if (obj == null || parent == null)
618 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Jerry Zhang952558d42017-09-26 17:49:52 -0700619
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700620 boolean allowed = mManager.beginMoveObject(obj, parent);
621 return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR;
622 }
623
Jeff Sharkeyff200952019-03-24 12:50:51 -0600624 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700625 private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage,
626 int objId, boolean success) {
627 MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ?
628 mManager.getStorageRoot(oldStorage) : mManager.getObject(oldParent);
629 MtpStorageManager.MtpObject newParentObj = newParent == 0 ?
630 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
631 MtpStorageManager.MtpObject obj = mManager.getObject(objId);
632 String name = obj.getName();
633 if (newParentObj == null || oldParentObj == null
634 ||!mManager.endMoveObject(oldParentObj, newParentObj, name, success)) {
635 Log.e(TAG, "Failed to end move object");
636 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700637 }
638
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700639 obj = mManager.getObject(objId);
640 if (!success || obj == null)
641 return;
642 // Get parent info from MediaProvider, since the id is different from MTP's
Jerry Zhang952558d42017-09-26 17:49:52 -0700643 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700644 Path path = newParentObj.getPath().resolve(name);
645 Path oldPath = oldParentObj.getPath().resolve(name);
646 values.put(Files.FileColumns.DATA, path.toString());
647 if (obj.getParent().isRoot()) {
648 values.put(Files.FileColumns.PARENT, 0);
649 } else {
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600650 int parentId = findInMedia(newParentObj, path.getParent());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700651 if (parentId != -1) {
652 values.put(Files.FileColumns.PARENT, parentId);
653 } else {
654 // The new parent isn't in MediaProvider, so delete the object instead
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600655 deleteFromMedia(obj, oldPath, obj.isDir());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700656 return;
657 }
658 }
659 // update MediaProvider
660 Cursor c = null;
661 String[] whereArgs = new String[]{oldPath.toString()};
Jerry Zhang952558d42017-09-26 17:49:52 -0700662 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700663 int parentId = -1;
664 if (!oldParentObj.isRoot()) {
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600665 parentId = findInMedia(oldParentObj, oldPath.getParent());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700666 }
667 if (oldParentObj.isRoot() || parentId != -1) {
668 // Old parent exists in MediaProvider - perform a move
669 // note - we are relying on a special case in MediaProvider.update() to update
670 // the paths for all children in the case where this is a directory.
Jeff Sharkey8f5cdbb2019-12-16 14:10:53 -0700671 final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600672 mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700673 } else {
674 // Old parent doesn't exist - add the object
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700675 MediaStore.scanFile(mContext.getContentResolver(), path.toFile());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700676 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700677 } catch (RemoteException e) {
678 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
679 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700680 }
681
Jeff Sharkeyff200952019-03-24 12:50:51 -0600682 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700683 private int beginCopyObject(int handle, int newParent, int newStorage) {
684 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
685 MtpStorageManager.MtpObject parent = newParent == 0 ?
686 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
687 if (obj == null || parent == null)
688 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
689 return mManager.beginCopyObject(obj, parent);
690 }
691
Jeff Sharkeyff200952019-03-24 12:50:51 -0600692 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700693 private void endCopyObject(int handle, boolean success) {
694 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
695 if (obj == null || !mManager.endCopyObject(obj, success)) {
696 Log.e(TAG, "Failed to end copy object");
697 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700698 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700699 if (!success) {
700 return;
701 }
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700702 MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
Jerry Zhang952558d42017-09-26 17:49:52 -0700703 }
704
Jeff Sharkeyff200952019-03-24 12:50:51 -0600705 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400706 private int setObjectProperty(int handle, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700707 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400708 switch (property) {
709 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
710 return renameFile(handle, stringValue);
711
712 default:
713 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
714 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400715 }
716
Jeff Sharkeyff200952019-03-24 12:50:51 -0600717 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400718 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400719 switch (property) {
720 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
721 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500722 // writable string properties kept in shared preferences
723 String value = mDeviceProperties.getString(Integer.toString(property), "");
724 int length = value.length();
725 if (length > 255) {
726 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400727 }
Mike Lockwood775de952011-03-05 17:34:11 -0500728 value.getChars(0, length, outStringValue, 0);
729 outStringValue[length] = 0;
730 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800731 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
732 // use screen size as max image size
Andrii Kuliane57f2dc2020-01-26 20:59:07 -0800733 // TODO(b/147721765): Add support for foldables/multi-display devices.
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700734 Display display = ((WindowManager) mContext.getSystemService(
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800735 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700736 int width = display.getMaximumSizeDimension();
737 int height = display.getMaximumSizeDimension();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700738 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800739 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
740 outStringValue[imageSize.length()] = 0;
741 return MtpConstants.RESPONSE_OK;
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800742 case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
743 outIntValue[0] = mDeviceType;
744 return MtpConstants.RESPONSE_OK;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700745 case MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL:
746 outIntValue[0] = mBatteryLevel;
747 outIntValue[1] = mBatteryScale;
748 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800749 default:
750 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
751 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400752 }
753
Jeff Sharkeyff200952019-03-24 12:50:51 -0600754 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400755 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400756 switch (property) {
757 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
758 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500759 // writable string properties kept in shared prefs
760 SharedPreferences.Editor e = mDeviceProperties.edit();
761 e.putString(Integer.toString(property), stringValue);
762 return (e.commit() ? MtpConstants.RESPONSE_OK
763 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400764 }
765
766 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
767 }
768
Jeff Sharkeyff200952019-03-24 12:50:51 -0600769 @VisibleForNative
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400770 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700771 char[] outName, long[] outCreatedModified) {
772 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
773 if (obj == null) {
774 return false;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400775 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700776 outStorageFormatParent[0] = obj.getStorageId();
777 outStorageFormatParent[1] = obj.getFormat();
778 outStorageFormatParent[2] = obj.getParent().isRoot() ? 0 : obj.getParent().getId();
779
780 int nameLen = Integer.min(obj.getName().length(), 255);
781 obj.getName().getChars(0, nameLen, outName, 0);
782 outName[nameLen] = 0;
783
784 outCreatedModified[0] = obj.getModifiedTime();
785 outCreatedModified[1] = obj.getModifiedTime();
786 return true;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400787 }
788
Jeff Sharkeyff200952019-03-24 12:50:51 -0600789 @VisibleForNative
Mike Lockwood365e03e2010-12-08 16:08:01 -0800790 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700791 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
792 if (obj == null) {
793 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood01788562010-10-11 11:22:19 -0400794 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700795
796 String path = obj.getPath().toString();
797 int pathLen = Integer.min(path.length(), 4096);
798 path.getChars(0, pathLen, outFilePath, 0);
799 outFilePath[pathLen] = 0;
800
801 outFileLengthFormat[0] = obj.getSize();
802 outFileLengthFormat[1] = obj.getFormat();
803 return MtpConstants.RESPONSE_OK;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400804 }
805
Mike Lockwood71827742015-01-23 10:50:08 -0800806 private int getObjectFormat(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700807 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
808 if (obj == null) {
Mike Lockwood71827742015-01-23 10:50:08 -0800809 return -1;
Mike Lockwood71827742015-01-23 10:50:08 -0800810 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700811 return obj.getFormat();
Mike Lockwood71827742015-01-23 10:50:08 -0800812 }
813
James Weibb3f5482019-12-24 19:12:29 +0800814 private byte[] getThumbnailProcess(String path, Bitmap bitmap) {
815 try {
816 if (bitmap == null) {
817 Log.d(TAG, "getThumbnailProcess: Fail to generate thumbnail. Probably unsupported or corrupted image");
818 return null;
819 }
820
821 ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
822 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteStream);
823
James Wei1f1f81e2021-03-22 13:17:01 +0800824 if (byteStream.size() > MAX_THUMB_SIZE) {
825 Log.w(TAG, "getThumbnailProcess: size=" + byteStream.size());
James Weibb3f5482019-12-24 19:12:29 +0800826 return null;
James Wei1f1f81e2021-03-22 13:17:01 +0800827 }
James Weibb3f5482019-12-24 19:12:29 +0800828
829 byte[] byteArray = byteStream.toByteArray();
830
831 return byteArray;
832 } catch (OutOfMemoryError oomEx) {
833 Log.w(TAG, "OutOfMemoryError:" + oomEx);
834 }
835 return null;
836 }
837
Jeff Sharkeyff200952019-03-24 12:50:51 -0600838 @VisibleForNative
James Wei19ded222019-12-24 19:48:51 +0800839 @VisibleForTesting
840 public boolean getThumbnailInfo(int handle, long[] outLongs) {
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700841 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
842 if (obj == null) {
843 return false;
844 }
845
846 String path = obj.getPath().toString();
847 switch (obj.getFormat()) {
848 case MtpConstants.FORMAT_HEIF:
849 case MtpConstants.FORMAT_EXIF_JPEG:
850 case MtpConstants.FORMAT_JFIF:
851 try {
852 ExifInterface exif = new ExifInterface(path);
853 long[] thumbOffsetAndSize = exif.getThumbnailRange();
854 outLongs[0] = thumbOffsetAndSize != null ? thumbOffsetAndSize[1] : 0;
855 outLongs[1] = exif.getAttributeInt(ExifInterface.TAG_PIXEL_X_DIMENSION, 0);
856 outLongs[2] = exif.getAttributeInt(ExifInterface.TAG_PIXEL_Y_DIMENSION, 0);
James Wei1f1f81e2021-03-22 13:17:01 +0800857 if (exif.getThumbnailRange() != null) {
858 if ((outLongs[0] == 0) || (outLongs[1] == 0) || (outLongs[2] == 0)) {
859 Log.d(TAG, "getThumbnailInfo: check thumb info:"
860 + thumbOffsetAndSize[0] + "," + thumbOffsetAndSize[1]
861 + "," + outLongs[1] + "," + outLongs[2]);
862 }
863
864 return true;
865 }
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700866 } catch (IOException e) {
867 // ignore and fall through
868 }
James Weibb3f5482019-12-24 19:12:29 +0800869
870// Note: above formats will fall through and go on below thumbnail generation if Exif processing fails
871 case MtpConstants.FORMAT_PNG:
872 case MtpConstants.FORMAT_GIF:
873 case MtpConstants.FORMAT_BMP:
874 outLongs[0] = MAX_THUMB_SIZE;
875 // only non-zero Width & Height needed. Actual size will be retrieved upon getThumbnailData by Host
876 outLongs[1] = 320;
877 outLongs[2] = 240;
878 return true;
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700879 }
880 return false;
881 }
882
883 @VisibleForNative
James Wei19ded222019-12-24 19:48:51 +0800884 @VisibleForTesting
885 public byte[] getThumbnailData(int handle) {
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700886 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
887 if (obj == null) {
888 return null;
889 }
890
891 String path = obj.getPath().toString();
892 switch (obj.getFormat()) {
893 case MtpConstants.FORMAT_HEIF:
894 case MtpConstants.FORMAT_EXIF_JPEG:
895 case MtpConstants.FORMAT_JFIF:
896 try {
897 ExifInterface exif = new ExifInterface(path);
James Wei1f1f81e2021-03-22 13:17:01 +0800898
899 if (exif.getThumbnailRange() != null)
900 return exif.getThumbnail();
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700901 } catch (IOException e) {
902 // ignore and fall through
903 }
James Weibb3f5482019-12-24 19:12:29 +0800904
905// Note: above formats will fall through and go on below thumbnail generation if Exif processing fails
906 case MtpConstants.FORMAT_PNG:
907 case MtpConstants.FORMAT_GIF:
908 case MtpConstants.FORMAT_BMP:
909 {
910 Bitmap bitmap = ThumbnailUtils.createImageThumbnail(path, MediaStore.Images.Thumbnails.MINI_KIND);
911 byte[] byteArray = getThumbnailProcess(path, bitmap);
912
913 return byteArray;
914 }
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700915 }
916 return null;
917 }
918
919 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700920 private int beginDeleteObject(int handle) {
921 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
922 if (obj == null) {
923 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
924 }
925 if (!mManager.beginRemoveObject(obj)) {
926 return MtpConstants.RESPONSE_GENERAL_ERROR;
927 }
928 return MtpConstants.RESPONSE_OK;
929 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800930
Jeff Sharkeyff200952019-03-24 12:50:51 -0600931 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700932 private void endDeleteObject(int handle, boolean success) {
933 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
934 if (obj == null) {
935 return;
936 }
937 if (!mManager.endRemoveObject(obj, success))
938 Log.e(TAG, "Failed to end remove object");
939 if (success)
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600940 deleteFromMedia(obj, obj.getPath(), obj.isDir());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700941 }
942
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600943 private int findInMedia(MtpStorageManager.MtpObject obj, Path path) {
Jeff Sharkey8f5cdbb2019-12-16 14:10:53 -0700944 final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600945
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700946 int ret = -1;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800947 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400948 try {
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600949 c = mMediaProvider.query(objectsUri, ID_PROJECTION, PATH_WHERE,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700950 new String[]{path.toString()}, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800951 if (c != null && c.moveToNext()) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700952 ret = c.getInt(0);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800953 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700954 } catch (RemoteException e) {
955 Log.e(TAG, "Error finding " + path + " in MediaProvider");
956 } finally {
957 if (c != null)
958 c.close();
959 }
960 return ret;
961 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800962
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600963 private void deleteFromMedia(MtpStorageManager.MtpObject obj, Path path, boolean isDir) {
Jeff Sharkey8f5cdbb2019-12-16 14:10:53 -0700964 final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700965 try {
966 // Delete the object(s) from MediaProvider, but ignore errors.
967 if (isDir) {
Mike Lockwood55f808c2010-12-14 13:14:29 -0800968 // recursive case - delete all children first
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600969 mMediaProvider.delete(objectsUri,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700970 // the 'like' makes it use the index, the 'lower()' makes it correct
971 // when the path contains sqlite wildcard characters
972 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
973 new String[]{path + "/%", Integer.toString(path.toString().length() + 1),
974 path.toString() + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800975 }
976
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700977 String[] whereArgs = new String[]{path.toString()};
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600978 if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700979 if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700980 MediaStore.scanFile(mContext.getContentResolver(), path.getParent().toFile());
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800981 }
Mike Lockwood59c777a2010-08-02 10:37:41 -0400982 } else {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700983 Log.i(TAG, "Mediaprovider didn't delete " + path);
Mike Lockwood59c777a2010-08-02 10:37:41 -0400984 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700985 } catch (Exception e) {
986 Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400987 }
988 }
989
Jeff Sharkeyff200952019-03-24 12:50:51 -0600990 @VisibleForNative
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400991 private int[] getObjectReferences(int handle) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400992 return null;
993 }
994
Jeff Sharkeyff200952019-03-24 12:50:51 -0600995 @VisibleForNative
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400996 private int setObjectReferences(int handle, int[] references) {
Jeff Sharkey8f5cdbb2019-12-16 14:10:53 -0700997 return MtpConstants.RESPONSE_OPERATION_NOT_SUPPORTED;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400998 }
999
Jeff Sharkeyff200952019-03-24 12:50:51 -06001000 @VisibleForNative
Ashok Bhate2e59322013-12-17 19:04:19 +00001001 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001002
1003 private native final void native_setup();
1004 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001005}