blob: 20d711cf4c54d3fb7d1cec6cc10b25821b9f551b [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;
Ivan Chiang20079f12020-11-10 10:44:05 +080022import android.content.ContentResolver;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070023import android.content.Context;
Mike Lockwood2837eef2010-08-31 16:25:12 -040024import android.content.Intent;
Mike Lockwood56c85242014-03-07 13:29:08 -080025import android.content.IntentFilter;
Mike Lockwood775de952011-03-05 17:34:11 -050026import android.content.SharedPreferences;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040027import android.database.Cursor;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040028import android.database.sqlite.SQLiteDatabase;
James Weibb3f5482019-12-24 19:12:29 +080029import android.graphics.Bitmap;
Zim223e3b52020-11-13 20:12:04 +000030import android.media.ApplicationMediaCapabilities;
Marco Nelissenf7ec1682019-06-27 15:56:32 -070031import android.media.ExifInterface;
Zim223e3b52020-11-13 20:12:04 +000032import android.media.MediaFormat;
James Weibb3f5482019-12-24 19:12:29 +080033import android.media.ThumbnailUtils;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040034import android.net.Uri;
Mike Lockwood56c85242014-03-07 13:29:08 -080035import android.os.BatteryManager;
Zim223e3b52020-11-13 20:12:04 +000036import android.os.Bundle;
37import android.os.RemoteException;
Jerry Zhang13bb2f42016-12-14 15:39:29 -080038import android.os.SystemProperties;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070039import android.os.storage.StorageVolume;
Mike Lockwooda3156052010-11-20 12:28:27 -050040import android.provider.MediaStore;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040041import android.provider.MediaStore.Files;
Jerry Zhangd470a1e2018-05-14 12:19:08 -070042import android.system.ErrnoException;
43import android.system.Os;
44import android.system.OsConstants;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040045import android.util.Log;
Jeff Sharkey42bf6d82019-04-17 11:16:12 -060046import android.util.SparseArray;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080047import android.view.Display;
48import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040049
Jeff Sharkeyff200952019-03-24 12:50:51 -060050import com.android.internal.annotations.VisibleForNative;
James Wei19ded222019-12-24 19:48:51 +080051import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkeyff200952019-03-24 12:50:51 -060052
Jeff Sharkey60cfad82016-01-05 17:30:57 -070053import dalvik.system.CloseGuard;
54
Jerry Zhangf9c5c252017-08-16 18:07:51 -070055import com.google.android.collect.Sets;
56
James Weibb3f5482019-12-24 19:12:29 +080057import java.io.ByteArrayOutputStream;
Mike Lockwood5ebac832010-10-12 11:33:47 -040058import java.io.File;
Zim223e3b52020-11-13 20:12:04 +000059import java.io.FileNotFoundException;
Marco Nelissenf7ec1682019-06-27 15:56:32 -070060import java.io.IOException;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070061import java.nio.file.Path;
62import java.nio.file.Paths;
63import java.util.ArrayList;
64import java.util.Arrays;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050065import java.util.HashMap;
Jerry Zhang9a018742018-05-10 18:27:13 -070066import java.util.List;
dujin.chafe464a72011-11-22 12:13:33 +090067import java.util.Locale;
James Wei9c968fd2018-11-12 21:43:15 +080068import java.util.Objects;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070069import java.util.concurrent.atomic.AtomicBoolean;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070070import java.util.stream.IntStream;
Mike Lockwood5ebac832010-10-12 11:33:47 -040071
Mike Lockwoodd21eac92010-07-03 00:44:05 -040072/**
Jerry Zhangf9c5c252017-08-16 18:07:51 -070073 * MtpDatabase provides an interface for MTP operations that MtpServer can use. To do this, it uses
74 * MtpStorageManager for filesystem operations and MediaProvider to get media metadata. File
75 * operations are also reflected in MediaProvider if possible.
76 * operations
Mike Lockwoodd21eac92010-07-03 00:44:05 -040077 * {@hide}
78 */
Jeff Sharkey60cfad82016-01-05 17:30:57 -070079public class MtpDatabase implements AutoCloseable {
Jerry Zhangf9c5c252017-08-16 18:07:51 -070080 private static final String TAG = MtpDatabase.class.getSimpleName();
James Weibb3f5482019-12-24 19:12:29 +080081 private static final int MAX_THUMB_SIZE = (200 * 1024);
Mike Lockwoodd21eac92010-07-03 00:44:05 -040082
Mike Lockwood2837eef2010-08-31 16:25:12 -040083 private final Context mContext;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070084 private final ContentProviderClient mMediaProvider;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070085
86 private final AtomicBoolean mClosed = new AtomicBoolean();
87 private final CloseGuard mCloseGuard = CloseGuard.get();
88
Jerry Zhangf9c5c252017-08-16 18:07:51 -070089 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040090
Mike Lockwood7d7fb632010-12-01 18:46:23 -050091 // cached property groups for single properties
Jeff Sharkey42bf6d82019-04-17 11:16:12 -060092 private final SparseArray<MtpPropertyGroup> mPropertyGroupsByProperty = new SparseArray<>();
Mike Lockwood7d7fb632010-12-01 18:46:23 -050093
94 // cached property groups for all properties for a given format
Jeff Sharkey42bf6d82019-04-17 11:16:12 -060095 private final SparseArray<MtpPropertyGroup> mPropertyGroupsByFormat = new SparseArray<>();
Mike Lockwood2837eef2010-08-31 16:25:12 -040096
Mike Lockwood775de952011-03-05 17:34:11 -050097 // SharedPreferences for writable MTP device properties
98 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040099
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700100 // Cached device properties
Mike Lockwood56c85242014-03-07 13:29:08 -0800101 private int mBatteryLevel;
102 private int mBatteryScale;
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800103 private int mDeviceType;
James Wei4f1a6d62021-06-19 20:58:00 +0800104 private String mHostType;
105 private boolean mSkipThumbForHost = false;
Kevin Rocardf0fbd642021-11-12 16:13:55 +0000106 private volatile boolean mHostIsWindows = false;
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800107
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700108 private MtpServer mServer;
109 private MtpStorageManager mManager;
110
111 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700112 private static final String NO_MEDIA = ".nomedia";
113
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400114 static {
115 System.loadLibrary("media_jni");
116 }
117
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700118 private static final int[] PLAYBACK_FORMATS = {
119 // allow transferring arbitrary files
Mike Lockwoode5211692010-09-08 13:50:45 -0400120 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400121
Mike Lockwood792ec842010-09-09 15:30:10 -0400122 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400123 MtpConstants.FORMAT_TEXT,
124 MtpConstants.FORMAT_HTML,
125 MtpConstants.FORMAT_WAV,
126 MtpConstants.FORMAT_MP3,
127 MtpConstants.FORMAT_MPEG,
128 MtpConstants.FORMAT_EXIF_JPEG,
129 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800130 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400131 MtpConstants.FORMAT_GIF,
132 MtpConstants.FORMAT_JFIF,
133 MtpConstants.FORMAT_PNG,
134 MtpConstants.FORMAT_TIFF,
135 MtpConstants.FORMAT_WMA,
136 MtpConstants.FORMAT_OGG,
137 MtpConstants.FORMAT_AAC,
138 MtpConstants.FORMAT_MP4_CONTAINER,
139 MtpConstants.FORMAT_MP2,
140 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400141 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400142 MtpConstants.FORMAT_WPL_PLAYLIST,
143 MtpConstants.FORMAT_M3U_PLAYLIST,
144 MtpConstants.FORMAT_PLS_PLAYLIST,
145 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800146 MtpConstants.FORMAT_FLAC,
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100147 MtpConstants.FORMAT_DNG,
Chong Zhang6e18cce2017-08-16 11:57:02 -0700148 MtpConstants.FORMAT_HEIF,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700149 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400150
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700151 private static final int[] FILE_PROPERTIES = {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400152 MtpConstants.PROPERTY_STORAGE_ID,
153 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400154 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400155 MtpConstants.PROPERTY_OBJECT_SIZE,
156 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400157 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400158 MtpConstants.PROPERTY_PERSISTENT_UID,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700159 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400160 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800161 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400162 MtpConstants.PROPERTY_DATE_ADDED,
163 };
164
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700165 private static final int[] AUDIO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400166 MtpConstants.PROPERTY_ARTIST,
167 MtpConstants.PROPERTY_ALBUM_NAME,
168 MtpConstants.PROPERTY_ALBUM_ARTIST,
169 MtpConstants.PROPERTY_TRACK,
170 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
171 MtpConstants.PROPERTY_DURATION,
James Weif545a3a2020-05-01 15:07:23 +0800172 MtpConstants.PROPERTY_GENRE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400173 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700174 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
175 MtpConstants.PROPERTY_BITRATE_TYPE,
176 MtpConstants.PROPERTY_AUDIO_BITRATE,
177 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
178 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400179 };
180
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700181 private static final int[] VIDEO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400182 MtpConstants.PROPERTY_ARTIST,
183 MtpConstants.PROPERTY_ALBUM_NAME,
184 MtpConstants.PROPERTY_DURATION,
185 MtpConstants.PROPERTY_DESCRIPTION,
186 };
187
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700188 private static final int[] IMAGE_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400189 MtpConstants.PROPERTY_DESCRIPTION,
190 };
191
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700192 private static final int[] DEVICE_PROPERTIES = {
193 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
194 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
195 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
196 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
197 MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
James Wei4f1a6d62021-06-19 20:58:00 +0800198 MtpConstants.DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700199 };
200
Jeff Sharkeyff200952019-03-24 12:50:51 -0600201 @VisibleForNative
Mike Lockwoodae078f72010-09-26 12:35:51 -0400202 private int[] getSupportedObjectProperties(int format) {
203 switch (format) {
204 case MtpConstants.FORMAT_MP3:
205 case MtpConstants.FORMAT_WAV:
206 case MtpConstants.FORMAT_WMA:
207 case MtpConstants.FORMAT_OGG:
208 case MtpConstants.FORMAT_AAC:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700209 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
210 Arrays.stream(AUDIO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400211 case MtpConstants.FORMAT_MPEG:
212 case MtpConstants.FORMAT_3GP_CONTAINER:
213 case MtpConstants.FORMAT_WMV:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700214 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
215 Arrays.stream(VIDEO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400216 case MtpConstants.FORMAT_EXIF_JPEG:
217 case MtpConstants.FORMAT_GIF:
218 case MtpConstants.FORMAT_PNG:
219 case MtpConstants.FORMAT_BMP:
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100220 case MtpConstants.FORMAT_DNG:
Chong Zhang6e18cce2017-08-16 11:57:02 -0700221 case MtpConstants.FORMAT_HEIF:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700222 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
223 Arrays.stream(IMAGE_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400224 default:
225 return FILE_PROPERTIES;
226 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400227 }
228
Jeff Sharkeyff200952019-03-24 12:50:51 -0600229 public static Uri getObjectPropertiesUri(int format, String volumeName) {
230 switch (format) {
231 case MtpConstants.FORMAT_MP3:
232 case MtpConstants.FORMAT_WAV:
233 case MtpConstants.FORMAT_WMA:
234 case MtpConstants.FORMAT_OGG:
235 case MtpConstants.FORMAT_AAC:
236 return MediaStore.Audio.Media.getContentUri(volumeName);
237 case MtpConstants.FORMAT_MPEG:
238 case MtpConstants.FORMAT_3GP_CONTAINER:
239 case MtpConstants.FORMAT_WMV:
240 return MediaStore.Video.Media.getContentUri(volumeName);
241 case MtpConstants.FORMAT_EXIF_JPEG:
242 case MtpConstants.FORMAT_GIF:
243 case MtpConstants.FORMAT_PNG:
244 case MtpConstants.FORMAT_BMP:
245 case MtpConstants.FORMAT_DNG:
246 case MtpConstants.FORMAT_HEIF:
247 return MediaStore.Images.Media.getContentUri(volumeName);
248 default:
249 return MediaStore.Files.getContentUri(volumeName);
250 }
251 }
252
253 @VisibleForNative
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400254 private int[] getSupportedDeviceProperties() {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700255 return DEVICE_PROPERTIES;
256 }
257
Jeff Sharkeyff200952019-03-24 12:50:51 -0600258 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700259 private int[] getSupportedPlaybackFormats() {
260 return PLAYBACK_FORMATS;
261 }
262
Jeff Sharkeyff200952019-03-24 12:50:51 -0600263 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700264 private int[] getSupportedCaptureFormats() {
265 // no capture formats yet
266 return null;
267 }
268
269 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
270 @Override
271 public void onReceive(Context context, Intent intent) {
272 String action = intent.getAction();
273 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
274 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
275 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
276 if (newLevel != mBatteryLevel) {
277 mBatteryLevel = newLevel;
278 if (mServer != null) {
279 // send device property changed event
280 mServer.sendDevicePropertyChanged(
281 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
282 }
283 }
284 }
285 }
286 };
287
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600288 public MtpDatabase(Context context, String[] subDirectories) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700289 native_setup();
James Wei9c968fd2018-11-12 21:43:15 +0800290 mContext = Objects.requireNonNull(context);
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800291 mMediaProvider = context.getContentResolver()
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700292 .acquireContentProviderClient(MediaStore.AUTHORITY);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700293 mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
294 @Override
295 public void sendObjectAdded(int id) {
296 if (MtpDatabase.this.mServer != null)
297 MtpDatabase.this.mServer.sendObjectAdded(id);
298 }
299
300 @Override
301 public void sendObjectRemoved(int id) {
302 if (MtpDatabase.this.mServer != null)
303 MtpDatabase.this.mServer.sendObjectRemoved(id);
304 }
Jamese4f680e2018-07-02 17:42:07 +0800305
306 @Override
307 public void sendObjectInfoChanged(int id) {
308 if (MtpDatabase.this.mServer != null)
309 MtpDatabase.this.mServer.sendObjectInfoChanged(id);
310 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700311 }, subDirectories == null ? null : Sets.newHashSet(subDirectories));
312
313 initDeviceProperties(context);
314 mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
315 mCloseGuard.open("close");
316 }
317
318 public void setServer(MtpServer server) {
319 mServer = server;
320 // always unregister before registering
321 try {
322 mContext.unregisterReceiver(mBatteryReceiver);
323 } catch (IllegalArgumentException e) {
324 // wasn't previously registered, ignore
325 }
326 // register for battery notifications when we are connected
327 if (server != null) {
328 mContext.registerReceiver(mBatteryReceiver,
329 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
330 }
331 }
332
James Wei9c968fd2018-11-12 21:43:15 +0800333 public Context getContext() {
334 return mContext;
335 }
336
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700337 @Override
338 public void close() {
339 mManager.close();
340 mCloseGuard.close();
341 if (mClosed.compareAndSet(false, true)) {
Jerry Zhang484ea672018-03-02 15:40:03 -0800342 if (mMediaProvider != null) {
343 mMediaProvider.close();
344 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700345 native_finalize();
346 }
347 }
348
349 @Override
350 protected void finalize() throws Throwable {
351 try {
352 if (mCloseGuard != null) {
353 mCloseGuard.warnIfOpen();
354 }
355 close();
356 } finally {
357 super.finalize();
358 }
359 }
360
361 public void addStorage(StorageVolume storage) {
Kevin Rocardf0fbd642021-11-12 16:13:55 +0000362 MtpStorage mtpStorage = mManager.addMtpStorage(storage, () -> mHostIsWindows);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700363 mStorageMap.put(storage.getPath(), mtpStorage);
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700364 if (mServer != null) {
365 mServer.addStorage(mtpStorage);
366 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700367 }
368
369 public void removeStorage(StorageVolume storage) {
370 MtpStorage mtpStorage = mStorageMap.get(storage.getPath());
371 if (mtpStorage == null) {
372 return;
373 }
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700374 if (mServer != null) {
375 mServer.removeStorage(mtpStorage);
376 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700377 mManager.removeMtpStorage(mtpStorage);
378 mStorageMap.remove(storage.getPath());
379 }
380
381 private void initDeviceProperties(Context context) {
382 final String devicePropertiesName = "device-properties";
383 mDeviceProperties = context.getSharedPreferences(devicePropertiesName,
384 Context.MODE_PRIVATE);
385 File databaseFile = context.getDatabasePath(devicePropertiesName);
386
387 if (databaseFile.exists()) {
388 // for backward compatibility - read device properties from sqlite database
389 // and migrate them to shared prefs
390 SQLiteDatabase db = null;
391 Cursor c = null;
392 try {
393 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
394 if (db != null) {
395 c = db.query("properties", new String[]{"_id", "code", "value"},
396 null, null, null, null, null);
397 if (c != null) {
398 SharedPreferences.Editor e = mDeviceProperties.edit();
399 while (c.moveToNext()) {
400 String name = c.getString(1);
401 String value = c.getString(2);
402 e.putString(name, value);
403 }
404 e.commit();
405 }
406 }
407 } catch (Exception e) {
408 Log.e(TAG, "failed to migrate device properties", e);
409 } finally {
410 if (c != null) c.close();
411 if (db != null) db.close();
412 }
413 context.deleteDatabase(devicePropertiesName);
414 }
James Wei4f1a6d62021-06-19 20:58:00 +0800415 mHostType = "";
416 mSkipThumbForHost = false;
Kevin Rocardf0fbd642021-11-12 16:13:55 +0000417 mHostIsWindows = false;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700418 }
419
Jeff Sharkeyff200952019-03-24 12:50:51 -0600420 @VisibleForNative
James Wei19ded222019-12-24 19:48:51 +0800421 @VisibleForTesting
422 public int beginSendObject(String path, int format, int parent, int storageId) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700423 MtpStorageManager.MtpObject parentObj =
424 parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
425 if (parentObj == null) {
426 return -1;
427 }
428
429 Path objPath = Paths.get(path);
430 return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format);
431 }
432
Jeff Sharkeyff200952019-03-24 12:50:51 -0600433 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700434 private void endSendObject(int handle, boolean succeeded) {
435 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
436 if (obj == null || !mManager.endSendObject(obj, succeeded)) {
437 Log.e(TAG, "Failed to successfully end send object");
438 return;
439 }
440 // Add the new file to MediaProvider
441 if (succeeded) {
Ivan Chiang20079f12020-11-10 10:44:05 +0800442 updateMediaStore(mContext, obj.getPath().toFile());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700443 }
444 }
445
Jeff Sharkeyff200952019-03-24 12:50:51 -0600446 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700447 private void rescanFile(String path, int handle, int format) {
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700448 MediaStore.scanFile(mContext.getContentResolver(), new File(path));
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700449 }
450
Jeff Sharkeyff200952019-03-24 12:50:51 -0600451 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700452 private int[] getObjectList(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700453 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700454 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700455 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700456 return null;
457 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700458 int[] ret = new int[objs.size()];
459 for (int i = 0; i < objs.size(); i++) {
460 ret[i] = objs.get(i).getId();
461 }
462 return ret;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700463 }
464
Jeff Sharkeyff200952019-03-24 12:50:51 -0600465 @VisibleForNative
James Wei82062b92020-04-20 21:40:50 +0800466 @VisibleForTesting
467 public int getNumObjects(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700468 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700469 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700470 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700471 return -1;
472 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700473 return objs.size();
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400474 }
475
Jeff Sharkeyff200952019-03-24 12:50:51 -0600476 @VisibleForNative
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900477 private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700478 int groupCode, int depth) {
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400479 // FIXME - implement group support
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700480 if (property == 0) {
481 if (groupCode == 0) {
482 return new MtpPropertyList(MtpConstants.RESPONSE_PARAMETER_NOT_SUPPORTED);
483 }
484 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
485 }
486 if (depth == 0xFFFFFFFF && (handle == 0 || handle == 0xFFFFFFFF)) {
487 // request all objects starting at root
488 handle = 0xFFFFFFFF;
489 depth = 0;
490 }
491 if (!(depth == 0 || depth == 1)) {
492 // we only support depth 0 and 1
493 // depth 0: single object, depth 1: immediate children
494 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
495 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700496 List<MtpStorageManager.MtpObject> objs = null;
497 MtpStorageManager.MtpObject thisObj = null;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700498 if (handle == 0xFFFFFFFF) {
499 // All objects are requested
Jerry Zhang9a018742018-05-10 18:27:13 -0700500 objs = mManager.getObjects(0, format, 0xFFFFFFFF);
501 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700502 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
503 }
504 } else if (handle != 0) {
505 // Add the requested object if format matches
506 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
507 if (obj == null) {
508 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
509 }
510 if (obj.getFormat() == format || format == 0) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700511 thisObj = obj;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700512 }
513 }
514 if (handle == 0 || depth == 1) {
515 if (handle == 0) {
516 handle = 0xFFFFFFFF;
517 }
518 // Get the direct children of root or this object.
Jerry Zhang9a018742018-05-10 18:27:13 -0700519 objs = mManager.getObjects(handle, format,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700520 0xFFFFFFFF);
Jerry Zhang9a018742018-05-10 18:27:13 -0700521 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700522 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
523 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700524 }
525 if (objs == null) {
526 objs = new ArrayList<>();
527 }
528 if (thisObj != null) {
529 objs.add(thisObj);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400530 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400531
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700532 MtpPropertyList ret = new MtpPropertyList(MtpConstants.RESPONSE_OK);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500533 MtpPropertyGroup propertyGroup;
Jerry Zhang9a018742018-05-10 18:27:13 -0700534 for (MtpStorageManager.MtpObject obj : objs) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700535 if (property == 0xffffffff) {
James Weif7f608c2018-08-15 22:23:12 +0800536 if (format == 0 && handle != 0 && handle != 0xffffffff) {
537 // return properties based on the object's format
538 format = obj.getFormat();
539 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700540 // Get all properties supported by this object
James Weif7f608c2018-08-15 22:23:12 +0800541 // format should be the same between get & put
542 propertyGroup = mPropertyGroupsByFormat.get(format);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700543 if (propertyGroup == null) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600544 final int[] propertyList = getSupportedObjectProperties(format);
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600545 propertyGroup = new MtpPropertyGroup(propertyList);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700546 mPropertyGroupsByFormat.put(format, propertyGroup);
547 }
548 } else {
549 // Get this property value
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700550 propertyGroup = mPropertyGroupsByProperty.get(property);
551 if (propertyGroup == null) {
Jeff Sharkeyff200952019-03-24 12:50:51 -0600552 final int[] propertyList = new int[]{property};
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600553 propertyGroup = new MtpPropertyGroup(propertyList);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700554 mPropertyGroupsByProperty.put(property, propertyGroup);
555 }
Mike Lockwood71827742015-01-23 10:50:08 -0800556 }
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600557 int err = propertyGroup.getPropertyList(mMediaProvider, obj.getVolumeName(), obj, ret);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700558 if (err != MtpConstants.RESPONSE_OK) {
559 return new MtpPropertyList(err);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400560 }
561 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700562 return ret;
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400563 }
564
Mike Lockwood5ebac832010-10-12 11:33:47 -0400565 private int renameFile(int handle, String newName) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700566 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
567 if (obj == null) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400568 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
569 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700570 Path oldPath = obj.getPath();
Mike Lockwood73e56d92011-12-01 16:58:41 -0500571
Mike Lockwood5ebac832010-10-12 11:33:47 -0400572 // now rename the file. make sure this succeeds before updating database
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700573 if (!mManager.beginRenameObject(obj, newName))
Mike Lockwood5ebac832010-10-12 11:33:47 -0400574 return MtpConstants.RESPONSE_GENERAL_ERROR;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700575 Path newPath = obj.getPath();
576 boolean success = oldPath.toFile().renameTo(newPath.toFile());
Jerry Zhangd470a1e2018-05-14 12:19:08 -0700577 try {
578 Os.access(oldPath.toString(), OsConstants.F_OK);
579 Os.access(newPath.toString(), OsConstants.F_OK);
580 } catch (ErrnoException e) {
581 // Ignore. Could fail if the metadata was already updated.
582 }
583
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700584 if (!mManager.endRenameObject(obj, oldPath.getFileName().toString(), success)) {
585 Log.e(TAG, "Failed to end rename object");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400586 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400587 if (!success) {
588 return MtpConstants.RESPONSE_GENERAL_ERROR;
589 }
590
Ivan Chiang20079f12020-11-10 10:44:05 +0800591 updateMediaStore(mContext, oldPath.toFile());
592 updateMediaStore(mContext, newPath.toFile());
Mike Lockwood5ebac832010-10-12 11:33:47 -0400593 return MtpConstants.RESPONSE_OK;
594 }
595
Jeff Sharkeyff200952019-03-24 12:50:51 -0600596 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700597 private int beginMoveObject(int handle, int newParent, int newStorage) {
598 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
599 MtpStorageManager.MtpObject parent = newParent == 0 ?
600 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
601 if (obj == null || parent == null)
602 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Jerry Zhang952558d42017-09-26 17:49:52 -0700603
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700604 boolean allowed = mManager.beginMoveObject(obj, parent);
605 return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR;
606 }
607
Jeff Sharkeyff200952019-03-24 12:50:51 -0600608 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700609 private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage,
610 int objId, boolean success) {
611 MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ?
612 mManager.getStorageRoot(oldStorage) : mManager.getObject(oldParent);
613 MtpStorageManager.MtpObject newParentObj = newParent == 0 ?
614 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
615 MtpStorageManager.MtpObject obj = mManager.getObject(objId);
616 String name = obj.getName();
617 if (newParentObj == null || oldParentObj == null
618 ||!mManager.endMoveObject(oldParentObj, newParentObj, name, success)) {
619 Log.e(TAG, "Failed to end move object");
620 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700621 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700622 obj = mManager.getObject(objId);
623 if (!success || obj == null)
624 return;
Ivan Chiang20079f12020-11-10 10:44:05 +0800625
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700626 Path path = newParentObj.getPath().resolve(name);
627 Path oldPath = oldParentObj.getPath().resolve(name);
Ivan Chiang20079f12020-11-10 10:44:05 +0800628
629 updateMediaStore(mContext, oldPath.toFile());
630 updateMediaStore(mContext, path.toFile());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700631 }
632
Jeff Sharkeyff200952019-03-24 12:50:51 -0600633 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700634 private int beginCopyObject(int handle, int newParent, int newStorage) {
635 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
636 MtpStorageManager.MtpObject parent = newParent == 0 ?
637 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
638 if (obj == null || parent == null)
639 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
640 return mManager.beginCopyObject(obj, parent);
641 }
642
Jeff Sharkeyff200952019-03-24 12:50:51 -0600643 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700644 private void endCopyObject(int handle, boolean success) {
645 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
646 if (obj == null || !mManager.endCopyObject(obj, success)) {
647 Log.e(TAG, "Failed to end copy object");
648 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700649 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700650 if (!success) {
651 return;
652 }
Ivan Chiang20079f12020-11-10 10:44:05 +0800653
654 updateMediaStore(mContext, obj.getPath().toFile());
655 }
656
657 private static void updateMediaStore(@NonNull Context context, @NonNull File file) {
658 final ContentResolver resolver = context.getContentResolver();
659 // For file, check whether the file name is .nomedia or not.
660 // If yes, scan the parent directory to update all files in the directory.
661 if (!file.isDirectory() && file.getName().toLowerCase(Locale.ROOT).endsWith(NO_MEDIA)) {
662 MediaStore.scanFile(resolver, file.getParentFile());
663 } else {
664 MediaStore.scanFile(resolver, file);
665 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700666 }
667
Jeff Sharkeyff200952019-03-24 12:50:51 -0600668 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400669 private int setObjectProperty(int handle, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700670 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400671 switch (property) {
672 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
673 return renameFile(handle, stringValue);
674
675 default:
676 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
677 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400678 }
679
Jeff Sharkeyff200952019-03-24 12:50:51 -0600680 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400681 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
James Wei4f1a6d62021-06-19 20:58:00 +0800682 int length;
683 String value;
684
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400685 switch (property) {
686 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
687 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500688 // writable string properties kept in shared preferences
James Wei4f1a6d62021-06-19 20:58:00 +0800689 value = mDeviceProperties.getString(Integer.toString(property), "");
690 length = value.length();
691 if (length > 255) {
692 length = 255;
693 }
694 value.getChars(0, length, outStringValue, 0);
695 outStringValue[length] = 0;
696 return MtpConstants.RESPONSE_OK;
697 case MtpConstants.DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO:
698 value = mHostType;
699 length = value.length();
Mike Lockwood775de952011-03-05 17:34:11 -0500700 if (length > 255) {
701 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400702 }
Mike Lockwood775de952011-03-05 17:34:11 -0500703 value.getChars(0, length, outStringValue, 0);
704 outStringValue[length] = 0;
705 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800706 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
707 // use screen size as max image size
Andrii Kuliane57f2dc2020-01-26 20:59:07 -0800708 // TODO(b/147721765): Add support for foldables/multi-display devices.
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700709 Display display = ((WindowManager) mContext.getSystemService(
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800710 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700711 int width = display.getMaximumSizeDimension();
712 int height = display.getMaximumSizeDimension();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700713 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800714 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
715 outStringValue[imageSize.length()] = 0;
716 return MtpConstants.RESPONSE_OK;
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800717 case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
718 outIntValue[0] = mDeviceType;
719 return MtpConstants.RESPONSE_OK;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700720 case MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL:
721 outIntValue[0] = mBatteryLevel;
722 outIntValue[1] = mBatteryScale;
723 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800724 default:
725 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
726 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400727 }
728
Jeff Sharkeyff200952019-03-24 12:50:51 -0600729 @VisibleForNative
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400730 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400731 switch (property) {
732 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
733 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500734 // writable string properties kept in shared prefs
735 SharedPreferences.Editor e = mDeviceProperties.edit();
736 e.putString(Integer.toString(property), stringValue);
737 return (e.commit() ? MtpConstants.RESPONSE_OK
738 : MtpConstants.RESPONSE_GENERAL_ERROR);
James Wei4f1a6d62021-06-19 20:58:00 +0800739 case MtpConstants.DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO:
740 mHostType = stringValue;
Kevin Rocardf0fbd642021-11-12 16:13:55 +0000741 Log.d(TAG, "setDeviceProperty." + Integer.toHexString(property)
742 + "=" + stringValue);
James Wei4f1a6d62021-06-19 20:58:00 +0800743 if (stringValue.startsWith("Android/")) {
James Wei4f1a6d62021-06-19 20:58:00 +0800744 mSkipThumbForHost = true;
Kevin Rocardf0fbd642021-11-12 16:13:55 +0000745 } else if (stringValue.startsWith("Windows/")) {
746 mHostIsWindows = true;
James Wei4f1a6d62021-06-19 20:58:00 +0800747 }
748 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400749 }
750
751 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
752 }
753
Jeff Sharkeyff200952019-03-24 12:50:51 -0600754 @VisibleForNative
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400755 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700756 char[] outName, long[] outCreatedModified) {
757 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
758 if (obj == null) {
759 return false;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400760 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700761 outStorageFormatParent[0] = obj.getStorageId();
762 outStorageFormatParent[1] = obj.getFormat();
763 outStorageFormatParent[2] = obj.getParent().isRoot() ? 0 : obj.getParent().getId();
764
765 int nameLen = Integer.min(obj.getName().length(), 255);
766 obj.getName().getChars(0, nameLen, outName, 0);
767 outName[nameLen] = 0;
768
769 outCreatedModified[0] = obj.getModifiedTime();
770 outCreatedModified[1] = obj.getModifiedTime();
771 return true;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400772 }
773
Jeff Sharkeyff200952019-03-24 12:50:51 -0600774 @VisibleForNative
Mike Lockwood365e03e2010-12-08 16:08:01 -0800775 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700776 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
777 if (obj == null) {
778 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood01788562010-10-11 11:22:19 -0400779 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700780
781 String path = obj.getPath().toString();
782 int pathLen = Integer.min(path.length(), 4096);
783 path.getChars(0, pathLen, outFilePath, 0);
784 outFilePath[pathLen] = 0;
785
786 outFileLengthFormat[0] = obj.getSize();
787 outFileLengthFormat[1] = obj.getFormat();
788 return MtpConstants.RESPONSE_OK;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400789 }
790
Zim223e3b52020-11-13 20:12:04 +0000791 @VisibleForNative
792 private int openFilePath(String path, boolean transcode) {
793 Uri uri = MediaStore.scanFile(mContext.getContentResolver(), new File(path));
794 if (uri == null) {
795 Log.i(TAG, "Failed to obtain URI for openFile with transcode support: " + path);
796 return -1;
797 }
798
799 try {
800 Log.i(TAG, "openFile with transcode support: " + path);
Manish Singh530d7d92021-01-19 21:30:31 +0000801 Bundle bundle = new Bundle();
802 if (transcode) {
803 bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES,
804 new ApplicationMediaCapabilities.Builder().addUnsupportedVideoMimeType(
805 MediaFormat.MIMETYPE_VIDEO_HEVC).build());
806 } else {
Zim223e3b52020-11-13 20:12:04 +0000807 bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES,
808 new ApplicationMediaCapabilities.Builder().addSupportedVideoMimeType(
809 MediaFormat.MIMETYPE_VIDEO_HEVC).build());
810 }
811 return mMediaProvider.openTypedAssetFileDescriptor(uri, "*/*", bundle)
812 .getParcelFileDescriptor().detachFd();
813 } catch (RemoteException | FileNotFoundException e) {
814 Log.w(TAG, "Failed to openFile with transcode support: " + path, e);
815 return -1;
816 }
817 }
818
Mike Lockwood71827742015-01-23 10:50:08 -0800819 private int getObjectFormat(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700820 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
821 if (obj == null) {
Mike Lockwood71827742015-01-23 10:50:08 -0800822 return -1;
Mike Lockwood71827742015-01-23 10:50:08 -0800823 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700824 return obj.getFormat();
Mike Lockwood71827742015-01-23 10:50:08 -0800825 }
826
James Weibb3f5482019-12-24 19:12:29 +0800827 private byte[] getThumbnailProcess(String path, Bitmap bitmap) {
828 try {
829 if (bitmap == null) {
830 Log.d(TAG, "getThumbnailProcess: Fail to generate thumbnail. Probably unsupported or corrupted image");
831 return null;
832 }
833
834 ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
835 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteStream);
836
James Wei1f1f81e2021-03-22 13:17:01 +0800837 if (byteStream.size() > MAX_THUMB_SIZE) {
838 Log.w(TAG, "getThumbnailProcess: size=" + byteStream.size());
James Weibb3f5482019-12-24 19:12:29 +0800839 return null;
James Wei1f1f81e2021-03-22 13:17:01 +0800840 }
James Weibb3f5482019-12-24 19:12:29 +0800841
842 byte[] byteArray = byteStream.toByteArray();
843
844 return byteArray;
845 } catch (OutOfMemoryError oomEx) {
846 Log.w(TAG, "OutOfMemoryError:" + oomEx);
847 }
848 return null;
849 }
850
Jeff Sharkeyff200952019-03-24 12:50:51 -0600851 @VisibleForNative
James Wei19ded222019-12-24 19:48:51 +0800852 @VisibleForTesting
853 public boolean getThumbnailInfo(int handle, long[] outLongs) {
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700854 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
855 if (obj == null) {
856 return false;
857 }
858
859 String path = obj.getPath().toString();
860 switch (obj.getFormat()) {
861 case MtpConstants.FORMAT_HEIF:
862 case MtpConstants.FORMAT_EXIF_JPEG:
863 case MtpConstants.FORMAT_JFIF:
864 try {
865 ExifInterface exif = new ExifInterface(path);
866 long[] thumbOffsetAndSize = exif.getThumbnailRange();
867 outLongs[0] = thumbOffsetAndSize != null ? thumbOffsetAndSize[1] : 0;
868 outLongs[1] = exif.getAttributeInt(ExifInterface.TAG_PIXEL_X_DIMENSION, 0);
869 outLongs[2] = exif.getAttributeInt(ExifInterface.TAG_PIXEL_Y_DIMENSION, 0);
James Wei4f1a6d62021-06-19 20:58:00 +0800870 if (mSkipThumbForHost) {
871 Log.d(TAG, "getThumbnailInfo: Skip runtime thumbnail.");
872 return true;
873 }
James Wei1f1f81e2021-03-22 13:17:01 +0800874 if (exif.getThumbnailRange() != null) {
875 if ((outLongs[0] == 0) || (outLongs[1] == 0) || (outLongs[2] == 0)) {
876 Log.d(TAG, "getThumbnailInfo: check thumb info:"
877 + thumbOffsetAndSize[0] + "," + thumbOffsetAndSize[1]
878 + "," + outLongs[1] + "," + outLongs[2]);
879 }
880
881 return true;
882 }
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700883 } catch (IOException e) {
884 // ignore and fall through
885 }
James Weibb3f5482019-12-24 19:12:29 +0800886
887// Note: above formats will fall through and go on below thumbnail generation if Exif processing fails
888 case MtpConstants.FORMAT_PNG:
889 case MtpConstants.FORMAT_GIF:
890 case MtpConstants.FORMAT_BMP:
891 outLongs[0] = MAX_THUMB_SIZE;
892 // only non-zero Width & Height needed. Actual size will be retrieved upon getThumbnailData by Host
893 outLongs[1] = 320;
894 outLongs[2] = 240;
895 return true;
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700896 }
897 return false;
898 }
899
900 @VisibleForNative
James Wei19ded222019-12-24 19:48:51 +0800901 @VisibleForTesting
902 public byte[] getThumbnailData(int handle) {
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700903 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
904 if (obj == null) {
905 return null;
906 }
907
908 String path = obj.getPath().toString();
909 switch (obj.getFormat()) {
910 case MtpConstants.FORMAT_HEIF:
911 case MtpConstants.FORMAT_EXIF_JPEG:
912 case MtpConstants.FORMAT_JFIF:
913 try {
914 ExifInterface exif = new ExifInterface(path);
James Wei1f1f81e2021-03-22 13:17:01 +0800915
James Wei4f1a6d62021-06-19 20:58:00 +0800916 if (mSkipThumbForHost) {
917 Log.d(TAG, "getThumbnailData: Skip runtime thumbnail.");
918 return exif.getThumbnail();
919 }
James Wei1f1f81e2021-03-22 13:17:01 +0800920 if (exif.getThumbnailRange() != null)
921 return exif.getThumbnail();
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700922 } catch (IOException e) {
923 // ignore and fall through
924 }
James Weibb3f5482019-12-24 19:12:29 +0800925
926// Note: above formats will fall through and go on below thumbnail generation if Exif processing fails
927 case MtpConstants.FORMAT_PNG:
928 case MtpConstants.FORMAT_GIF:
929 case MtpConstants.FORMAT_BMP:
930 {
931 Bitmap bitmap = ThumbnailUtils.createImageThumbnail(path, MediaStore.Images.Thumbnails.MINI_KIND);
932 byte[] byteArray = getThumbnailProcess(path, bitmap);
933
934 return byteArray;
935 }
Marco Nelissenf7ec1682019-06-27 15:56:32 -0700936 }
937 return null;
938 }
939
940 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700941 private int beginDeleteObject(int handle) {
942 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
943 if (obj == null) {
944 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
945 }
946 if (!mManager.beginRemoveObject(obj)) {
947 return MtpConstants.RESPONSE_GENERAL_ERROR;
948 }
949 return MtpConstants.RESPONSE_OK;
950 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800951
Jeff Sharkeyff200952019-03-24 12:50:51 -0600952 @VisibleForNative
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700953 private void endDeleteObject(int handle, boolean success) {
954 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
955 if (obj == null) {
956 return;
957 }
958 if (!mManager.endRemoveObject(obj, success))
959 Log.e(TAG, "Failed to end remove object");
960 if (success)
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600961 deleteFromMedia(obj, obj.getPath(), obj.isDir());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700962 }
963
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600964 private void deleteFromMedia(MtpStorageManager.MtpObject obj, Path path, boolean isDir) {
Jeff Sharkey8f5cdbb2019-12-16 14:10:53 -0700965 final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700966 try {
967 // Delete the object(s) from MediaProvider, but ignore errors.
968 if (isDir) {
Mike Lockwood55f808c2010-12-14 13:14:29 -0800969 // recursive case - delete all children first
Jeff Sharkey42bf6d82019-04-17 11:16:12 -0600970 mMediaProvider.delete(objectsUri,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700971 // the 'like' makes it use the index, the 'lower()' makes it correct
972 // when the path contains sqlite wildcard characters
973 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
974 new String[]{path + "/%", Integer.toString(path.toString().length() + 1),
975 path.toString() + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800976 }
977
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700978 String[] whereArgs = new String[]{path.toString()};
Ivan Chiang20079f12020-11-10 10:44:05 +0800979 if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) == 0) {
980 Log.i(TAG, "MediaProvider didn't delete " + path);
Mike Lockwood59c777a2010-08-02 10:37:41 -0400981 }
Ivan Chiang20079f12020-11-10 10:44:05 +0800982 updateMediaStore(mContext, path.toFile());
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700983 } catch (Exception e) {
984 Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400985 }
986 }
987
Jeff Sharkeyff200952019-03-24 12:50:51 -0600988 @VisibleForNative
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400989 private int[] getObjectReferences(int handle) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400990 return null;
991 }
992
Jeff Sharkeyff200952019-03-24 12:50:51 -0600993 @VisibleForNative
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400994 private int setObjectReferences(int handle, int[] references) {
Jeff Sharkey8f5cdbb2019-12-16 14:10:53 -0700995 return MtpConstants.RESPONSE_OPERATION_NOT_SUPPORTED;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400996 }
997
Jeff Sharkeyff200952019-03-24 12:50:51 -0600998 @VisibleForNative
Ashok Bhate2e59322013-12-17 19:04:19 +0000999 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001000
1001 private native final void native_setup();
1002 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001003}