MtpDocumentsProvider.java revision 4e94b8deaa646f176bad9b80d5924ce64142743e
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; 203faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.content.res.AssetFileDescriptor; 2117c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hironoimport android.content.res.Resources; 22c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor; 23c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.database.MatrixCursor; 243faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point; 259e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile; 269e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants; 27bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo; 28c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.os.Bundle; 29c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal; 30c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor; 31f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager; 32c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document; 33c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root; 34bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract; 35c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider; 36d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log; 37d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 38e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy; 39d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting; 40c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport com.android.mtp.exceptions.BusyDeviceException; 41d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 42c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException; 43d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException; 444c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap; 454c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map; 46c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 47d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/** 48d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices. 49d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */ 50c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider { 512efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String AUTHORITY = "com.android.mtp.documents"; 522efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String TAG = "MtpDocumentsProvider"; 536baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 54c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, 55c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, 56c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_AVAILABLE_BYTES, 57c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 586baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 59c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, 60c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, 61c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 62c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 63c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 64f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono static final boolean DEBUG = false; 6519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono 66e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private final Object mDeviceListLock = new Object(); 67e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 682efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private static MtpDocumentsProvider sSingleton; 692efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 702efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private MtpManager mMtpManager; 71d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono private ContentResolver mResolver; 72e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono @GuardedBy("mDeviceListLock") 734c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private Map<Integer, DeviceToolkit> mDeviceToolkits; 748b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono private RootScanner mRootScanner; 7517c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono private Resources mResources; 76dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono private MtpDatabase mDatabase; 77f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private AppFuse mAppFuse; 78fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono private ServiceIntentSender mIntentSender; 79d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 802efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono /** 812efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono * Provides singleton instance to MtpDocumentsService. 822efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono */ 832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static MtpDocumentsProvider getInstance() { 842efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono return sSingleton; 852efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono } 862efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 87c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 88c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public boolean onCreate() { 892efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono sSingleton = this; 9017c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = getContext().getResources(); 912efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager = new MtpManager(getContext()); 92d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono mResolver = getContext().getContentResolver(); 934c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 9447eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); 95f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); 96f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono mAppFuse = new AppFuse(TAG, new AppFuseCallback()); 97fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender = new ServiceIntentSender(getContext()); 98f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Mount AppFuse on demands. 99e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono try { 100e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono mAppFuse.mount(getContext().getSystemService(StorageManager.class)); 101e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono } catch (IOException e) { 102e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono Log.e(TAG, "Failed to start app fuse.", e); 103e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono return false; 104e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono } 105e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 106c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono return true; 107c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 108c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 109d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono @VisibleForTesting 110b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono boolean onCreateForTesting( 111dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono Resources resources, 112dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono MtpManager mtpManager, 113dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono ContentResolver resolver, 114b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono MtpDatabase database, 115fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono StorageManager storageManager, 116fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono ServiceIntentSender intentSender) { 11717c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = resources; 1186baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mMtpManager = mtpManager; 1196baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mResolver = resolver; 1204c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 121dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mDatabase = database; 122f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); 123b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse = new AppFuse(TAG, new AppFuseCallback()); 124fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender = intentSender; 125b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono // TODO: Mount AppFuse on demands. 126b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono try { 127b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse.mount(storageManager); 128b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono } catch (IOException e) { 129b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono Log.e(TAG, "Failed to start app fuse.", e); 130b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return false; 131b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono } 132e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 133b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return true; 134d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 135d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 136c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 137c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryRoots(String[] projection) throws FileNotFoundException { 13850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono if (projection == null) { 13950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; 14050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 141f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono final Cursor cursor = mDatabase.queryRoots(mResources, projection); 14250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono cursor.setNotificationUri( 14350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); 14450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono return cursor; 145c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 146c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 147c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 148c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryDocument(String documentId, String[] projection) 149c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono throws FileNotFoundException { 150e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono if (projection == null) { 151e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 152e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono } 1539e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono return mDatabase.queryDocument(documentId, projection); 154c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 155c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 156c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 157124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono public Cursor queryChildDocuments(String parentDocumentId, 158124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono String[] projection, String sortOrder) throws FileNotFoundException { 15919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 16019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "queryChildDocuments: " + parentDocumentId); 16119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 162124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono if (projection == null) { 163124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 164124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 1656a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId); 166124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono try { 167fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(parentIdentifier.mDeviceId); 1686a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) { 1692965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId); 1702965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono if (storageDocIds.length == 0) { 1712965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // Remote device does not provide storages. Maybe it is locked. 1722965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return createErrorCursor(projection, R.string.error_locked_device); 1732965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono } else if (storageDocIds.length > 1) { 1746a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // Returns storage list from database. 1756a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono return mDatabase.queryChildDocuments(projection, parentDocumentId); 1766a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 1772965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 1782965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // Exact one storage is found. Skip storage and returns object in the single 1792965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // storage. 1802965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]); 1816a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 1822965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 1836a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // Returns object list from document loader. 1844c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDocumentLoader(parentIdentifier).queryChildDocuments( 1854c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski projection, parentIdentifier); 186c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono } catch (BusyDeviceException exception) { 1872965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return createErrorCursor(projection, R.string.error_busy_device); 188124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } catch (IOException exception) { 1896a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception); 190124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono throw new FileNotFoundException(exception.getMessage()); 191124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 192c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 193c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 194c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 1958ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono public ParcelFileDescriptor openDocument( 1968ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono String documentId, String mode, CancellationSignal signal) 1978ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throws FileNotFoundException { 1986213cefbc06170f9463abf40c240322be11047bcDaichi Hirono if (DEBUG) { 1996213cefbc06170f9463abf40c240322be11047bcDaichi Hirono Log.d(TAG, "openDocument: " + documentId); 2006213cefbc06170f9463abf40c240322be11047bcDaichi Hirono } 2019e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2028ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono try { 203fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 204b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski switch (mode) { 205b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski case "r": 206f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final long fileSize = getFileSize(documentId); 207fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono // MTP getPartialObject operation does not support files that are larger than 208fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono // 4GB. Fallback to non-seekable file descriptor. 209f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Use getPartialObject64 for MTP devices that support Android vendor 210f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // extension. 211b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono if (fileSize <= 0xffffffffl) { 212f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return mAppFuse.openFile(Integer.parseInt(documentId)); 213f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } else { 214f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return getPipeManager(identifier).readDocument(mMtpManager, identifier); 215f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 216b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski case "w": 21781d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski // TODO: Clear the parent document loader task (if exists) and call notify 21881d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski // when writing is completed. 2194c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getPipeManager(identifier).writeDocument( 2204c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getContext(), mMtpManager, identifier); 221f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono case "rw": 222f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Add support for "rw" mode. 223b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski throw new UnsupportedOperationException( 224f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono "The provider does not support 'rw' mode."); 225f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono default: 226f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono throw new IllegalArgumentException("Unknown mode for openDocument: " + mode); 227b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski } 2288ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } catch (IOException error) { 2296a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocument", error); 2308ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throw new FileNotFoundException(error.getMessage()); 2318ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } 232d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 233d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 2343faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 2353faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public AssetFileDescriptor openDocumentThumbnail( 2363faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono String documentId, 2373faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono Point sizeHint, 2383faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono CancellationSignal signal) throws FileNotFoundException { 2399e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2403faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 241fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 2423faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono return new AssetFileDescriptor( 2434c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getPipeManager(identifier).readThumbnail(mMtpManager, identifier), 244573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono 0, // Start offset. 2453faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono AssetFileDescriptor.UNKNOWN_LENGTH); 2463faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 2476a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error); 2483faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 2493faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2503faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2513faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 2523faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 2533faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public void deleteDocument(String documentId) throws FileNotFoundException { 2543faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 2559e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 256fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 2576a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId); 2583faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); 2599e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono mDatabase.deleteDocument(documentId); 2604c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getDocumentLoader(parentIdentifier).clearTask(parentIdentifier); 2619e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono notifyChildDocumentsChange(parentIdentifier.mDocumentId); 2626a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) { 2636a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // If the parent is storage, the object might be appeared as child of device because 2646a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // we skip storage when the device has only one storage. 2656a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono final Identifier deviceIdentifier = mDatabase.getParentIdentifier( 2666a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono parentIdentifier.mDocumentId); 2676a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono notifyChildDocumentsChange(deviceIdentifier.mDocumentId); 2686a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 2693faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 2706a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error); 2713faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 2723faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2733faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2743faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 2756baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono @Override 2766baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono public void onTrimMemory(int level) { 277e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 278e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 279e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono toolkit.mDocumentLoader.clearCompletedTasks(); 280e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 281e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 2826baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono } 2836baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono 28487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski @Override 28587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski public String createDocument(String parentDocumentId, String mimeType, String displayName) 28687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throws FileNotFoundException { 2876213cefbc06170f9463abf40c240322be11047bcDaichi Hirono if (DEBUG) { 2886213cefbc06170f9463abf40c240322be11047bcDaichi Hirono Log.d(TAG, "createDocument: " + displayName); 2896213cefbc06170f9463abf40c240322be11047bcDaichi Hirono } 29087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski try { 2919e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier parentId = mDatabase.createIdentifier(parentDocumentId); 292fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(parentId.mDeviceId); 293df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe(); 294df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski pipe[0].close(); // 0 bytes for a new document. 2959e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ? 2969e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono MtpConstants.FORMAT_ASSOCIATION : 2979e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono MediaFile.getFormatCode(displayName, mimeType); 2989e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo info = new MtpObjectInfo.Builder() 2999e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setStorageId(parentId.mStorageId) 3009e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setParent(parentId.mObjectHandle) 3019e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setFormat(formatCode) 3029e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setName(displayName) 3039e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .build(); 3049e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]); 3059e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo infoWithHandle = 3069e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build(); 3079e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final String documentId = mDatabase.putNewDocument( 3089e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono parentId.mDeviceId, parentDocumentId, infoWithHandle); 3094c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getDocumentLoader(parentId).clearTask(parentId); 31087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski notifyChildDocumentsChange(parentDocumentId); 31187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski return documentId; 31287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } catch (IOException error) { 3136a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(TAG, "createDocument", error); 31487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throw new FileNotFoundException(error.getMessage()); 31587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 31687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 31787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski 3182efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono void openDevice(int deviceId) throws IOException { 319e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 320fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono if (mDeviceToolkits.containsKey(deviceId)) { 321fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono return; 322fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 32319aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 32419aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "Open device " + deviceId); 32519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 326e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono mMtpManager.openDevice(deviceId); 3274e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono final DeviceToolkit toolkit = 3284e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono new DeviceToolkit(deviceId, mMtpManager, mResolver, mDatabase); 3294e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono mDeviceToolkits.put(deviceId, toolkit); 330fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender.sendUpdateNotificationIntent(); 331fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono try { 332fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mRootScanner.resume().await(); 333fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } catch (InterruptedException error) { 334fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono Log.e(TAG, "openDevice", error); 335fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 3364e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono // Resume document loader to remap disconnected document ID. Must be invoked after the 3374e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono // root scanner resumes. 3384e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono toolkit.mDocumentLoader.resume(); 339e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 340d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 341d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 342e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono void closeDevice(int deviceId) throws IOException, InterruptedException { 343e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 344e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(deviceId); 345e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 34620754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono mRootScanner.resume(); 347fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender.sendUpdateNotificationIntent(); 348d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 349d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 350a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono int[] getOpenedDeviceIds() { 351e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 352a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono return mMtpManager.getOpenedDeviceIds(); 353a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono } 354a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono } 355a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono 356a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono String getDeviceName(int deviceId) throws IOException { 357a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono synchronized (mDeviceListLock) { 35820754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono for (final MtpDeviceRecord device : mMtpManager.getDevices()) { 35920754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono if (device.deviceId == deviceId) { 36020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono return device.name; 36120754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 36220754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 36320754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono throw new IOException("Not found the device: " + Integer.toString(deviceId)); 364e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 36550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 36650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono 367e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono /** 3681e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * Obtains document ID for the given device ID. 3691e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @param deviceId 3701e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @return document ID 3711e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @throws FileNotFoundException device ID has not been build. 3721e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono */ 3731e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono public String getDeviceDocumentId(int deviceId) throws FileNotFoundException { 3741e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono return mDatabase.getDeviceDocumentId(deviceId); 3751e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono } 3761e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono 3771e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono /** 378fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono * Resumes root scanner to handle the update of device list. 379fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono */ 380fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono void resumeRootScanner() { 381ebd24051599280443435606cab220de33b9356adDaichi Hirono if (DEBUG) { 382ebd24051599280443435606cab220de33b9356adDaichi Hirono Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner"); 383ebd24051599280443435606cab220de33b9356adDaichi Hirono } 384fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mRootScanner.resume(); 385fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 386fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono 387fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono /** 388e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono * Finalize the content provider for unit tests. 389e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono */ 390e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono @Override 391e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono public void shutdown() { 392e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 393e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono try { 394e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono for (final int id : mMtpManager.getOpenedDeviceIds()) { 395e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(id); 396e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 397e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } catch (InterruptedException|IOException e) { 398e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // It should fail unit tests by throwing runtime exception. 399e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono throw new RuntimeException(e); 400e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } finally { 401e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.close(); 402b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse.close(); 403e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono super.shutdown(); 404e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 405e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 406e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 407e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono 4085fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono private void notifyChildDocumentsChange(String parentDocumentId) { 4095fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono mResolver.notifyChange( 4105fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId), 4115fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono null, 4125fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono false); 4135fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono } 4144c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 415e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono /** 416be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono * Clears MTP identifier in the database. 417e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono */ 418e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void resume() { 419e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 420e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.getMapper().clearMapping(); 421e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 422e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 423e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 424e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException { 425e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // TODO: Flush the device before closing (if not closed externally). 426fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono if (!mDeviceToolkits.containsKey(deviceId)) { 427fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono return; 428fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 42919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 43019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "Close device " + deviceId); 43119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 4324e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono getDeviceToolkit(deviceId).mDocumentLoader.close(); 433e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDeviceToolkits.remove(deviceId); 434e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mMtpManager.closeDevice(deviceId); 435a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono if (getOpenedDeviceIds().length == 0) { 436e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mRootScanner.pause(); 437e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 438e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 439e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 4404c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException { 441e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 442e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId); 443e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono if (toolkit == null) { 444e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono throw new FileNotFoundException(); 445e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 446e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono return toolkit; 4474c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 4484c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 4494c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 4504c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException { 4514c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mPipeManager; 4524c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 4534c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 4544c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException { 4554c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader; 4564c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 4574c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 458f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private long getFileSize(String documentId) throws FileNotFoundException { 459f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Cursor cursor = mDatabase.queryDocument( 460f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono documentId, 461f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME)); 462f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono try { 463f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono if (cursor.moveToNext()) { 464f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return cursor.getLong(0); 465f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } else { 466f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono throw new FileNotFoundException(); 467f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 468f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } finally { 469f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono cursor.close(); 470f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 471f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 472f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 4732965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono /** 4742965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * Creates empty cursor with specific error message. 4752965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * 4762965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @param projection Column names. 4772965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @param stringResId String resource ID of error message. 4782965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @return Empty cursor with error message. 4792965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono */ 4802965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono private Cursor createErrorCursor(String[] projection, int stringResId) { 4812965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final Bundle bundle = new Bundle(); 4822965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId)); 4832965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final Cursor cursor = new MatrixCursor(projection); 4842965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono cursor.setExtras(bundle); 4852965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return cursor; 4862965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono } 4872965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 4884c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private static class DeviceToolkit { 4894c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final PipeManager mPipeManager; 4904c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final DocumentLoader mDocumentLoader; 4914c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 4924e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono public DeviceToolkit( 4934e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono int deviceId, MtpManager manager, ContentResolver resolver, MtpDatabase database) { 494f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono mPipeManager = new PipeManager(database); 4954e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono mDocumentLoader = new DocumentLoader(deviceId, manager, resolver, database); 4964c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 4974c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 498f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 499f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private class AppFuseCallback implements AppFuse.Callback { 500f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 5012f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono public long readObjectBytes( 5022f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono int inode, long offset, long size, byte[] buffer) throws IOException { 503f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode)); 5042f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono return mMtpManager.getPartialObject( 5052f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer); 506f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 507f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 508f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 509f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono public long getFileSize(int inode) throws FileNotFoundException { 510f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); 511f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 512f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 513c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono} 514