MtpDocumentsProvider.java revision 4f04fd358d84f7ea8a9a26bf4c2ef4bef553c732
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; 203bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.content.UriPermission; 213faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.content.res.AssetFileDescriptor; 2217c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hironoimport android.content.res.Resources; 23c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor; 24c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.database.MatrixCursor; 255884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hironoimport android.database.sqlite.SQLiteDiskIOException; 263faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point; 279e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile; 289e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants; 29bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo; 303bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.net.Uri; 31c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.os.Bundle; 32c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal; 33fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hironoimport android.os.FileUriExposedException; 34fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hironoimport android.os.FileUtils; 35c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor; 36f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager; 37c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document; 38c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root; 39bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract; 40c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider; 413bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.provider.Settings; 42d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log; 43d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 44e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy; 45d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting; 46d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 47c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException; 48d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException; 494c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap; 503bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport java.util.List; 514c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map; 52acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hironoimport java.util.concurrent.TimeoutException; 53c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 54d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/** 55d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices. 56d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */ 57c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider { 582efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String AUTHORITY = "com.android.mtp.documents"; 592efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String TAG = "MtpDocumentsProvider"; 606baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 61c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, 62c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, 63c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_AVAILABLE_BYTES, 64c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 656baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 66c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, 67c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, 68c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 70c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 71f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono static final boolean DEBUG = false; 7219aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono 73e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private final Object mDeviceListLock = new Object(); 74e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 752efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private static MtpDocumentsProvider sSingleton; 762efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 772efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private MtpManager mMtpManager; 78d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono private ContentResolver mResolver; 79e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono @GuardedBy("mDeviceListLock") 804c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private Map<Integer, DeviceToolkit> mDeviceToolkits; 818b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono private RootScanner mRootScanner; 8217c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono private Resources mResources; 83dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono private MtpDatabase mDatabase; 84f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private AppFuse mAppFuse; 85fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono private ServiceIntentSender mIntentSender; 86d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 872efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono /** 882efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono * Provides singleton instance to MtpDocumentsService. 892efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono */ 902efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static MtpDocumentsProvider getInstance() { 912efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono return sSingleton; 922efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono } 932efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 94c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 95c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public boolean onCreate() { 962efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono sSingleton = this; 9717c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = getContext().getResources(); 982efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager = new MtpManager(getContext()); 99d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono mResolver = getContext().getContentResolver(); 1004c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 10147eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); 102f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); 103f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono mAppFuse = new AppFuse(TAG, new AppFuseCallback()); 104fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender = new ServiceIntentSender(getContext()); 1053bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 1063bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider 1073bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono // after booting. 1085884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono try { 1095884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1); 1105884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final int lastBootCount = mDatabase.getLastBootCount(); 1115884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono if (bootCount != -1 && bootCount != lastBootCount) { 1125884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mDatabase.setLastBootCount(bootCount); 1135884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final List<UriPermission> permissions = 1145884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mResolver.getOutgoingPersistedUriPermissions(); 1155884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final Uri[] uris = new Uri[permissions.size()]; 1165884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono for (int i = 0; i < permissions.size(); i++) { 1175884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono uris[i] = permissions.get(i).getUri(); 1185884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono } 1195884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mDatabase.cleanDatabase(uris); 1203bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono } 1215884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono } catch (SQLiteDiskIOException error) { 1225884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono // It can happen due to disk shortage. 1235884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono Log.e(TAG, "Failed to clean database.", error); 1245884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono return false; 1253bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono } 1263bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 127f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Mount AppFuse on demands. 128e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono try { 129e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono mAppFuse.mount(getContext().getSystemService(StorageManager.class)); 1305884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono } catch (IOException error) { 1315884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono Log.e(TAG, "Failed to start app fuse.", error); 132e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono return false; 133e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono } 1345884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono 135e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 136c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono return true; 137c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 138c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 139d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono @VisibleForTesting 140b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono boolean onCreateForTesting( 141dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono Resources resources, 142dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono MtpManager mtpManager, 143dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono ContentResolver resolver, 144b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono MtpDatabase database, 145fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono StorageManager storageManager, 146fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono ServiceIntentSender intentSender) { 14717c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = resources; 1486baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mMtpManager = mtpManager; 1496baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mResolver = resolver; 1504c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 151dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mDatabase = database; 152f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); 153b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse = new AppFuse(TAG, new AppFuseCallback()); 154fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender = intentSender; 1553bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 156b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono // TODO: Mount AppFuse on demands. 157b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono try { 158b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse.mount(storageManager); 159b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono } catch (IOException e) { 160b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono Log.e(TAG, "Failed to start app fuse.", e); 161b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return false; 162b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono } 163e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 164b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return true; 165d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 166d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 167c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 168c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryRoots(String[] projection) throws FileNotFoundException { 16950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono if (projection == null) { 17050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; 17150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 172f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono final Cursor cursor = mDatabase.queryRoots(mResources, projection); 17350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono cursor.setNotificationUri( 17450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); 17550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono return cursor; 176c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 177c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 178c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 179c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryDocument(String documentId, String[] projection) 180c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono throws FileNotFoundException { 181e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono if (projection == null) { 182e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 183e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono } 1849e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono return mDatabase.queryDocument(documentId, projection); 185c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 186c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 187c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 188124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono public Cursor queryChildDocuments(String parentDocumentId, 189124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono String[] projection, String sortOrder) throws FileNotFoundException { 19019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 19119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "queryChildDocuments: " + parentDocumentId); 19219aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 193124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono if (projection == null) { 194124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 195124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 1966a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId); 197124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono try { 198fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(parentIdentifier.mDeviceId); 1996a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) { 2002965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId); 2012965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono if (storageDocIds.length == 0) { 2022965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // Remote device does not provide storages. Maybe it is locked. 2032965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return createErrorCursor(projection, R.string.error_locked_device); 2042965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono } else if (storageDocIds.length > 1) { 2056a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // Returns storage list from database. 2066a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono return mDatabase.queryChildDocuments(projection, parentDocumentId); 2076a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 2082965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 2092965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // Exact one storage is found. Skip storage and returns object in the single 2102965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // storage. 2112965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]); 2126a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 2132965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 2146a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // Returns object list from document loader. 2154c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDocumentLoader(parentIdentifier).queryChildDocuments( 2164c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski projection, parentIdentifier); 217c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono } catch (BusyDeviceException exception) { 2182965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return createErrorCursor(projection, R.string.error_busy_device); 219124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } catch (IOException exception) { 2206a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception); 221124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono throw new FileNotFoundException(exception.getMessage()); 222124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 223c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 224c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 225c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 2268ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono public ParcelFileDescriptor openDocument( 2278ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono String documentId, String mode, CancellationSignal signal) 2288ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throws FileNotFoundException { 2296213cefbc06170f9463abf40c240322be11047bcDaichi Hirono if (DEBUG) { 2306213cefbc06170f9463abf40c240322be11047bcDaichi Hirono Log.d(TAG, "openDocument: " + documentId); 2316213cefbc06170f9463abf40c240322be11047bcDaichi Hirono } 2329e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2338ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono try { 234fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 2350f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; 236b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski switch (mode) { 237b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski case "r": 238f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final long fileSize = getFileSize(documentId); 239fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono // MTP getPartialObject operation does not support files that are larger than 240fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono // 4GB. Fallback to non-seekable file descriptor. 241f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Use getPartialObject64 for MTP devices that support Android vendor 242f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // extension. 2430f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono if (MtpDeviceRecord.isPartialReadSupported( 2440f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono device.operationsSupported, fileSize)) { 245f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return mAppFuse.openFile(Integer.parseInt(documentId)); 246f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } else { 247f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return getPipeManager(identifier).readDocument(mMtpManager, identifier); 248f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 249b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski case "w": 25081d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski // TODO: Clear the parent document loader task (if exists) and call notify 25181d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski // when writing is completed. 2520f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) { 2530f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono return getPipeManager(identifier).writeDocument( 25461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono getContext(), mMtpManager, identifier, device.operationsSupported); 2550f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono } else { 2560f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono throw new UnsupportedOperationException( 2570f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono "The device does not support writing operation."); 2580f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono } 259f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono case "rw": 260f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Add support for "rw" mode. 261b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski throw new UnsupportedOperationException( 262f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono "The provider does not support 'rw' mode."); 263f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono default: 264f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono throw new IllegalArgumentException("Unknown mode for openDocument: " + mode); 265b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski } 2668ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } catch (IOException error) { 2676a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocument", error); 2688ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throw new FileNotFoundException(error.getMessage()); 2698ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } 270d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 271d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 2723faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 2733faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public AssetFileDescriptor openDocumentThumbnail( 2743faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono String documentId, 2753faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono Point sizeHint, 2763faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono CancellationSignal signal) throws FileNotFoundException { 2779e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2783faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 279fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 2803faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono return new AssetFileDescriptor( 2814c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getPipeManager(identifier).readThumbnail(mMtpManager, identifier), 282573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono 0, // Start offset. 2833faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono AssetFileDescriptor.UNKNOWN_LENGTH); 2843faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 2856a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error); 2863faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 2873faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2883faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2893faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 2903faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 2913faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public void deleteDocument(String documentId) throws FileNotFoundException { 2923faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 2939e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 294fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 2956a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId); 2963faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); 2979e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono mDatabase.deleteDocument(documentId); 2984c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getDocumentLoader(parentIdentifier).clearTask(parentIdentifier); 2999e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono notifyChildDocumentsChange(parentIdentifier.mDocumentId); 3006a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) { 3016a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // If the parent is storage, the object might be appeared as child of device because 3026a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // we skip storage when the device has only one storage. 3036a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono final Identifier deviceIdentifier = mDatabase.getParentIdentifier( 3046a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono parentIdentifier.mDocumentId); 3056a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono notifyChildDocumentsChange(deviceIdentifier.mDocumentId); 3066a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 3073faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 3086a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error); 3093faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 3103faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 3113faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 3123faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 3136baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono @Override 3146baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono public void onTrimMemory(int level) { 315e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 316e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 317e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono toolkit.mDocumentLoader.clearCompletedTasks(); 318e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 319e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 3206baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono } 3216baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono 32287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski @Override 32387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski public String createDocument(String parentDocumentId, String mimeType, String displayName) 32487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throws FileNotFoundException { 3256213cefbc06170f9463abf40c240322be11047bcDaichi Hirono if (DEBUG) { 3266213cefbc06170f9463abf40c240322be11047bcDaichi Hirono Log.d(TAG, "createDocument: " + displayName); 3276213cefbc06170f9463abf40c240322be11047bcDaichi Hirono } 328fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final Identifier parentId; 329fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final MtpDeviceRecord record; 330fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final ParcelFileDescriptor[] pipe; 33187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski try { 332fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono parentId = mDatabase.createIdentifier(parentDocumentId); 333fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(parentId.mDeviceId); 334fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono record = getDeviceToolkit(parentId.mDeviceId).mDeviceRecord; 3350f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono if (!MtpDeviceRecord.isWritingSupported(record.operationsSupported)) { 336fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new UnsupportedOperationException( 337fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "Writing operation is not supported by the device."); 338fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 339fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe = ParcelFileDescriptor.createReliablePipe(); 340fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono int objectHandle = -1; 341fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MtpObjectInfo info = null; 342fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono try { 343fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe[0].close(); // 0 bytes for a new document. 344fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono 345fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ? 346fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MtpConstants.FORMAT_ASSOCIATION : 347fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MediaFile.getFormatCode(displayName, mimeType); 348fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono info = new MtpObjectInfo.Builder() 349fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setStorageId(parentId.mStorageId) 350fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setParent(parentId.mObjectHandle) 351fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setFormat(formatCode) 352fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setName(displayName) 353fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .build(); 354fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono 355fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String[] parts = FileUtils.splitFileName(mimeType, displayName); 356fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String baseName = parts[0]; 357fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String extension = parts[1]; 358fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono for (int i = 0; i <= 32; i++) { 359fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final MtpObjectInfo infoUniqueName; 360fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono if (i == 0) { 361fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono infoUniqueName = info; 362fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } else { 3634f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono String suffixedName = baseName + " (" + i + " )"; 3644f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono if (!extension.isEmpty()) { 3654f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono suffixedName += "." + extension; 3664f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono } 3674f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono infoUniqueName = 3684f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono new MtpObjectInfo.Builder(info).setName(suffixedName).build(); 369fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 370fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono try { 371fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono objectHandle = mMtpManager.createDocument( 372fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono parentId.mDeviceId, infoUniqueName, pipe[1]); 373fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono break; 374fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } catch (SendObjectInfoFailure exp) { 375fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono // This can be caused when we have an existing file with the same name. 376fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono continue; 377fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 378fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 379fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } finally { 380fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe[1].close(); 381fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 382fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono if (objectHandle == -1) { 383fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new IllegalArgumentException( 384fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "The file name \"" + displayName + "\" is conflicted with existing files " + 385fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "and the provider failed to find unique name."); 3860f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono } 3879e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo infoWithHandle = 3889e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build(); 3899e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final String documentId = mDatabase.putNewDocument( 39061ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono parentId.mDeviceId, parentDocumentId, record.operationsSupported, 39161ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono infoWithHandle); 3924c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getDocumentLoader(parentId).clearTask(parentId); 39387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski notifyChildDocumentsChange(parentDocumentId); 39487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski return documentId; 395fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } catch (FileNotFoundException | RuntimeException error) { 396fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono Log.e(TAG, "createDocument", error); 397fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw error; 39887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } catch (IOException error) { 3996a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(TAG, "createDocument", error); 400fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new IllegalStateException(error); 40187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 40287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 40387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski 4042efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono void openDevice(int deviceId) throws IOException { 405e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 406fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono if (mDeviceToolkits.containsKey(deviceId)) { 407fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono return; 408fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 40919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 41019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "Open device " + deviceId); 41119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 4120f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord device = mMtpManager.openDevice(deviceId); 4134e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono final DeviceToolkit toolkit = 41461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono new DeviceToolkit(mMtpManager, mResolver, mDatabase, device); 4154e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono mDeviceToolkits.put(deviceId, toolkit); 416fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender.sendUpdateNotificationIntent(); 417fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono try { 418fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mRootScanner.resume().await(); 419fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } catch (InterruptedException error) { 420fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono Log.e(TAG, "openDevice", error); 421fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 4224e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono // Resume document loader to remap disconnected document ID. Must be invoked after the 4234e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono // root scanner resumes. 4244e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono toolkit.mDocumentLoader.resume(); 425e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 426d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 427d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 428e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono void closeDevice(int deviceId) throws IOException, InterruptedException { 429e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 430e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(deviceId); 431e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 43220754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono mRootScanner.resume(); 433fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender.sendUpdateNotificationIntent(); 434d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 435d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 4360f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono MtpDeviceRecord[] getOpenedDeviceRecordsCache() { 437e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 4380f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord[] records = new MtpDeviceRecord[mDeviceToolkits.size()]; 4390f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono int i = 0; 4400f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 4410f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono records[i] = toolkit.mDeviceRecord; 4420f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono i++; 44320754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 4440f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono return records; 445e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 44650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 44750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono 448e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono /** 4491e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * Obtains document ID for the given device ID. 4501e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @param deviceId 4511e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @return document ID 4521e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @throws FileNotFoundException device ID has not been build. 4531e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono */ 4541e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono public String getDeviceDocumentId(int deviceId) throws FileNotFoundException { 4551e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono return mDatabase.getDeviceDocumentId(deviceId); 4561e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono } 4571e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono 4581e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono /** 459fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono * Resumes root scanner to handle the update of device list. 460fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono */ 461fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono void resumeRootScanner() { 462ebd24051599280443435606cab220de33b9356adDaichi Hirono if (DEBUG) { 463ebd24051599280443435606cab220de33b9356adDaichi Hirono Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner"); 464ebd24051599280443435606cab220de33b9356adDaichi Hirono } 465fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mRootScanner.resume(); 466fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 467fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono 468fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono /** 469e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono * Finalize the content provider for unit tests. 470e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono */ 471e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono @Override 472e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono public void shutdown() { 473e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 474e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono try { 4750f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono // Copy the opened key set because it will be modified when closing devices. 4760f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final Integer[] keySet = 4770f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono mDeviceToolkits.keySet().toArray(new Integer[mDeviceToolkits.size()]); 4780f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono for (final int id : keySet) { 479e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(id); 480e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 4812e9a57b0d4839b322787dad36bfcb64f211f7ac4Daichi Hirono mRootScanner.pause(); 482acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hirono } catch (InterruptedException | IOException | TimeoutException e) { 483e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // It should fail unit tests by throwing runtime exception. 484e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono throw new RuntimeException(e); 485e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } finally { 486e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.close(); 487b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse.close(); 488e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono super.shutdown(); 489e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 490e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 491e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 492e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono 4935fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono private void notifyChildDocumentsChange(String parentDocumentId) { 4945fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono mResolver.notifyChange( 4955fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId), 4965fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono null, 4975fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono false); 4985fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono } 4994c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 500e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono /** 501be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono * Clears MTP identifier in the database. 502e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono */ 503e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void resume() { 504e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 505e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.getMapper().clearMapping(); 506e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 507e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 508e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 509e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException { 510e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // TODO: Flush the device before closing (if not closed externally). 511fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono if (!mDeviceToolkits.containsKey(deviceId)) { 512fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono return; 513fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 51419aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 51519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "Close device " + deviceId); 51619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 51724ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono getDeviceToolkit(deviceId).close(); 518e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDeviceToolkits.remove(deviceId); 519e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mMtpManager.closeDevice(deviceId); 520e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 521e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 5224c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException { 523e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 524e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId); 525e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono if (toolkit == null) { 526e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono throw new FileNotFoundException(); 527e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 528e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono return toolkit; 5294c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 5304c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 5314c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 5324c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException { 5334c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mPipeManager; 5344c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 5354c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 5364c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException { 5374c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader; 5384c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 5394c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 540f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private long getFileSize(String documentId) throws FileNotFoundException { 541f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Cursor cursor = mDatabase.queryDocument( 542f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono documentId, 543f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME)); 544f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono try { 545f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono if (cursor.moveToNext()) { 546f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return cursor.getLong(0); 547f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } else { 548f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono throw new FileNotFoundException(); 549f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 550f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } finally { 551f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono cursor.close(); 552f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 553f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 554f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 5552965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono /** 5562965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * Creates empty cursor with specific error message. 5572965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * 5582965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @param projection Column names. 5592965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @param stringResId String resource ID of error message. 5602965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @return Empty cursor with error message. 5612965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono */ 5622965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono private Cursor createErrorCursor(String[] projection, int stringResId) { 5632965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final Bundle bundle = new Bundle(); 5642965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId)); 5652965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final Cursor cursor = new MatrixCursor(projection); 5662965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono cursor.setExtras(bundle); 5672965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return cursor; 5682965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono } 5692965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 57024ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono private static class DeviceToolkit implements AutoCloseable { 5714c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final PipeManager mPipeManager; 5724c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final DocumentLoader mDocumentLoader; 5730f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono public final MtpDeviceRecord mDeviceRecord; 5744c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 57561ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono public DeviceToolkit(MtpManager manager, 57661ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono ContentResolver resolver, 57761ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono MtpDatabase database, 57861ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono MtpDeviceRecord record) { 579f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono mPipeManager = new PipeManager(database); 58061ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono mDocumentLoader = new DocumentLoader(record, manager, resolver, database); 5810f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono mDeviceRecord = record; 5824c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 58324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono 58424ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono @Override 58524ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono public void close() throws InterruptedException { 58624ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono mPipeManager.close(); 58724ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono mDocumentLoader.close(); 58824ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono } 5894c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 590f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 591f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private class AppFuseCallback implements AppFuse.Callback { 592f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 5932f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono public long readObjectBytes( 5942f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono int inode, long offset, long size, byte[] buffer) throws IOException { 595f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode)); 5960f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; 5970f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono if (MtpDeviceRecord.isPartialReadSupported(record.operationsSupported, offset)) { 5980f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono return mMtpManager.getPartialObject( 5990f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer); 6000f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono } else { 6010f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono throw new UnsupportedOperationException(); 6020f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono } 603f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 604f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 605f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 606f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono public long getFileSize(int inode) throws FileNotFoundException { 607f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); 608f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 609f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 610c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono} 611