MtpDocumentsProvider.java revision 203be491ef479deea1f39f8b63f3e1916501a37a
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; 36e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport android.os.ProxyFileDescriptorCallback; 37f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager; 38c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document; 39b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport android.provider.DocumentsContract.Path; 40c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root; 41bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract; 42c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider; 433bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.provider.Settings; 44f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.system.ErrnoException; 45e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport android.system.OsConstants; 46d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log; 47d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 48e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy; 49d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting; 50d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 51c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException; 52d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException; 534c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap; 54b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport java.util.LinkedList; 553bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport java.util.List; 564c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map; 57acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hironoimport java.util.concurrent.TimeoutException; 58c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 59e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport libcore.io.IoUtils; 60e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono 61d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/** 62d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices. 63d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */ 64c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider { 652efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String AUTHORITY = "com.android.mtp.documents"; 662efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String TAG = "MtpDocumentsProvider"; 676baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 68c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, 69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, 70c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_AVAILABLE_BYTES, 71c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 726baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 73c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, 74c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, 75c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 76c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 77c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 78f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono static final boolean DEBUG = false; 7919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono 80e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private final Object mDeviceListLock = new Object(); 81e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 822efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private static MtpDocumentsProvider sSingleton; 832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 842efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private MtpManager mMtpManager; 85d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono private ContentResolver mResolver; 86e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono @GuardedBy("mDeviceListLock") 874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private Map<Integer, DeviceToolkit> mDeviceToolkits; 888b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono private RootScanner mRootScanner; 8917c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono private Resources mResources; 90dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono private MtpDatabase mDatabase; 91fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono private ServiceIntentSender mIntentSender; 92f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono private Context mContext; 93e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono private StorageManager mStorageManager; 94d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 952efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono /** 962efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono * Provides singleton instance to MtpDocumentsService. 972efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono */ 982efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static MtpDocumentsProvider getInstance() { 992efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono return sSingleton; 1002efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono } 1012efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 102c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 103c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public boolean onCreate() { 1042efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono sSingleton = this; 105f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono mContext = getContext(); 10617c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = getContext().getResources(); 1072efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager = new MtpManager(getContext()); 108d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono mResolver = getContext().getContentResolver(); 1094c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 11047eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); 111f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); 112fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender = new ServiceIntentSender(getContext()); 113e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mStorageManager = getContext().getSystemService(StorageManager.class); 1143bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 1153bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider 1163bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono // after booting. 1175884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono try { 1185884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1); 1195884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final int lastBootCount = mDatabase.getLastBootCount(); 1205884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono if (bootCount != -1 && bootCount != lastBootCount) { 1215884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mDatabase.setLastBootCount(bootCount); 1225884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final List<UriPermission> permissions = 1235884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mResolver.getOutgoingPersistedUriPermissions(); 1245884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final Uri[] uris = new Uri[permissions.size()]; 1255884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono for (int i = 0; i < permissions.size(); i++) { 1265884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono uris[i] = permissions.get(i).getUri(); 1275884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono } 1285884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mDatabase.cleanDatabase(uris); 1293bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono } 1305884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono } catch (SQLiteDiskIOException error) { 1315884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono // It can happen due to disk shortage. 1325884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono Log.e(TAG, "Failed to clean database.", error); 1335884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono return false; 1343bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono } 1353bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 136e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 137c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono return true; 138c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 139c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 140d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono @VisibleForTesting 141b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono boolean onCreateForTesting( 142f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono Context context, 143dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono Resources resources, 144dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono MtpManager mtpManager, 145dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono ContentResolver resolver, 146b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono MtpDatabase database, 147fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono StorageManager storageManager, 148fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono ServiceIntentSender intentSender) { 149f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono mContext = context; 15017c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = resources; 1516baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mMtpManager = mtpManager; 1526baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mResolver = resolver; 1534c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 154dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mDatabase = database; 155f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); 156fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender = intentSender; 157e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mStorageManager = storageManager; 1583bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 159e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 160b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return true; 161d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 162d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 163c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 164c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryRoots(String[] projection) throws FileNotFoundException { 16550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono if (projection == null) { 16650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; 16750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 168f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono final Cursor cursor = mDatabase.queryRoots(mResources, projection); 16950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono cursor.setNotificationUri( 17050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); 17150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono return cursor; 172c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 173c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 174c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 175c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryDocument(String documentId, String[] projection) 176c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono throws FileNotFoundException { 177e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono if (projection == null) { 178e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 179e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono } 1809e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono return mDatabase.queryDocument(documentId, projection); 181c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 182c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 183c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 184124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono public Cursor queryChildDocuments(String parentDocumentId, 185124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono String[] projection, String sortOrder) throws FileNotFoundException { 18619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 18719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "queryChildDocuments: " + parentDocumentId); 18819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 189124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono if (projection == null) { 190124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 191124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 1926a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId); 193124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono try { 194fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(parentIdentifier.mDeviceId); 1956a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) { 1962965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId); 1972965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono if (storageDocIds.length == 0) { 1982965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // Remote device does not provide storages. Maybe it is locked. 1992965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return createErrorCursor(projection, R.string.error_locked_device); 2002965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono } else if (storageDocIds.length > 1) { 2016a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // Returns storage list from database. 2026a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono return mDatabase.queryChildDocuments(projection, parentDocumentId); 2036a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 2042965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 2052965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // Exact one storage is found. Skip storage and returns object in the single 2062965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // storage. 2072965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]); 2086a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 2092965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 2106a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // Returns object list from document loader. 2114c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDocumentLoader(parentIdentifier).queryChildDocuments( 2124c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski projection, parentIdentifier); 213c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono } catch (BusyDeviceException exception) { 2142965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return createErrorCursor(projection, R.string.error_busy_device); 215124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } catch (IOException exception) { 2166a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception); 217124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono throw new FileNotFoundException(exception.getMessage()); 218124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 219c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 220c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 221c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 2228ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono public ParcelFileDescriptor openDocument( 2238ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono String documentId, String mode, CancellationSignal signal) 2248ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throws FileNotFoundException { 2256213cefbc06170f9463abf40c240322be11047bcDaichi Hirono if (DEBUG) { 2266213cefbc06170f9463abf40c240322be11047bcDaichi Hirono Log.d(TAG, "openDocument: " + documentId); 2276213cefbc06170f9463abf40c240322be11047bcDaichi Hirono } 2289e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2298ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono try { 230fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 2310f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; 232f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // Turn off MODE_CREATE because openDocument does not allow to create new files. 233f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono final int modeFlag = 234f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE; 235f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) { 236f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono long fileSize; 237f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono try { 238f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono fileSize = getFileSize(documentId); 239f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } catch (UnsupportedOperationException exception) { 240f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono fileSize = -1; 241f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 242f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if (MtpDeviceRecord.isPartialReadSupported( 243f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono device.operationsSupported, fileSize)) { 244e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono 245e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return mStorageManager.openProxyFileDescriptor( 246e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono modeFlag, 247e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId))); 248f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else { 249f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // If getPartialObject{|64} are not supported for the device, returns 250f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // non-seekable pipe FD instead. 251f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono return getPipeManager(identifier).readDocument(mMtpManager, identifier); 252f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 253f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) { 254f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // TODO: Clear the parent document loader task (if exists) and call notify 255f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // when writing is completed. 256f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) { 257e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return mStorageManager.openProxyFileDescriptor( 258e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono modeFlag, 259e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId))); 260f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else { 261b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski throw new UnsupportedOperationException( 262f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono "The device does not support writing operation."); 263f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 264f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else { 265f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // TODO: Add support for "rw" mode. 266f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono throw new UnsupportedOperationException("The provider does not support 'rw' mode."); 267b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski } 268f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } catch (FileNotFoundException | RuntimeException error) { 269f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocument", error); 270f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono throw error; 2718ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } catch (IOException error) { 2726a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocument", error); 273f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono throw new IllegalStateException(error); 2748ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } 275d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 276d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 2773faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 2783faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public AssetFileDescriptor openDocumentThumbnail( 2793faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono String documentId, 2803faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono Point sizeHint, 2813faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono CancellationSignal signal) throws FileNotFoundException { 2829e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2833faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 284fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 2853faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono return new AssetFileDescriptor( 2864c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getPipeManager(identifier).readThumbnail(mMtpManager, identifier), 287573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono 0, // Start offset. 2883faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono AssetFileDescriptor.UNKNOWN_LENGTH); 2893faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 2906a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error); 2913faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 2923faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2933faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2943faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 2953faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 2963faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public void deleteDocument(String documentId) throws FileNotFoundException { 2973faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 2989e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 299fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 3006a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId); 3013faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); 3029e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono mDatabase.deleteDocument(documentId); 30376be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono getDocumentLoader(parentIdentifier).cancelTask(parentIdentifier); 3049e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono notifyChildDocumentsChange(parentIdentifier.mDocumentId); 3056a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) { 3066a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // If the parent is storage, the object might be appeared as child of device because 3076a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // we skip storage when the device has only one storage. 3086a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono final Identifier deviceIdentifier = mDatabase.getParentIdentifier( 3096a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono parentIdentifier.mDocumentId); 3106a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono notifyChildDocumentsChange(deviceIdentifier.mDocumentId); 3116a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 3123faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 3136a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error); 3143faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 3153faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 3163faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 3173faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 3186baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono @Override 3196baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono public void onTrimMemory(int level) { 320e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 321e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 322e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono toolkit.mDocumentLoader.clearCompletedTasks(); 323e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 324e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 3256baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono } 3266baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono 32787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski @Override 32887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski public String createDocument(String parentDocumentId, String mimeType, String displayName) 32987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throws FileNotFoundException { 3306213cefbc06170f9463abf40c240322be11047bcDaichi Hirono if (DEBUG) { 3316213cefbc06170f9463abf40c240322be11047bcDaichi Hirono Log.d(TAG, "createDocument: " + displayName); 3326213cefbc06170f9463abf40c240322be11047bcDaichi Hirono } 333fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final Identifier parentId; 334fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final MtpDeviceRecord record; 335fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final ParcelFileDescriptor[] pipe; 33687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski try { 337fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono parentId = mDatabase.createIdentifier(parentDocumentId); 338fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(parentId.mDeviceId); 339fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono record = getDeviceToolkit(parentId.mDeviceId).mDeviceRecord; 3400f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono if (!MtpDeviceRecord.isWritingSupported(record.operationsSupported)) { 341fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new UnsupportedOperationException( 342fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "Writing operation is not supported by the device."); 343fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 34435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono 34535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono final int parentObjectHandle; 34635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono final int storageId; 34735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono switch (parentId.mDocumentType) { 34835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE: 34935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono final String[] storageDocumentIds = 35035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono mDatabase.getStorageDocumentIds(parentId.mDocumentId); 35135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono if (storageDocumentIds.length == 1) { 35235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono final String newDocumentId = 35335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono createDocument(storageDocumentIds[0], mimeType, displayName); 35435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono notifyChildDocumentsChange(parentDocumentId); 35535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono return newDocumentId; 35635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono } else { 35735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono throw new UnsupportedOperationException( 35835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono "Cannot create a file under the device."); 35935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono } 36035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE: 36135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono storageId = parentId.mStorageId; 36235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono parentObjectHandle = -1; 36335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono break; 36435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT: 36535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono storageId = parentId.mStorageId; 36635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono parentObjectHandle = parentId.mObjectHandle; 36735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono break; 36835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono default: 36935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono throw new IllegalArgumentException("Unexpected document type."); 37035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono } 37135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono 372fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe = ParcelFileDescriptor.createReliablePipe(); 373fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono int objectHandle = -1; 374fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MtpObjectInfo info = null; 375fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono try { 376fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe[0].close(); // 0 bytes for a new document. 377fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono 378fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ? 379fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MtpConstants.FORMAT_ASSOCIATION : 380fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MediaFile.getFormatCode(displayName, mimeType); 381fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono info = new MtpObjectInfo.Builder() 38235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono .setStorageId(storageId) 38335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono .setParent(parentObjectHandle) 384fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setFormat(formatCode) 385fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setName(displayName) 386fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .build(); 387fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono 388fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String[] parts = FileUtils.splitFileName(mimeType, displayName); 389fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String baseName = parts[0]; 390fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String extension = parts[1]; 391fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono for (int i = 0; i <= 32; i++) { 392fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final MtpObjectInfo infoUniqueName; 393fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono if (i == 0) { 394fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono infoUniqueName = info; 395fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } else { 3964f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono String suffixedName = baseName + " (" + i + " )"; 3974f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono if (!extension.isEmpty()) { 3984f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono suffixedName += "." + extension; 3994f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono } 4004f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono infoUniqueName = 4014f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono new MtpObjectInfo.Builder(info).setName(suffixedName).build(); 402fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 403fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono try { 404fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono objectHandle = mMtpManager.createDocument( 405fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono parentId.mDeviceId, infoUniqueName, pipe[1]); 406fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono break; 407fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } catch (SendObjectInfoFailure exp) { 408fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono // This can be caused when we have an existing file with the same name. 409fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono continue; 410fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 411fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 412fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } finally { 413fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe[1].close(); 414fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 415fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono if (objectHandle == -1) { 416fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new IllegalArgumentException( 417fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "The file name \"" + displayName + "\" is conflicted with existing files " + 418fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "and the provider failed to find unique name."); 4190f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono } 4209e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo infoWithHandle = 4219e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build(); 4229e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final String documentId = mDatabase.putNewDocument( 42361ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono parentId.mDeviceId, parentDocumentId, record.operationsSupported, 42464111e08d905525c7f4fe27e69953eb71bd62511Daichi Hirono infoWithHandle, 0l); 42576be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono getDocumentLoader(parentId).cancelTask(parentId); 42687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski notifyChildDocumentsChange(parentDocumentId); 42787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski return documentId; 428fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } catch (FileNotFoundException | RuntimeException error) { 429fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono Log.e(TAG, "createDocument", error); 430fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw error; 43187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } catch (IOException error) { 4326a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(TAG, "createDocument", error); 433fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new IllegalStateException(error); 43487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 43587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 43687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski 437b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono @Override 438b690b4de06385a821aed3442e10058986c03badcGarfield Tan public Path findDocumentPath(String parentDocumentId, String childDocumentId) 439b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono throws FileNotFoundException { 440b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final LinkedList<String> ids = new LinkedList<>(); 441b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final Identifier childIdentifier = mDatabase.createIdentifier(childDocumentId); 442b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono 443b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono Identifier i = childIdentifier; 444b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono outer: while (true) { 445b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono if (i.mDocumentId.equals(parentDocumentId)) { 446b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 447b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break; 448b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 449b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono switch (i.mDocumentType) { 450b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT: 451b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 452b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono i = mDatabase.getParentIdentifier(i.mDocumentId); 453b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break; 454b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE: { 455b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono // Check if there is the multiple storage. 456b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final Identifier deviceIdentifier = 457b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono mDatabase.getParentIdentifier(i.mDocumentId); 458b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final String[] storageIds = 459b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono mDatabase.getStorageDocumentIds(deviceIdentifier.mDocumentId); 460b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono // Add storage's document ID to the path only when the device has multiple 461b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono // storages. 462b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono if (storageIds.length > 1) { 463b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 464b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break outer; 465b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 466b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono i = deviceIdentifier; 467b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break; 468b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 469b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE: 470b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 471b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break outer; 472b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 473b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 474b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono 475b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono if (parentDocumentId != null) { 476b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono return new Path(null, ids); 477b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } else { 478b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono return new Path(/* Should be same with root ID */ i.mDocumentId, ids); 479b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 480b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 481b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono 48229de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono @Override 48329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono public boolean isChildDocument(String parentDocumentId, String documentId) { 48429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono try { 48529de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono Identifier identifier = mDatabase.createIdentifier(documentId); 48629de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono while (true) { 48729de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono if (parentDocumentId.equals(identifier.mDocumentId)) { 48829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono return true; 48929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } 49029de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono if (identifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) { 49129de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono return false; 49229de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } 49329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono identifier = mDatabase.getParentIdentifier(identifier.mDocumentId); 49429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } 49529de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } catch (FileNotFoundException error) { 49629de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono return false; 49729de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } 49829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } 49929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono 5002efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono void openDevice(int deviceId) throws IOException { 501e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 502fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono if (mDeviceToolkits.containsKey(deviceId)) { 503fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono return; 504fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 50519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 50619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "Open device " + deviceId); 50719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 5080f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord device = mMtpManager.openDevice(deviceId); 5094e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono final DeviceToolkit toolkit = 51061ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono new DeviceToolkit(mMtpManager, mResolver, mDatabase, device); 5114e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono mDeviceToolkits.put(deviceId, toolkit); 512203be491ef479deea1f39f8b63f3e1916501a37aDaichi Hirono mIntentSender.sendUpdateNotificationIntent(getOpenedDeviceRecordsCache()); 513fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono try { 514fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mRootScanner.resume().await(); 515fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } catch (InterruptedException error) { 516fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono Log.e(TAG, "openDevice", error); 517fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 5184e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono // Resume document loader to remap disconnected document ID. Must be invoked after the 5194e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono // root scanner resumes. 5204e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono toolkit.mDocumentLoader.resume(); 521e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 522d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 523d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 524e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono void closeDevice(int deviceId) throws IOException, InterruptedException { 525e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 526e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(deviceId); 527203be491ef479deea1f39f8b63f3e1916501a37aDaichi Hirono mIntentSender.sendUpdateNotificationIntent(getOpenedDeviceRecordsCache()); 528e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 52920754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono mRootScanner.resume(); 530d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 531d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 5320f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono MtpDeviceRecord[] getOpenedDeviceRecordsCache() { 533e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 5340f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord[] records = new MtpDeviceRecord[mDeviceToolkits.size()]; 5350f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono int i = 0; 5360f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 5370f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono records[i] = toolkit.mDeviceRecord; 5380f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono i++; 53920754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 5400f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono return records; 541e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 54250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 54350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono 544e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono /** 5451e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * Obtains document ID for the given device ID. 5461e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @param deviceId 5471e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @return document ID 5481e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @throws FileNotFoundException device ID has not been build. 5491e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono */ 5501e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono public String getDeviceDocumentId(int deviceId) throws FileNotFoundException { 5511e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono return mDatabase.getDeviceDocumentId(deviceId); 5521e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono } 5531e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono 5541e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono /** 555fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono * Resumes root scanner to handle the update of device list. 556fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono */ 557fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono void resumeRootScanner() { 558ebd24051599280443435606cab220de33b9356adDaichi Hirono if (DEBUG) { 559ebd24051599280443435606cab220de33b9356adDaichi Hirono Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner"); 560ebd24051599280443435606cab220de33b9356adDaichi Hirono } 561fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mRootScanner.resume(); 562fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 563fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono 564fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono /** 565e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono * Finalize the content provider for unit tests. 566e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono */ 567e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono @Override 568e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono public void shutdown() { 569e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 570e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono try { 5710f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono // Copy the opened key set because it will be modified when closing devices. 5720f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final Integer[] keySet = 5730f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono mDeviceToolkits.keySet().toArray(new Integer[mDeviceToolkits.size()]); 5740f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono for (final int id : keySet) { 575e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(id); 576e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 5772e9a57b0d4839b322787dad36bfcb64f211f7ac4Daichi Hirono mRootScanner.pause(); 578acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hirono } catch (InterruptedException | IOException | TimeoutException e) { 579e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // It should fail unit tests by throwing runtime exception. 580e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono throw new RuntimeException(e); 581e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } finally { 582e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.close(); 583e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono super.shutdown(); 584e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 585e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 586e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 587e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono 5885fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono private void notifyChildDocumentsChange(String parentDocumentId) { 5895fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono mResolver.notifyChange( 5905fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId), 5915fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono null, 5925fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono false); 5935fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono } 5944c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 595e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono /** 596be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono * Clears MTP identifier in the database. 597e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono */ 598e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void resume() { 599e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 600e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.getMapper().clearMapping(); 601e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 602e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 603e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 604e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException { 605e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // TODO: Flush the device before closing (if not closed externally). 606fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono if (!mDeviceToolkits.containsKey(deviceId)) { 607fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono return; 608fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 60919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 61019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "Close device " + deviceId); 61119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 61224ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono getDeviceToolkit(deviceId).close(); 613e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDeviceToolkits.remove(deviceId); 614e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mMtpManager.closeDevice(deviceId); 615e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 616e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 6174c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException { 618e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 619e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId); 620e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono if (toolkit == null) { 621e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono throw new FileNotFoundException(); 622e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 623e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono return toolkit; 6244c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 6254c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 6264c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 6274c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException { 6284c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mPipeManager; 6294c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 6304c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 6314c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException { 6324c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader; 6334c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 6344c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 635f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private long getFileSize(String documentId) throws FileNotFoundException { 636f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Cursor cursor = mDatabase.queryDocument( 637f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono documentId, 638f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME)); 639f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono try { 640f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono if (cursor.moveToNext()) { 64177a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono if (cursor.isNull(0)) { 64277a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono throw new UnsupportedOperationException(); 64377a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono } 644f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return cursor.getLong(0); 645f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } else { 646f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono throw new FileNotFoundException(); 647f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 648f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } finally { 649f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono cursor.close(); 650f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 651f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 652f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 6532965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono /** 6542965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * Creates empty cursor with specific error message. 6552965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * 6562965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @param projection Column names. 6572965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @param stringResId String resource ID of error message. 6582965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @return Empty cursor with error message. 6592965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono */ 6602965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono private Cursor createErrorCursor(String[] projection, int stringResId) { 6612965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final Bundle bundle = new Bundle(); 6622965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId)); 6632965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final Cursor cursor = new MatrixCursor(projection); 6642965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono cursor.setExtras(bundle); 6652965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return cursor; 6662965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono } 6672965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 66824ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono private static class DeviceToolkit implements AutoCloseable { 6694c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final PipeManager mPipeManager; 6704c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final DocumentLoader mDocumentLoader; 6710f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono public final MtpDeviceRecord mDeviceRecord; 6724c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 67361ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono public DeviceToolkit(MtpManager manager, 67461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono ContentResolver resolver, 67561ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono MtpDatabase database, 67661ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono MtpDeviceRecord record) { 677f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono mPipeManager = new PipeManager(database); 67861ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono mDocumentLoader = new DocumentLoader(record, manager, resolver, database); 6790f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono mDeviceRecord = record; 6804c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 68124ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono 68224ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono @Override 68324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono public void close() throws InterruptedException { 68424ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono mPipeManager.close(); 68524ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono mDocumentLoader.close(); 68624ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono } 6874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 688f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 689e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono private class MtpProxyFileDescriptorCallback extends ProxyFileDescriptorCallback { 690e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono private final int mInode; 691e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono private MtpFileWriter mWriter; 692f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono 693e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono MtpProxyFileDescriptorCallback(int inode) { 694e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mInode = inode; 695f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 696f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono 697f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 698e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono public long onGetSize() throws ErrnoException { 699e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono try { 700e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return getFileSize(String.valueOf(mInode)); 701e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } catch (FileNotFoundException e) { 702e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono Log.e(TAG, e.getMessage(), e); 703e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono throw new ErrnoException("onGetSize", OsConstants.ENOENT); 70477a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono } 705e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 70677a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono 707e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono @Override 708e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono public int onRead(long offset, int size, byte[] data) throws ErrnoException { 709e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono try { 710e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(Integer.toString(mInode)); 711e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; 712e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono if (MtpDeviceRecord.isSupported( 713e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) { 714e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono 715e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return (int) mMtpManager.getPartialObject64( 716e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono identifier.mDeviceId, identifier.mObjectHandle, offset, size, data); 71777a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono 718e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 719e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported( 720e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) { 721e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return (int) mMtpManager.getPartialObject( 722e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono identifier.mDeviceId, identifier.mObjectHandle, offset, size, data); 723e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 724e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono throw new ErrnoException("onRead", OsConstants.ENOTSUP); 725e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } catch (IOException e) { 726e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono Log.e(TAG, e.getMessage(), e); 727e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono throw new ErrnoException("onRead", OsConstants.EIO); 728e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 729f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 730f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 731f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 732e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono public int onWrite(long offset, int size, byte[] data) throws ErrnoException { 733e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono try { 734e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono if (mWriter == null) { 735e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mWriter = new MtpFileWriter(mContext, String.valueOf(mInode)); 736e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 737e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return mWriter.write(offset, size, data); 738e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } catch (IOException e) { 739e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono Log.e(TAG, e.getMessage(), e); 740e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono throw new ErrnoException("onWrite", OsConstants.EIO); 741f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 742f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 74309ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono 74409ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono @Override 745e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono public void onFsync() throws ErrnoException { 746e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono tryFsync(); 747f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 748f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono 749f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono @Override 750e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono public void onRelease() { 751f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono try { 752e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono tryFsync(); 753e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } catch (ErrnoException error) { 754e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono // Cannot recover from the error at onRelease. Client app should use fsync to 755e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono // ensure the provider writes data correctly. 756e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono Log.e(TAG, "Cannot recover from the error at onRelease.", error); 757f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } finally { 758e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono if (mWriter != null) { 759e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono IoUtils.closeQuietly(mWriter); 760e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 761e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 762e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 763e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono 764e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono private void tryFsync() throws ErrnoException { 765e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono try { 766e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono if (mWriter != null) { 767e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono final MtpDeviceRecord device = 768e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono getDeviceToolkit(mDatabase.createIdentifier( 769e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mWriter.getDocumentId()).mDeviceId).mDeviceRecord; 770e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mWriter.flush(mMtpManager, mDatabase, device.operationsSupported); 771e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 772e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } catch (IOException e) { 773e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono Log.e(TAG, e.getMessage(), e); 774e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono throw new ErrnoException("onWrite", OsConstants.EIO); 775f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 77609ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono } 777f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 778c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono} 779