MtpDocumentsProvider.java revision b9ffa2a1d2cfd0b182629c849a22f2efa832892e
1c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono/* 2c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * Copyright (C) 2015 The Android Open Source Project 3c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * 4c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * Licensed under the Apache License, Version 2.0 (the "License"); 5c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * you may not use this file except in compliance with the License. 6c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * You may obtain a copy of the License at 7c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * 8c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * http://www.apache.org/licenses/LICENSE-2.0 9c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * 10c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * Unless required by applicable law or agreed to in writing, software 11c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * distributed under the License is distributed on an "AS IS" BASIS, 12c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * See the License for the specific language governing permissions and 14c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono * limitations under the License. 15c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono */ 16c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 17c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopackage com.android.mtp; 18c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 19d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.content.ContentResolver; 20f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.content.Context; 213bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.content.UriPermission; 223faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.content.res.AssetFileDescriptor; 2317c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hironoimport android.content.res.Resources; 24c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor; 25c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.database.MatrixCursor; 265884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hironoimport android.database.sqlite.SQLiteDiskIOException; 273faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point; 289e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile; 299e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants; 30bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo; 313bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.net.Uri; 32c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.os.Bundle; 33c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal; 34fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hironoimport android.os.FileUtils; 35c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor; 36f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager; 37c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document; 38b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport android.provider.DocumentsContract.Path; 39c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root; 40bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract; 41c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider; 423bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.provider.Settings; 43f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.system.ErrnoException; 44d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log; 45d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 46e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy; 47d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting; 48d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 49c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException; 50d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException; 514c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap; 52b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport java.util.LinkedList; 533bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport java.util.List; 544c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map; 55acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hironoimport java.util.concurrent.TimeoutException; 56c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 57d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/** 58d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices. 59d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */ 60c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider { 612efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String AUTHORITY = "com.android.mtp.documents"; 622efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String TAG = "MtpDocumentsProvider"; 636baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 64c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, 65c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, 66c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_AVAILABLE_BYTES, 67c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 686baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, 70c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, 71c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 72c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 73c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 74f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono static final boolean DEBUG = false; 7519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono 76e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private final Object mDeviceListLock = new Object(); 77e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 782efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private static MtpDocumentsProvider sSingleton; 792efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 802efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private MtpManager mMtpManager; 81d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono private ContentResolver mResolver; 82e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono @GuardedBy("mDeviceListLock") 834c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private Map<Integer, DeviceToolkit> mDeviceToolkits; 848b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono private RootScanner mRootScanner; 8517c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono private Resources mResources; 86dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono private MtpDatabase mDatabase; 87f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private AppFuse mAppFuse; 88fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono private ServiceIntentSender mIntentSender; 89f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono private Context mContext; 90d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 912efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono /** 922efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono * Provides singleton instance to MtpDocumentsService. 932efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono */ 942efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static MtpDocumentsProvider getInstance() { 952efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono return sSingleton; 962efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono } 972efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 98c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 99c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public boolean onCreate() { 1002efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono sSingleton = this; 101f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono mContext = getContext(); 10217c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = getContext().getResources(); 1032efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager = new MtpManager(getContext()); 104d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono mResolver = getContext().getContentResolver(); 1054c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 10647eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); 107f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); 108f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono mAppFuse = new AppFuse(TAG, new AppFuseCallback()); 109fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender = new ServiceIntentSender(getContext()); 1103bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 1113bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider 1123bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono // after booting. 1135884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono try { 1145884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1); 1155884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final int lastBootCount = mDatabase.getLastBootCount(); 1165884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono if (bootCount != -1 && bootCount != lastBootCount) { 1175884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mDatabase.setLastBootCount(bootCount); 1185884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final List<UriPermission> permissions = 1195884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mResolver.getOutgoingPersistedUriPermissions(); 1205884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final Uri[] uris = new Uri[permissions.size()]; 1215884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono for (int i = 0; i < permissions.size(); i++) { 1225884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono uris[i] = permissions.get(i).getUri(); 1235884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono } 1245884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mDatabase.cleanDatabase(uris); 1253bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono } 1265884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono } catch (SQLiteDiskIOException error) { 1275884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono // It can happen due to disk shortage. 1285884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono Log.e(TAG, "Failed to clean database.", error); 1295884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono return false; 1303bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono } 1313bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 132f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Mount AppFuse on demands. 133e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono try { 134e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono mAppFuse.mount(getContext().getSystemService(StorageManager.class)); 1355884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono } catch (IOException error) { 1365884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono Log.e(TAG, "Failed to start app fuse.", error); 137e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono return false; 138e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono } 1395884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono 140e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 141c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono return true; 142c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 143c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 144d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono @VisibleForTesting 145b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono boolean onCreateForTesting( 146f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono Context context, 147dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono Resources resources, 148dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono MtpManager mtpManager, 149dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono ContentResolver resolver, 150b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono MtpDatabase database, 151fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono StorageManager storageManager, 152fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono ServiceIntentSender intentSender) { 153f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono mContext = context; 15417c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = resources; 1556baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mMtpManager = mtpManager; 1566baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mResolver = resolver; 1574c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 158dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mDatabase = database; 159f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); 160b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse = new AppFuse(TAG, new AppFuseCallback()); 161fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender = intentSender; 1623bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 163b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono // TODO: Mount AppFuse on demands. 164b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono try { 165b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse.mount(storageManager); 166b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono } catch (IOException e) { 167b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono Log.e(TAG, "Failed to start app fuse.", e); 168b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return false; 169b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono } 170e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 171b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return true; 172d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 173d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 174c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 175c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryRoots(String[] projection) throws FileNotFoundException { 17650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono if (projection == null) { 17750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; 17850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 179f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono final Cursor cursor = mDatabase.queryRoots(mResources, projection); 18050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono cursor.setNotificationUri( 18150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); 18250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono return cursor; 183c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 184c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 185c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 186c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryDocument(String documentId, String[] projection) 187c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono throws FileNotFoundException { 188e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono if (projection == null) { 189e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 190e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono } 1919e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono return mDatabase.queryDocument(documentId, projection); 192c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 193c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 194c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 195124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono public Cursor queryChildDocuments(String parentDocumentId, 196124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono String[] projection, String sortOrder) throws FileNotFoundException { 19719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 19819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "queryChildDocuments: " + parentDocumentId); 19919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 200124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono if (projection == null) { 201124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 202124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 2036a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId); 204124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono try { 205fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(parentIdentifier.mDeviceId); 2066a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) { 2072965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId); 2082965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono if (storageDocIds.length == 0) { 2092965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // Remote device does not provide storages. Maybe it is locked. 2102965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return createErrorCursor(projection, R.string.error_locked_device); 2112965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono } else if (storageDocIds.length > 1) { 2126a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // Returns storage list from database. 2136a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono return mDatabase.queryChildDocuments(projection, parentDocumentId); 2146a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 2152965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 2162965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // Exact one storage is found. Skip storage and returns object in the single 2172965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // storage. 2182965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]); 2196a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 2202965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 2216a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // Returns object list from document loader. 2224c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDocumentLoader(parentIdentifier).queryChildDocuments( 2234c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski projection, parentIdentifier); 224c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono } catch (BusyDeviceException exception) { 2252965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return createErrorCursor(projection, R.string.error_busy_device); 226124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } catch (IOException exception) { 2276a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception); 228124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono throw new FileNotFoundException(exception.getMessage()); 229124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 230c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 231c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 232c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 2338ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono public ParcelFileDescriptor openDocument( 2348ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono String documentId, String mode, CancellationSignal signal) 2358ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throws FileNotFoundException { 2366213cefbc06170f9463abf40c240322be11047bcDaichi Hirono if (DEBUG) { 2376213cefbc06170f9463abf40c240322be11047bcDaichi Hirono Log.d(TAG, "openDocument: " + documentId); 2386213cefbc06170f9463abf40c240322be11047bcDaichi Hirono } 2399e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2408ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono try { 241fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 2420f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; 243f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // Turn off MODE_CREATE because openDocument does not allow to create new files. 244f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono final int modeFlag = 245f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE; 246f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) { 247f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono long fileSize; 248f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono try { 249f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono fileSize = getFileSize(documentId); 250f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } catch (UnsupportedOperationException exception) { 251f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono fileSize = -1; 252f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 253f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if (MtpDeviceRecord.isPartialReadSupported( 254f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono device.operationsSupported, fileSize)) { 255f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag); 256f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else { 257f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // If getPartialObject{|64} are not supported for the device, returns 258f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // non-seekable pipe FD instead. 259f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono return getPipeManager(identifier).readDocument(mMtpManager, identifier); 260f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 261f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) { 262f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // TODO: Clear the parent document loader task (if exists) and call notify 263f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // when writing is completed. 264f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) { 265f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag); 266f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else { 267b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski throw new UnsupportedOperationException( 268f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono "The device does not support writing operation."); 269f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 270f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else { 271f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // TODO: Add support for "rw" mode. 272f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono throw new UnsupportedOperationException("The provider does not support 'rw' mode."); 273b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski } 274f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } catch (FileNotFoundException | RuntimeException error) { 275f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocument", error); 276f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono throw error; 2778ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } catch (IOException error) { 2786a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocument", error); 279f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono throw new IllegalStateException(error); 2808ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } 281d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 282d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 2833faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 2843faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public AssetFileDescriptor openDocumentThumbnail( 2853faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono String documentId, 2863faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono Point sizeHint, 2873faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono CancellationSignal signal) throws FileNotFoundException { 2889e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2893faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 290fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 2913faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono return new AssetFileDescriptor( 2924c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getPipeManager(identifier).readThumbnail(mMtpManager, identifier), 293573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono 0, // Start offset. 2943faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono AssetFileDescriptor.UNKNOWN_LENGTH); 2953faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 2966a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error); 2973faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 2983faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2993faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 3003faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 3013faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 3023faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public void deleteDocument(String documentId) throws FileNotFoundException { 3033faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 3049e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 305fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 3066a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId); 3073faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); 3089e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono mDatabase.deleteDocument(documentId); 30976be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono getDocumentLoader(parentIdentifier).cancelTask(parentIdentifier); 3109e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono notifyChildDocumentsChange(parentIdentifier.mDocumentId); 3116a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) { 3126a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // If the parent is storage, the object might be appeared as child of device because 3136a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // we skip storage when the device has only one storage. 3146a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono final Identifier deviceIdentifier = mDatabase.getParentIdentifier( 3156a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono parentIdentifier.mDocumentId); 3166a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono notifyChildDocumentsChange(deviceIdentifier.mDocumentId); 3176a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 3183faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 3196a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error); 3203faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 3213faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 3223faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 3233faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 3246baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono @Override 3256baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono public void onTrimMemory(int level) { 326e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 327e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 328e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono toolkit.mDocumentLoader.clearCompletedTasks(); 329e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 330e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 3316baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono } 3326baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono 33387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski @Override 33487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski public String createDocument(String parentDocumentId, String mimeType, String displayName) 33587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throws FileNotFoundException { 3366213cefbc06170f9463abf40c240322be11047bcDaichi Hirono if (DEBUG) { 3376213cefbc06170f9463abf40c240322be11047bcDaichi Hirono Log.d(TAG, "createDocument: " + displayName); 3386213cefbc06170f9463abf40c240322be11047bcDaichi Hirono } 339fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final Identifier parentId; 340fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final MtpDeviceRecord record; 341fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final ParcelFileDescriptor[] pipe; 34287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski try { 343fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono parentId = mDatabase.createIdentifier(parentDocumentId); 344fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(parentId.mDeviceId); 345fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono record = getDeviceToolkit(parentId.mDeviceId).mDeviceRecord; 3460f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono if (!MtpDeviceRecord.isWritingSupported(record.operationsSupported)) { 347fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new UnsupportedOperationException( 348fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "Writing operation is not supported by the device."); 349fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 350fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe = ParcelFileDescriptor.createReliablePipe(); 351fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono int objectHandle = -1; 352fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MtpObjectInfo info = null; 353fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono try { 354fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe[0].close(); // 0 bytes for a new document. 355fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono 356fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ? 357fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MtpConstants.FORMAT_ASSOCIATION : 358fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MediaFile.getFormatCode(displayName, mimeType); 359fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono info = new MtpObjectInfo.Builder() 360fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setStorageId(parentId.mStorageId) 361fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setParent(parentId.mObjectHandle) 362fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setFormat(formatCode) 363fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setName(displayName) 364fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .build(); 365fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono 366fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String[] parts = FileUtils.splitFileName(mimeType, displayName); 367fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String baseName = parts[0]; 368fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String extension = parts[1]; 369fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono for (int i = 0; i <= 32; i++) { 370fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final MtpObjectInfo infoUniqueName; 371fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono if (i == 0) { 372fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono infoUniqueName = info; 373fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } else { 3744f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono String suffixedName = baseName + " (" + i + " )"; 3754f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono if (!extension.isEmpty()) { 3764f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono suffixedName += "." + extension; 3774f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono } 3784f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono infoUniqueName = 3794f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono new MtpObjectInfo.Builder(info).setName(suffixedName).build(); 380fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 381fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono try { 382fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono objectHandle = mMtpManager.createDocument( 383fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono parentId.mDeviceId, infoUniqueName, pipe[1]); 384fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono break; 385fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } catch (SendObjectInfoFailure exp) { 386fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono // This can be caused when we have an existing file with the same name. 387fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono continue; 388fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 389fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 390fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } finally { 391fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe[1].close(); 392fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 393fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono if (objectHandle == -1) { 394fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new IllegalArgumentException( 395fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "The file name \"" + displayName + "\" is conflicted with existing files " + 396fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "and the provider failed to find unique name."); 3970f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono } 3989e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo infoWithHandle = 3999e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build(); 4009e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final String documentId = mDatabase.putNewDocument( 40161ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono parentId.mDeviceId, parentDocumentId, record.operationsSupported, 40264111e08d905525c7f4fe27e69953eb71bd62511Daichi Hirono infoWithHandle, 0l); 40376be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono getDocumentLoader(parentId).cancelTask(parentId); 40487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski notifyChildDocumentsChange(parentDocumentId); 40587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski return documentId; 406fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } catch (FileNotFoundException | RuntimeException error) { 407fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono Log.e(TAG, "createDocument", error); 408fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw error; 40987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } catch (IOException error) { 4106a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(TAG, "createDocument", error); 411fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new IllegalStateException(error); 41287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 41387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 41487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski 415b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono @Override 416b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono public Path findPath(String childDocumentId, String parentDocumentId) 417b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono throws FileNotFoundException { 418b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final LinkedList<String> ids = new LinkedList<>(); 419b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final Identifier childIdentifier = mDatabase.createIdentifier(childDocumentId); 420b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono 421b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono Identifier i = childIdentifier; 422b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono outer: while (true) { 423b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono if (i.mDocumentId.equals(parentDocumentId)) { 424b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 425b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break; 426b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 427b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono switch (i.mDocumentType) { 428b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT: 429b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 430b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono i = mDatabase.getParentIdentifier(i.mDocumentId); 431b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break; 432b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE: { 433b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono // Check if there is the multiple storage. 434b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final Identifier deviceIdentifier = 435b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono mDatabase.getParentIdentifier(i.mDocumentId); 436b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final String[] storageIds = 437b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono mDatabase.getStorageDocumentIds(deviceIdentifier.mDocumentId); 438b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono // Add storage's document ID to the path only when the device has multiple 439b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono // storages. 440b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono if (storageIds.length > 1) { 441b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 442b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break outer; 443b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 444b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono i = deviceIdentifier; 445b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break; 446b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 447b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE: 448b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 449b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break outer; 450b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 451b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 452b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono 453b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono if (parentDocumentId != null) { 454b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono return new Path(null, ids); 455b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } else { 456b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono return new Path(/* Should be same with root ID */ i.mDocumentId, ids); 457b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 458b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 459b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono 4602efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono void openDevice(int deviceId) throws IOException { 461e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 462fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono if (mDeviceToolkits.containsKey(deviceId)) { 463fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono return; 464fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 46519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 46619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "Open device " + deviceId); 46719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 4680f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord device = mMtpManager.openDevice(deviceId); 4694e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono final DeviceToolkit toolkit = 47061ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono new DeviceToolkit(mMtpManager, mResolver, mDatabase, device); 4714e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono mDeviceToolkits.put(deviceId, toolkit); 472fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender.sendUpdateNotificationIntent(); 473fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono try { 474fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mRootScanner.resume().await(); 475fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } catch (InterruptedException error) { 476fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono Log.e(TAG, "openDevice", error); 477fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 4784e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono // Resume document loader to remap disconnected document ID. Must be invoked after the 4794e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono // root scanner resumes. 4804e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono toolkit.mDocumentLoader.resume(); 481e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 482d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 483d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 484e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono void closeDevice(int deviceId) throws IOException, InterruptedException { 485e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 486e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(deviceId); 487e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 48820754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono mRootScanner.resume(); 489fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender.sendUpdateNotificationIntent(); 490d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 491d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 4920f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono MtpDeviceRecord[] getOpenedDeviceRecordsCache() { 493e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 4940f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord[] records = new MtpDeviceRecord[mDeviceToolkits.size()]; 4950f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono int i = 0; 4960f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 4970f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono records[i] = toolkit.mDeviceRecord; 4980f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono i++; 49920754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 5000f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono return records; 501e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 50250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 50350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono 504e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono /** 5051e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * Obtains document ID for the given device ID. 5061e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @param deviceId 5071e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @return document ID 5081e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @throws FileNotFoundException device ID has not been build. 5091e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono */ 5101e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono public String getDeviceDocumentId(int deviceId) throws FileNotFoundException { 5111e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono return mDatabase.getDeviceDocumentId(deviceId); 5121e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono } 5131e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono 5141e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono /** 515fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono * Resumes root scanner to handle the update of device list. 516fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono */ 517fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono void resumeRootScanner() { 518ebd24051599280443435606cab220de33b9356adDaichi Hirono if (DEBUG) { 519ebd24051599280443435606cab220de33b9356adDaichi Hirono Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner"); 520ebd24051599280443435606cab220de33b9356adDaichi Hirono } 521fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mRootScanner.resume(); 522fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 523fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono 524fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono /** 525e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono * Finalize the content provider for unit tests. 526e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono */ 527e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono @Override 528e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono public void shutdown() { 529e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 530e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono try { 5310f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono // Copy the opened key set because it will be modified when closing devices. 5320f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final Integer[] keySet = 5330f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono mDeviceToolkits.keySet().toArray(new Integer[mDeviceToolkits.size()]); 5340f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono for (final int id : keySet) { 535e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(id); 536e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 5372e9a57b0d4839b322787dad36bfcb64f211f7ac4Daichi Hirono mRootScanner.pause(); 538acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hirono } catch (InterruptedException | IOException | TimeoutException e) { 539e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // It should fail unit tests by throwing runtime exception. 540e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono throw new RuntimeException(e); 541e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } finally { 542e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.close(); 543b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse.close(); 544e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono super.shutdown(); 545e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 546e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 547e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 548e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono 5495fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono private void notifyChildDocumentsChange(String parentDocumentId) { 5505fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono mResolver.notifyChange( 5515fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId), 5525fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono null, 5535fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono false); 5545fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono } 5554c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 556e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono /** 557be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono * Clears MTP identifier in the database. 558e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono */ 559e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void resume() { 560e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 561e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.getMapper().clearMapping(); 562e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 563e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 564e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 565e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException { 566e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // TODO: Flush the device before closing (if not closed externally). 567fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono if (!mDeviceToolkits.containsKey(deviceId)) { 568fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono return; 569fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 57019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 57119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "Close device " + deviceId); 57219aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 57324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono getDeviceToolkit(deviceId).close(); 574e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDeviceToolkits.remove(deviceId); 575e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mMtpManager.closeDevice(deviceId); 576e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 577e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 5784c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException { 579e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 580e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId); 581e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono if (toolkit == null) { 582e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono throw new FileNotFoundException(); 583e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 584e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono return toolkit; 5854c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 5864c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 5874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 5884c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException { 5894c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mPipeManager; 5904c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 5914c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 5924c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException { 5934c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader; 5944c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 5954c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 596f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private long getFileSize(String documentId) throws FileNotFoundException { 597f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Cursor cursor = mDatabase.queryDocument( 598f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono documentId, 599f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME)); 600f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono try { 601f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono if (cursor.moveToNext()) { 60277a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono if (cursor.isNull(0)) { 60377a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono throw new UnsupportedOperationException(); 60477a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono } 605f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return cursor.getLong(0); 606f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } else { 607f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono throw new FileNotFoundException(); 608f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 609f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } finally { 610f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono cursor.close(); 611f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 612f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 613f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 6142965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono /** 6152965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * Creates empty cursor with specific error message. 6162965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * 6172965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @param projection Column names. 6182965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @param stringResId String resource ID of error message. 6192965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @return Empty cursor with error message. 6202965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono */ 6212965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono private Cursor createErrorCursor(String[] projection, int stringResId) { 6222965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final Bundle bundle = new Bundle(); 6232965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId)); 6242965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final Cursor cursor = new MatrixCursor(projection); 6252965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono cursor.setExtras(bundle); 6262965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return cursor; 6272965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono } 6282965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 62924ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono private static class DeviceToolkit implements AutoCloseable { 6304c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final PipeManager mPipeManager; 6314c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final DocumentLoader mDocumentLoader; 6320f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono public final MtpDeviceRecord mDeviceRecord; 6334c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 63461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono public DeviceToolkit(MtpManager manager, 63561ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono ContentResolver resolver, 63661ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono MtpDatabase database, 63761ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono MtpDeviceRecord record) { 638f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono mPipeManager = new PipeManager(database); 63961ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono mDocumentLoader = new DocumentLoader(record, manager, resolver, database); 6400f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono mDeviceRecord = record; 6414c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 64224ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono 64324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono @Override 64424ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono public void close() throws InterruptedException { 64524ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono mPipeManager.close(); 64624ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono mDocumentLoader.close(); 64724ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono } 6484c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 649f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 650f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private class AppFuseCallback implements AppFuse.Callback { 651f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono private final Map<Long, MtpFileWriter> mWriters = new HashMap<>(); 652f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono 653f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono @Override 654f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono public long getFileSize(int inode) throws FileNotFoundException { 655f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); 656f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 657f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono 658f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 6592f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono public long readObjectBytes( 6602f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono int inode, long offset, long size, byte[] buffer) throws IOException { 661f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode)); 6620f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; 66377a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono 66477a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono if (MtpDeviceRecord.isSupported( 66577a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) { 66677a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono return mMtpManager.getPartialObject64( 66777a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer); 66877a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono } 66977a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono 67077a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported( 67177a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) { 6720f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono return mMtpManager.getPartialObject( 6730f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer); 6740f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono } 67577a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono 67677a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono throw new UnsupportedOperationException(); 677f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 678f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 679f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 680f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono public int writeObjectBytes( 681f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono long fileHandle, int inode, long offset, int size, byte[] bytes) 682f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono throws IOException, ErrnoException { 683f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono final MtpFileWriter writer; 684f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if (mWriters.containsKey(fileHandle)) { 685f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono writer = mWriters.get(fileHandle); 686f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else { 687f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono writer = new MtpFileWriter(mContext, String.valueOf(inode)); 688f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono mWriters.put(fileHandle, writer); 689f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 690f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono return writer.write(offset, size, bytes); 691f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 69209ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono 69309ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono @Override 694f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono public void flushFileHandle(long fileHandle) throws IOException, ErrnoException { 695f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono final MtpFileWriter writer = mWriters.get(fileHandle); 696f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if (writer == null) { 697f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // File handle for reading. 698f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono return; 699f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 700f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono final MtpDeviceRecord device = getDeviceToolkit( 701f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono mDatabase.createIdentifier(writer.getDocumentId()).mDeviceId).mDeviceRecord; 702f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono writer.flush(mMtpManager, mDatabase, device.operationsSupported); 703f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 704f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono 705f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono @Override 706f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono public void closeFileHandle(long fileHandle) throws IOException, ErrnoException { 707f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono final MtpFileWriter writer = mWriters.get(fileHandle); 708f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if (writer == null) { 709f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // File handle for reading. 710f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono return; 711f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 712f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono try { 713f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono writer.close(); 714f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } finally { 715f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono mWriters.remove(fileHandle); 716f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 71709ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono } 718f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 719c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono} 720