blob: 9c581352154513053f802930b70a6239058740bb [file] [log] [blame]
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Mike Lockwood0cd01362010-12-30 11:54:33 -050017package android.mtp;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040018
Mike Lockwood56c85242014-03-07 13:29:08 -080019import android.content.BroadcastReceiver;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070020import android.content.ContentProviderClient;
Mike Lockwoodd815f792010-07-12 08:49:01 -040021import android.content.ContentValues;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070022import android.content.Context;
Mike Lockwood2837eef2010-08-31 16:25:12 -040023import android.content.Intent;
Mike Lockwood56c85242014-03-07 13:29:08 -080024import android.content.IntentFilter;
Mike Lockwood775de952011-03-05 17:34:11 -050025import android.content.SharedPreferences;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040026import android.database.Cursor;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040027import android.database.sqlite.SQLiteDatabase;
Mike Lockwood0cd01362010-12-30 11:54:33 -050028import android.media.MediaScanner;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040029import android.net.Uri;
Mike Lockwood56c85242014-03-07 13:29:08 -080030import android.os.BatteryManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040031import android.os.RemoteException;
Jerry Zhang13bb2f42016-12-14 15:39:29 -080032import android.os.SystemProperties;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070033import android.os.storage.StorageVolume;
Mike Lockwooda3156052010-11-20 12:28:27 -050034import android.provider.MediaStore;
Mike Lockwood9a2046f2010-08-03 15:30:09 -040035import android.provider.MediaStore.Audio;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040036import android.provider.MediaStore.Files;
Mike Lockwoodae078f72010-09-26 12:35:51 -040037import android.provider.MediaStore.MediaColumns;
Jerry Zhangd470a1e2018-05-14 12:19:08 -070038import android.system.ErrnoException;
39import android.system.Os;
40import android.system.OsConstants;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040041import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080042import android.view.Display;
43import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040044
Jeff Sharkey60cfad82016-01-05 17:30:57 -070045import dalvik.system.CloseGuard;
46
Jerry Zhangf9c5c252017-08-16 18:07:51 -070047import com.google.android.collect.Sets;
48
Mike Lockwood5ebac832010-10-12 11:33:47 -040049import java.io.File;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070050import java.nio.file.Path;
51import java.nio.file.Paths;
52import java.util.ArrayList;
53import java.util.Arrays;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050054import java.util.HashMap;
Jerry Zhang9a018742018-05-10 18:27:13 -070055import java.util.List;
dujin.chafe464a72011-11-22 12:13:33 +090056import java.util.Locale;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070057import java.util.concurrent.atomic.AtomicBoolean;
Jerry Zhangf9c5c252017-08-16 18:07:51 -070058import java.util.stream.IntStream;
Mike Lockwood5ebac832010-10-12 11:33:47 -040059
Mike Lockwoodd21eac92010-07-03 00:44:05 -040060/**
Jerry Zhangf9c5c252017-08-16 18:07:51 -070061 * MtpDatabase provides an interface for MTP operations that MtpServer can use. To do this, it uses
62 * MtpStorageManager for filesystem operations and MediaProvider to get media metadata. File
63 * operations are also reflected in MediaProvider if possible.
64 * operations
Mike Lockwoodd21eac92010-07-03 00:44:05 -040065 * {@hide}
66 */
Jeff Sharkey60cfad82016-01-05 17:30:57 -070067public class MtpDatabase implements AutoCloseable {
Jerry Zhangf9c5c252017-08-16 18:07:51 -070068 private static final String TAG = MtpDatabase.class.getSimpleName();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040069
Mike Lockwood2837eef2010-08-31 16:25:12 -040070 private final Context mContext;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070071 private final ContentProviderClient mMediaProvider;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040072 private final String mVolumeName;
73 private final Uri mObjectsUri;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070074 private final MediaScanner mMediaScanner;
75
76 private final AtomicBoolean mClosed = new AtomicBoolean();
77 private final CloseGuard mCloseGuard = CloseGuard.get();
78
Jerry Zhangf9c5c252017-08-16 18:07:51 -070079 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040080
Mike Lockwood7d7fb632010-12-01 18:46:23 -050081 // cached property groups for single properties
Jerry Zhangf9c5c252017-08-16 18:07:51 -070082 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty = new HashMap<>();
Mike Lockwood7d7fb632010-12-01 18:46:23 -050083
84 // cached property groups for all properties for a given format
Jerry Zhangf9c5c252017-08-16 18:07:51 -070085 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat = new HashMap<>();
Mike Lockwood2837eef2010-08-31 16:25:12 -040086
Mike Lockwood775de952011-03-05 17:34:11 -050087 // SharedPreferences for writable MTP device properties
88 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040089
Jerry Zhangf9c5c252017-08-16 18:07:51 -070090 // Cached device properties
Mike Lockwood56c85242014-03-07 13:29:08 -080091 private int mBatteryLevel;
92 private int mBatteryScale;
Jerry Zhang13bb2f42016-12-14 15:39:29 -080093 private int mDeviceType;
94
Jerry Zhangf9c5c252017-08-16 18:07:51 -070095 private MtpServer mServer;
96 private MtpStorageManager mManager;
97
98 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
99 private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
100 private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
101 private static final String NO_MEDIA = ".nomedia";
102
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400103 static {
104 System.loadLibrary("media_jni");
105 }
106
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700107 private static final int[] PLAYBACK_FORMATS = {
108 // allow transferring arbitrary files
Mike Lockwoode5211692010-09-08 13:50:45 -0400109 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400110
Mike Lockwood792ec842010-09-09 15:30:10 -0400111 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400112 MtpConstants.FORMAT_TEXT,
113 MtpConstants.FORMAT_HTML,
114 MtpConstants.FORMAT_WAV,
115 MtpConstants.FORMAT_MP3,
116 MtpConstants.FORMAT_MPEG,
117 MtpConstants.FORMAT_EXIF_JPEG,
118 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800119 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400120 MtpConstants.FORMAT_GIF,
121 MtpConstants.FORMAT_JFIF,
122 MtpConstants.FORMAT_PNG,
123 MtpConstants.FORMAT_TIFF,
124 MtpConstants.FORMAT_WMA,
125 MtpConstants.FORMAT_OGG,
126 MtpConstants.FORMAT_AAC,
127 MtpConstants.FORMAT_MP4_CONTAINER,
128 MtpConstants.FORMAT_MP2,
129 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400130 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400131 MtpConstants.FORMAT_WPL_PLAYLIST,
132 MtpConstants.FORMAT_M3U_PLAYLIST,
133 MtpConstants.FORMAT_PLS_PLAYLIST,
134 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800135 MtpConstants.FORMAT_FLAC,
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100136 MtpConstants.FORMAT_DNG,
Chong Zhang6e18cce2017-08-16 11:57:02 -0700137 MtpConstants.FORMAT_HEIF,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700138 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400139
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700140 private static final int[] FILE_PROPERTIES = {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400141 MtpConstants.PROPERTY_STORAGE_ID,
142 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400143 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400144 MtpConstants.PROPERTY_OBJECT_SIZE,
145 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400146 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400147 MtpConstants.PROPERTY_PERSISTENT_UID,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700148 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400149 MtpConstants.PROPERTY_NAME,
Mike Lockwood71827742015-01-23 10:50:08 -0800150 MtpConstants.PROPERTY_DISPLAY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400151 MtpConstants.PROPERTY_DATE_ADDED,
152 };
153
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700154 private static final int[] AUDIO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400155 MtpConstants.PROPERTY_ARTIST,
156 MtpConstants.PROPERTY_ALBUM_NAME,
157 MtpConstants.PROPERTY_ALBUM_ARTIST,
158 MtpConstants.PROPERTY_TRACK,
159 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
160 MtpConstants.PROPERTY_DURATION,
161 MtpConstants.PROPERTY_GENRE,
162 MtpConstants.PROPERTY_COMPOSER,
Mike Lockwood92b53bc2014-03-13 14:51:29 -0700163 MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
164 MtpConstants.PROPERTY_BITRATE_TYPE,
165 MtpConstants.PROPERTY_AUDIO_BITRATE,
166 MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
167 MtpConstants.PROPERTY_SAMPLE_RATE,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400168 };
169
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700170 private static final int[] VIDEO_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400171 MtpConstants.PROPERTY_ARTIST,
172 MtpConstants.PROPERTY_ALBUM_NAME,
173 MtpConstants.PROPERTY_DURATION,
174 MtpConstants.PROPERTY_DESCRIPTION,
175 };
176
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700177 private static final int[] IMAGE_PROPERTIES = {
Mike Lockwoodae078f72010-09-26 12:35:51 -0400178 MtpConstants.PROPERTY_DESCRIPTION,
179 };
180
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700181 private static final int[] DEVICE_PROPERTIES = {
182 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
183 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
184 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
185 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
186 MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
187 };
188
Mike Lockwoodae078f72010-09-26 12:35:51 -0400189 private int[] getSupportedObjectProperties(int format) {
190 switch (format) {
191 case MtpConstants.FORMAT_MP3:
192 case MtpConstants.FORMAT_WAV:
193 case MtpConstants.FORMAT_WMA:
194 case MtpConstants.FORMAT_OGG:
195 case MtpConstants.FORMAT_AAC:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700196 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
197 Arrays.stream(AUDIO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400198 case MtpConstants.FORMAT_MPEG:
199 case MtpConstants.FORMAT_3GP_CONTAINER:
200 case MtpConstants.FORMAT_WMV:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700201 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
202 Arrays.stream(VIDEO_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400203 case MtpConstants.FORMAT_EXIF_JPEG:
204 case MtpConstants.FORMAT_GIF:
205 case MtpConstants.FORMAT_PNG:
206 case MtpConstants.FORMAT_BMP:
Jaesung Chung5a8b9622015-12-18 05:50:21 +0100207 case MtpConstants.FORMAT_DNG:
Chong Zhang6e18cce2017-08-16 11:57:02 -0700208 case MtpConstants.FORMAT_HEIF:
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700209 return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
210 Arrays.stream(IMAGE_PROPERTIES)).toArray();
Mike Lockwoodae078f72010-09-26 12:35:51 -0400211 default:
212 return FILE_PROPERTIES;
213 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400214 }
215
216 private int[] getSupportedDeviceProperties() {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700217 return DEVICE_PROPERTIES;
218 }
219
220 private int[] getSupportedPlaybackFormats() {
221 return PLAYBACK_FORMATS;
222 }
223
224 private int[] getSupportedCaptureFormats() {
225 // no capture formats yet
226 return null;
227 }
228
229 private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
230 @Override
231 public void onReceive(Context context, Intent intent) {
232 String action = intent.getAction();
233 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
234 mBatteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
235 int newLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
236 if (newLevel != mBatteryLevel) {
237 mBatteryLevel = newLevel;
238 if (mServer != null) {
239 // send device property changed event
240 mServer.sendDevicePropertyChanged(
241 MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL);
242 }
243 }
244 }
245 }
246 };
247
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800248 public MtpDatabase(Context context, String volumeName,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700249 String[] subDirectories) {
250 native_setup();
251 mContext = context;
Jerry Zhang63a69fd2018-02-02 17:20:41 -0800252 mMediaProvider = context.getContentResolver()
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700253 .acquireContentProviderClient(MediaStore.AUTHORITY);
254 mVolumeName = volumeName;
255 mObjectsUri = Files.getMtpObjectsUri(volumeName);
256 mMediaScanner = new MediaScanner(context, mVolumeName);
257 mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
258 @Override
259 public void sendObjectAdded(int id) {
260 if (MtpDatabase.this.mServer != null)
261 MtpDatabase.this.mServer.sendObjectAdded(id);
262 }
263
264 @Override
265 public void sendObjectRemoved(int id) {
266 if (MtpDatabase.this.mServer != null)
267 MtpDatabase.this.mServer.sendObjectRemoved(id);
268 }
269 }, subDirectories == null ? null : Sets.newHashSet(subDirectories));
270
271 initDeviceProperties(context);
272 mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
273 mCloseGuard.open("close");
274 }
275
276 public void setServer(MtpServer server) {
277 mServer = server;
278 // always unregister before registering
279 try {
280 mContext.unregisterReceiver(mBatteryReceiver);
281 } catch (IllegalArgumentException e) {
282 // wasn't previously registered, ignore
283 }
284 // register for battery notifications when we are connected
285 if (server != null) {
286 mContext.registerReceiver(mBatteryReceiver,
287 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
288 }
289 }
290
291 @Override
292 public void close() {
293 mManager.close();
294 mCloseGuard.close();
295 if (mClosed.compareAndSet(false, true)) {
296 mMediaScanner.close();
Jerry Zhang484ea672018-03-02 15:40:03 -0800297 if (mMediaProvider != null) {
298 mMediaProvider.close();
299 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700300 native_finalize();
301 }
302 }
303
304 @Override
305 protected void finalize() throws Throwable {
306 try {
307 if (mCloseGuard != null) {
308 mCloseGuard.warnIfOpen();
309 }
310 close();
311 } finally {
312 super.finalize();
313 }
314 }
315
316 public void addStorage(StorageVolume storage) {
317 MtpStorage mtpStorage = mManager.addMtpStorage(storage);
318 mStorageMap.put(storage.getPath(), mtpStorage);
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700319 if (mServer != null) {
320 mServer.addStorage(mtpStorage);
321 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700322 }
323
324 public void removeStorage(StorageVolume storage) {
325 MtpStorage mtpStorage = mStorageMap.get(storage.getPath());
326 if (mtpStorage == null) {
327 return;
328 }
Jerry Zhang2ecbc7a2018-03-26 14:59:39 -0700329 if (mServer != null) {
330 mServer.removeStorage(mtpStorage);
331 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700332 mManager.removeMtpStorage(mtpStorage);
333 mStorageMap.remove(storage.getPath());
334 }
335
336 private void initDeviceProperties(Context context) {
337 final String devicePropertiesName = "device-properties";
338 mDeviceProperties = context.getSharedPreferences(devicePropertiesName,
339 Context.MODE_PRIVATE);
340 File databaseFile = context.getDatabasePath(devicePropertiesName);
341
342 if (databaseFile.exists()) {
343 // for backward compatibility - read device properties from sqlite database
344 // and migrate them to shared prefs
345 SQLiteDatabase db = null;
346 Cursor c = null;
347 try {
348 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
349 if (db != null) {
350 c = db.query("properties", new String[]{"_id", "code", "value"},
351 null, null, null, null, null);
352 if (c != null) {
353 SharedPreferences.Editor e = mDeviceProperties.edit();
354 while (c.moveToNext()) {
355 String name = c.getString(1);
356 String value = c.getString(2);
357 e.putString(name, value);
358 }
359 e.commit();
360 }
361 }
362 } catch (Exception e) {
363 Log.e(TAG, "failed to migrate device properties", e);
364 } finally {
365 if (c != null) c.close();
366 if (db != null) db.close();
367 }
368 context.deleteDatabase(devicePropertiesName);
369 }
370 }
371
372 private int beginSendObject(String path, int format, int parent, int storageId) {
373 MtpStorageManager.MtpObject parentObj =
374 parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
375 if (parentObj == null) {
376 return -1;
377 }
378
379 Path objPath = Paths.get(path);
380 return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format);
381 }
382
383 private void endSendObject(int handle, boolean succeeded) {
384 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
385 if (obj == null || !mManager.endSendObject(obj, succeeded)) {
386 Log.e(TAG, "Failed to successfully end send object");
387 return;
388 }
389 // Add the new file to MediaProvider
390 if (succeeded) {
391 String path = obj.getPath().toString();
392 int format = obj.getFormat();
393 // Get parent info from MediaProvider, since the id is different from MTP's
394 ContentValues values = new ContentValues();
395 values.put(Files.FileColumns.DATA, path);
396 values.put(Files.FileColumns.FORMAT, format);
397 values.put(Files.FileColumns.SIZE, obj.getSize());
398 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
399 try {
400 if (obj.getParent().isRoot()) {
401 values.put(Files.FileColumns.PARENT, 0);
402 } else {
403 int parentId = findInMedia(obj.getParent().getPath());
404 if (parentId != -1) {
405 values.put(Files.FileColumns.PARENT, parentId);
406 } else {
407 // The parent isn't in MediaProvider. Don't add the new file.
408 return;
409 }
410 }
411
412 Uri uri = mMediaProvider.insert(mObjectsUri, values);
413 if (uri != null) {
414 rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
415 }
416 } catch (RemoteException e) {
417 Log.e(TAG, "RemoteException in beginSendObject", e);
418 }
419 }
420 }
421
422 private void rescanFile(String path, int handle, int format) {
423 // handle abstract playlists separately
424 // they do not exist in the file system so don't use the media scanner here
425 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
426 // extract name from path
427 String name = path;
428 int lastSlash = name.lastIndexOf('/');
429 if (lastSlash >= 0) {
430 name = name.substring(lastSlash + 1);
431 }
432 // strip trailing ".pla" from the name
433 if (name.endsWith(".pla")) {
434 name = name.substring(0, name.length() - 4);
435 }
436
437 ContentValues values = new ContentValues(1);
438 values.put(Audio.Playlists.DATA, path);
439 values.put(Audio.Playlists.NAME, name);
440 values.put(Files.FileColumns.FORMAT, format);
441 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
442 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
443 try {
444 mMediaProvider.insert(
445 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
446 } catch (RemoteException e) {
447 Log.e(TAG, "RemoteException in endSendObject", e);
448 }
449 } else {
450 mMediaScanner.scanMtpFile(path, handle, format);
451 }
452 }
453
454 private int[] getObjectList(int storageID, int format, int parent) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700455 List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700456 format, storageID);
Jerry Zhang9a018742018-05-10 18:27:13 -0700457 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700458 return null;
459 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700460 int[] ret = new int[objs.size()];
461 for (int i = 0; i < objs.size(); i++) {
462 ret[i] = objs.get(i).getId();
463 }
464 return ret;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700465 }
466
467 private 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
Daichi Hirono486ad2e2016-02-29 17:28:47 +0900476 private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700477 int groupCode, int depth) {
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400478 // FIXME - implement group support
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700479 if (property == 0) {
480 if (groupCode == 0) {
481 return new MtpPropertyList(MtpConstants.RESPONSE_PARAMETER_NOT_SUPPORTED);
482 }
483 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
484 }
485 if (depth == 0xFFFFFFFF && (handle == 0 || handle == 0xFFFFFFFF)) {
486 // request all objects starting at root
487 handle = 0xFFFFFFFF;
488 depth = 0;
489 }
490 if (!(depth == 0 || depth == 1)) {
491 // we only support depth 0 and 1
492 // depth 0: single object, depth 1: immediate children
493 return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
494 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700495 List<MtpStorageManager.MtpObject> objs = null;
496 MtpStorageManager.MtpObject thisObj = null;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700497 if (handle == 0xFFFFFFFF) {
498 // All objects are requested
Jerry Zhang9a018742018-05-10 18:27:13 -0700499 objs = mManager.getObjects(0, format, 0xFFFFFFFF);
500 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700501 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
502 }
503 } else if (handle != 0) {
504 // Add the requested object if format matches
505 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
506 if (obj == null) {
507 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
508 }
509 if (obj.getFormat() == format || format == 0) {
Jerry Zhang9a018742018-05-10 18:27:13 -0700510 thisObj = obj;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700511 }
512 }
513 if (handle == 0 || depth == 1) {
514 if (handle == 0) {
515 handle = 0xFFFFFFFF;
516 }
517 // Get the direct children of root or this object.
Jerry Zhang9a018742018-05-10 18:27:13 -0700518 objs = mManager.getObjects(handle, format,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700519 0xFFFFFFFF);
Jerry Zhang9a018742018-05-10 18:27:13 -0700520 if (objs == null) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700521 return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
522 }
Jerry Zhang9a018742018-05-10 18:27:13 -0700523 }
524 if (objs == null) {
525 objs = new ArrayList<>();
526 }
527 if (thisObj != null) {
528 objs.add(thisObj);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400529 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400530
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700531 MtpPropertyList ret = new MtpPropertyList(MtpConstants.RESPONSE_OK);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500532 MtpPropertyGroup propertyGroup;
Jerry Zhang9a018742018-05-10 18:27:13 -0700533 for (MtpStorageManager.MtpObject obj : objs) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700534 if (property == 0xffffffff) {
535 // Get all properties supported by this object
536 propertyGroup = mPropertyGroupsByFormat.get(obj.getFormat());
537 if (propertyGroup == null) {
538 int[] propertyList = getSupportedObjectProperties(format);
539 propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
540 propertyList);
541 mPropertyGroupsByFormat.put(format, propertyGroup);
542 }
543 } else {
544 // Get this property value
545 final int[] propertyList = new int[]{property};
546 propertyGroup = mPropertyGroupsByProperty.get(property);
547 if (propertyGroup == null) {
548 propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
549 propertyList);
550 mPropertyGroupsByProperty.put(property, propertyGroup);
551 }
Mike Lockwood71827742015-01-23 10:50:08 -0800552 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700553 int err = propertyGroup.getPropertyList(obj, ret);
554 if (err != MtpConstants.RESPONSE_OK) {
555 return new MtpPropertyList(err);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400556 }
557 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700558 return ret;
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400559 }
560
Mike Lockwood5ebac832010-10-12 11:33:47 -0400561 private int renameFile(int handle, String newName) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700562 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
563 if (obj == null) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400564 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
565 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700566 Path oldPath = obj.getPath();
Mike Lockwood73e56d92011-12-01 16:58:41 -0500567
Mike Lockwood5ebac832010-10-12 11:33:47 -0400568 // now rename the file. make sure this succeeds before updating database
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700569 if (!mManager.beginRenameObject(obj, newName))
Mike Lockwood5ebac832010-10-12 11:33:47 -0400570 return MtpConstants.RESPONSE_GENERAL_ERROR;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700571 Path newPath = obj.getPath();
572 boolean success = oldPath.toFile().renameTo(newPath.toFile());
Jerry Zhangd470a1e2018-05-14 12:19:08 -0700573 try {
574 Os.access(oldPath.toString(), OsConstants.F_OK);
575 Os.access(newPath.toString(), OsConstants.F_OK);
576 } catch (ErrnoException e) {
577 // Ignore. Could fail if the metadata was already updated.
578 }
579
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700580 if (!mManager.endRenameObject(obj, oldPath.getFileName().toString(), success)) {
581 Log.e(TAG, "Failed to end rename object");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400582 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400583 if (!success) {
584 return MtpConstants.RESPONSE_GENERAL_ERROR;
585 }
586
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700587 // finally update MediaProvider
Mike Lockwood5ebac832010-10-12 11:33:47 -0400588 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700589 values.put(Files.FileColumns.DATA, newPath.toString());
590 String[] whereArgs = new String[]{oldPath.toString()};
Mike Lockwood5ebac832010-10-12 11:33:47 -0400591 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400592 // note - we are relying on a special case in MediaProvider.update() to update
593 // the paths for all children in the case where this is a directory.
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700594 mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400595 } catch (RemoteException e) {
596 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
597 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400598
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800599 // check if nomedia status changed
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700600 if (obj.isDir()) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800601 // for directories, check if renamed from something hidden to something non-hidden
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700602 if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800603 // directory was unhidden
604 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700605 mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath.toString(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800606 } catch (RemoteException e) {
607 Log.e(TAG, "failed to unhide/rescan for " + newPath);
608 }
609 }
610 } else {
611 // for files, check if renamed from .nomedia to something else
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700612 if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
613 && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800614 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700615 mMediaProvider.call(MediaStore.UNHIDE_CALL,
616 oldPath.getParent().toString(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800617 } catch (RemoteException e) {
618 Log.e(TAG, "failed to unhide/rescan for " + newPath);
619 }
620 }
621 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400622 return MtpConstants.RESPONSE_OK;
623 }
624
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700625 private int beginMoveObject(int handle, int newParent, int newStorage) {
626 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
627 MtpStorageManager.MtpObject parent = newParent == 0 ?
628 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
629 if (obj == null || parent == null)
630 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Jerry Zhang952558d42017-09-26 17:49:52 -0700631
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700632 boolean allowed = mManager.beginMoveObject(obj, parent);
633 return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR;
634 }
635
636 private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage,
637 int objId, boolean success) {
638 MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ?
639 mManager.getStorageRoot(oldStorage) : mManager.getObject(oldParent);
640 MtpStorageManager.MtpObject newParentObj = newParent == 0 ?
641 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
642 MtpStorageManager.MtpObject obj = mManager.getObject(objId);
643 String name = obj.getName();
644 if (newParentObj == null || oldParentObj == null
645 ||!mManager.endMoveObject(oldParentObj, newParentObj, name, success)) {
646 Log.e(TAG, "Failed to end move object");
647 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700648 }
649
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700650 obj = mManager.getObject(objId);
651 if (!success || obj == null)
652 return;
653 // Get parent info from MediaProvider, since the id is different from MTP's
Jerry Zhang952558d42017-09-26 17:49:52 -0700654 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700655 Path path = newParentObj.getPath().resolve(name);
656 Path oldPath = oldParentObj.getPath().resolve(name);
657 values.put(Files.FileColumns.DATA, path.toString());
658 if (obj.getParent().isRoot()) {
659 values.put(Files.FileColumns.PARENT, 0);
660 } else {
661 int parentId = findInMedia(path.getParent());
662 if (parentId != -1) {
663 values.put(Files.FileColumns.PARENT, parentId);
664 } else {
665 // The new parent isn't in MediaProvider, so delete the object instead
666 deleteFromMedia(oldPath, obj.isDir());
667 return;
668 }
669 }
670 // update MediaProvider
671 Cursor c = null;
672 String[] whereArgs = new String[]{oldPath.toString()};
Jerry Zhang952558d42017-09-26 17:49:52 -0700673 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700674 int parentId = -1;
675 if (!oldParentObj.isRoot()) {
676 parentId = findInMedia(oldPath.getParent());
677 }
678 if (oldParentObj.isRoot() || parentId != -1) {
679 // Old parent exists in MediaProvider - perform a move
680 // note - we are relying on a special case in MediaProvider.update() to update
681 // the paths for all children in the case where this is a directory.
682 mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
683 } else {
684 // Old parent doesn't exist - add the object
685 values.put(Files.FileColumns.FORMAT, obj.getFormat());
686 values.put(Files.FileColumns.SIZE, obj.getSize());
687 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
688 Uri uri = mMediaProvider.insert(mObjectsUri, values);
689 if (uri != null) {
690 rescanFile(path.toString(),
691 Integer.parseInt(uri.getPathSegments().get(2)), obj.getFormat());
692 }
693 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700694 } catch (RemoteException e) {
695 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
696 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700697 }
698
699 private int beginCopyObject(int handle, int newParent, int newStorage) {
700 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
701 MtpStorageManager.MtpObject parent = newParent == 0 ?
702 mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
703 if (obj == null || parent == null)
704 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
705 return mManager.beginCopyObject(obj, parent);
706 }
707
708 private void endCopyObject(int handle, boolean success) {
709 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
710 if (obj == null || !mManager.endCopyObject(obj, success)) {
711 Log.e(TAG, "Failed to end copy object");
712 return;
Jerry Zhang952558d42017-09-26 17:49:52 -0700713 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700714 if (!success) {
715 return;
716 }
717 String path = obj.getPath().toString();
718 int format = obj.getFormat();
719 // Get parent info from MediaProvider, since the id is different from MTP's
720 ContentValues values = new ContentValues();
721 values.put(Files.FileColumns.DATA, path);
722 values.put(Files.FileColumns.FORMAT, format);
723 values.put(Files.FileColumns.SIZE, obj.getSize());
724 values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
725 try {
726 if (obj.getParent().isRoot()) {
727 values.put(Files.FileColumns.PARENT, 0);
728 } else {
729 int parentId = findInMedia(obj.getParent().getPath());
730 if (parentId != -1) {
731 values.put(Files.FileColumns.PARENT, parentId);
732 } else {
733 // The parent isn't in MediaProvider. Don't add the new file.
734 return;
735 }
736 }
737 if (obj.isDir()) {
738 mMediaScanner.scanDirectories(new String[]{path});
739 } else {
740 Uri uri = mMediaProvider.insert(mObjectsUri, values);
741 if (uri != null) {
742 rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
743 }
744 }
745 } catch (RemoteException e) {
746 Log.e(TAG, "RemoteException in beginSendObject", e);
747 }
Jerry Zhang952558d42017-09-26 17:49:52 -0700748 }
749
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400750 private int setObjectProperty(int handle, int property,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700751 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400752 switch (property) {
753 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
754 return renameFile(handle, stringValue);
755
756 default:
757 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
758 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400759 }
760
761 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400762 switch (property) {
763 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
764 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500765 // writable string properties kept in shared preferences
766 String value = mDeviceProperties.getString(Integer.toString(property), "");
767 int length = value.length();
768 if (length > 255) {
769 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400770 }
Mike Lockwood775de952011-03-05 17:34:11 -0500771 value.getChars(0, length, outStringValue, 0);
772 outStringValue[length] = 0;
773 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800774 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
775 // use screen size as max image size
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700776 Display display = ((WindowManager) mContext.getSystemService(
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800777 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700778 int width = display.getMaximumSizeDimension();
779 int height = display.getMaximumSizeDimension();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700780 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800781 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
782 outStringValue[imageSize.length()] = 0;
783 return MtpConstants.RESPONSE_OK;
Jerry Zhang13bb2f42016-12-14 15:39:29 -0800784 case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
785 outIntValue[0] = mDeviceType;
786 return MtpConstants.RESPONSE_OK;
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700787 case MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL:
788 outIntValue[0] = mBatteryLevel;
789 outIntValue[1] = mBatteryScale;
790 return MtpConstants.RESPONSE_OK;
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800791 default:
792 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
793 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400794 }
795
796 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400797 switch (property) {
798 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
799 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500800 // writable string properties kept in shared prefs
801 SharedPreferences.Editor e = mDeviceProperties.edit();
802 e.putString(Integer.toString(property), stringValue);
803 return (e.commit() ? MtpConstants.RESPONSE_OK
804 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400805 }
806
807 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
808 }
809
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400810 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700811 char[] outName, long[] outCreatedModified) {
812 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
813 if (obj == null) {
814 return false;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400815 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700816 outStorageFormatParent[0] = obj.getStorageId();
817 outStorageFormatParent[1] = obj.getFormat();
818 outStorageFormatParent[2] = obj.getParent().isRoot() ? 0 : obj.getParent().getId();
819
820 int nameLen = Integer.min(obj.getName().length(), 255);
821 obj.getName().getChars(0, nameLen, outName, 0);
822 outName[nameLen] = 0;
823
824 outCreatedModified[0] = obj.getModifiedTime();
825 outCreatedModified[1] = obj.getModifiedTime();
826 return true;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400827 }
828
Mike Lockwood365e03e2010-12-08 16:08:01 -0800829 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700830 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
831 if (obj == null) {
832 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood01788562010-10-11 11:22:19 -0400833 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700834
835 String path = obj.getPath().toString();
836 int pathLen = Integer.min(path.length(), 4096);
837 path.getChars(0, pathLen, outFilePath, 0);
838 outFilePath[pathLen] = 0;
839
840 outFileLengthFormat[0] = obj.getSize();
841 outFileLengthFormat[1] = obj.getFormat();
842 return MtpConstants.RESPONSE_OK;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400843 }
844
Mike Lockwood71827742015-01-23 10:50:08 -0800845 private int getObjectFormat(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700846 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
847 if (obj == null) {
Mike Lockwood71827742015-01-23 10:50:08 -0800848 return -1;
Mike Lockwood71827742015-01-23 10:50:08 -0800849 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700850 return obj.getFormat();
Mike Lockwood71827742015-01-23 10:50:08 -0800851 }
852
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700853 private int beginDeleteObject(int handle) {
854 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
855 if (obj == null) {
856 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
857 }
858 if (!mManager.beginRemoveObject(obj)) {
859 return MtpConstants.RESPONSE_GENERAL_ERROR;
860 }
861 return MtpConstants.RESPONSE_OK;
862 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800863
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700864 private void endDeleteObject(int handle, boolean success) {
865 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
866 if (obj == null) {
867 return;
868 }
869 if (!mManager.endRemoveObject(obj, success))
870 Log.e(TAG, "Failed to end remove object");
871 if (success)
872 deleteFromMedia(obj.getPath(), obj.isDir());
873 }
874
875 private int findInMedia(Path path) {
876 int ret = -1;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800877 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400878 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700879 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
880 new String[]{path.toString()}, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800881 if (c != null && c.moveToNext()) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700882 ret = c.getInt(0);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800883 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700884 } catch (RemoteException e) {
885 Log.e(TAG, "Error finding " + path + " in MediaProvider");
886 } finally {
887 if (c != null)
888 c.close();
889 }
890 return ret;
891 }
Mike Lockwood55f808c2010-12-14 13:14:29 -0800892
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700893 private void deleteFromMedia(Path path, boolean isDir) {
894 try {
895 // Delete the object(s) from MediaProvider, but ignore errors.
896 if (isDir) {
Mike Lockwood55f808c2010-12-14 13:14:29 -0800897 // recursive case - delete all children first
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700898 mMediaProvider.delete(mObjectsUri,
899 // the 'like' makes it use the index, the 'lower()' makes it correct
900 // when the path contains sqlite wildcard characters
901 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
902 new String[]{path + "/%", Integer.toString(path.toString().length() + 1),
903 path.toString() + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800904 }
905
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700906 String[] whereArgs = new String[]{path.toString()};
907 if (mMediaProvider.delete(mObjectsUri, PATH_WHERE, whereArgs) > 0) {
908 if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800909 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700910 String parentPath = path.getParent().toString();
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700911 mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800912 } catch (RemoteException e) {
913 Log.e(TAG, "failed to unhide/rescan for " + path);
914 }
915 }
Mike Lockwood59c777a2010-08-02 10:37:41 -0400916 } else {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700917 Log.i(TAG, "Mediaprovider didn't delete " + path);
Mike Lockwood59c777a2010-08-02 10:37:41 -0400918 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700919 } catch (Exception e) {
920 Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400921 }
922 }
923
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400924 private int[] getObjectReferences(int handle) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700925 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
926 if (obj == null)
927 return null;
928 // Translate this handle to the MediaProvider Handle
929 handle = findInMedia(obj.getPath());
930 if (handle == -1)
931 return null;
Mike Lockwood8490e662010-09-09 14:16:22 -0400932 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400933 Cursor c = null;
934 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700935 c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400936 if (c == null) {
937 return null;
938 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700939 ArrayList<Integer> result = new ArrayList<>();
940 while (c.moveToNext()) {
941 // Translate result handles back into handles for this session.
942 String refPath = c.getString(0);
943 MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath);
944 if (refObj != null) {
945 result.add(refObj.getId());
946 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400947 }
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700948 return result.stream().mapToInt(Integer::intValue).toArray();
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400949 } catch (RemoteException e) {
950 Log.e(TAG, "RemoteException in getObjectList", e);
951 } finally {
952 if (c != null) {
953 c.close();
954 }
955 }
956 return null;
957 }
958
959 private int setObjectReferences(int handle, int[] references) {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700960 MtpStorageManager.MtpObject obj = mManager.getObject(handle);
961 if (obj == null)
962 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
963 // Translate this handle to the MediaProvider Handle
964 handle = findInMedia(obj.getPath());
965 if (handle == -1)
966 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood8490e662010-09-09 14:16:22 -0400967 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700968 ArrayList<ContentValues> valuesList = new ArrayList<>();
969 for (int id : references) {
970 // Translate each reference id to the MediaProvider Id
971 MtpStorageManager.MtpObject refObj = mManager.getObject(id);
972 if (refObj == null)
973 continue;
974 int refHandle = findInMedia(refObj.getPath());
975 if (refHandle == -1)
976 continue;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400977 ContentValues values = new ContentValues();
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700978 values.put(Files.FileColumns._ID, refHandle);
979 valuesList.add(values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400980 }
981 try {
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700982 if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400983 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400984 }
985 } catch (RemoteException e) {
986 Log.e(TAG, "RemoteException in setObjectReferences", e);
987 }
Mike Lockwood5367ab62010-08-30 13:23:02 -0400988 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400989 }
990
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400991 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +0000992 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400993
994 private native final void native_setup();
995 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400996}