MtpDocumentsProvider.java revision 20754c5a112e418c11cc88176283db2c4bf2efd6
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; 233faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point; 249e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile; 259e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants; 26bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo; 27c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal; 28c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor; 29c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document; 30c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root; 31bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract; 32c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider; 33d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log; 34d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 35e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy; 36d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting; 37d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 38c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException; 39d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException; 404c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap; 414c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map; 42c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 43d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/** 44d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices. 45d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */ 46c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider { 472efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String AUTHORITY = "com.android.mtp.documents"; 482efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String TAG = "MtpDocumentsProvider"; 496baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 50c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, 51c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, 52c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_AVAILABLE_BYTES, 53c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 546baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 55c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, 56c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, 57c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 58c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 59c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 60e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private final Object mDeviceListLock = new Object(); 61e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 622efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private static MtpDocumentsProvider sSingleton; 632efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 642efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private MtpManager mMtpManager; 65d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono private ContentResolver mResolver; 66e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono @GuardedBy("mDeviceListLock") 674c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private Map<Integer, DeviceToolkit> mDeviceToolkits; 688b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono private RootScanner mRootScanner; 6917c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono private Resources mResources; 70dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono private MtpDatabase mDatabase; 71d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 722efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono /** 732efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono * Provides singleton instance to MtpDocumentsService. 742efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono */ 752efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static MtpDocumentsProvider getInstance() { 762efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono return sSingleton; 772efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono } 782efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 79c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 80c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public boolean onCreate() { 812efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono sSingleton = this; 8217c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = getContext().getResources(); 832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager = new MtpManager(getContext()); 84d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono mResolver = getContext().getContentResolver(); 854c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 8647eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); 87dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); 88e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 89c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono return true; 90c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 91c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 92d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono @VisibleForTesting 93dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono void onCreateForTesting( 94dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono Resources resources, 95dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono MtpManager mtpManager, 96dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono ContentResolver resolver, 97dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono MtpDatabase database) { 9817c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = resources; 996baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mMtpManager = mtpManager; 1006baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mResolver = resolver; 1014c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 102dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mDatabase = database; 103dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); 104e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 105d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 106d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 107c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 108c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryRoots(String[] projection) throws FileNotFoundException { 10950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono if (projection == null) { 11050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; 11150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 112dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono final Cursor cursor = mDatabase.queryRoots(projection); 11350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono cursor.setNotificationUri( 11450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); 11550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono return cursor; 116c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 117c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 118c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 119c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryDocument(String documentId, String[] projection) 120c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono throws FileNotFoundException { 121e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono if (projection == null) { 122e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 123e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono } 1249e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono return mDatabase.queryDocument(documentId, projection); 125c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 126c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 127c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 128124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono public Cursor queryChildDocuments(String parentDocumentId, 129124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono String[] projection, String sortOrder) throws FileNotFoundException { 130124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono if (projection == null) { 131124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 132124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 13347eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono final Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId); 134124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono try { 1354c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDocumentLoader(parentIdentifier).queryChildDocuments( 1364c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski projection, parentIdentifier); 137124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } catch (IOException exception) { 138124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono throw new FileNotFoundException(exception.getMessage()); 139124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 140c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 141c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 142c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 1438ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono public ParcelFileDescriptor openDocument( 1448ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono String documentId, String mode, CancellationSignal signal) 1458ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throws FileNotFoundException { 1469e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 1478ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono try { 148b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski switch (mode) { 149b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski case "r": 1504c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getPipeManager(identifier).readDocument(mMtpManager, identifier); 151b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski case "w": 15281d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski // TODO: Clear the parent document loader task (if exists) and call notify 15381d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski // when writing is completed. 1544c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getPipeManager(identifier).writeDocument( 1554c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getContext(), mMtpManager, identifier); 156b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski default: 157b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski // TODO: Add support for seekable files. 158b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski throw new UnsupportedOperationException( 159b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski "The provider does not support seekable file."); 160b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski } 1618ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } catch (IOException error) { 1628ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throw new FileNotFoundException(error.getMessage()); 1638ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } 164d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 165d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 1663faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 1673faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public AssetFileDescriptor openDocumentThumbnail( 1683faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono String documentId, 1693faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono Point sizeHint, 1703faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono CancellationSignal signal) throws FileNotFoundException { 1719e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 1723faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 1733faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono return new AssetFileDescriptor( 1744c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getPipeManager(identifier).readThumbnail(mMtpManager, identifier), 175573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono 0, // Start offset. 1763faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono AssetFileDescriptor.UNKNOWN_LENGTH); 1773faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 1783faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 1793faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 1803faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 1813faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 1823faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 1833faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public void deleteDocument(String documentId) throws FileNotFoundException { 1843faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 1859e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 1869e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier parentIdentifier = 1879e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono mDatabase.createIdentifier(mDatabase.getParentId(documentId)); 1883faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); 1899e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono mDatabase.deleteDocument(documentId); 1904c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getDocumentLoader(parentIdentifier).clearTask(parentIdentifier); 1919e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono notifyChildDocumentsChange(parentIdentifier.mDocumentId); 1923faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 1933faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 1943faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 1953faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 1963faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 1976baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono @Override 1986baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono public void onTrimMemory(int level) { 199e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 200e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 201e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono toolkit.mDocumentLoader.clearCompletedTasks(); 202e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 203e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 2046baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono } 2056baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono 20687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski @Override 20787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski public String createDocument(String parentDocumentId, String mimeType, String displayName) 20887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throws FileNotFoundException { 20987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski try { 2109e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier parentId = mDatabase.createIdentifier(parentDocumentId); 211df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe(); 212df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski pipe[0].close(); // 0 bytes for a new document. 2139e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ? 2149e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono MtpConstants.FORMAT_ASSOCIATION : 2159e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono MediaFile.getFormatCode(displayName, mimeType); 2169e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo info = new MtpObjectInfo.Builder() 2179e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setStorageId(parentId.mStorageId) 2189e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setParent(parentId.mObjectHandle) 2199e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setFormat(formatCode) 2209e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setName(displayName) 2219e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .build(); 2229e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]); 2239e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo infoWithHandle = 2249e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build(); 2259e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final String documentId = mDatabase.putNewDocument( 2269e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono parentId.mDeviceId, parentDocumentId, infoWithHandle); 2274c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getDocumentLoader(parentId).clearTask(parentId); 22887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski notifyChildDocumentsChange(parentDocumentId); 22987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski return documentId; 23087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } catch (IOException error) { 23187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski Log.e(TAG, error.getMessage()); 23287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throw new FileNotFoundException(error.getMessage()); 23387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 23487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 23587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski 2362efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono void openDevice(int deviceId) throws IOException { 237e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 238e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono mMtpManager.openDevice(deviceId); 239e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDeviceToolkits.put( 240e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase)); 241e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 242e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono mRootScanner.resume(); 243d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 244d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 245e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono void closeDevice(int deviceId) throws IOException, InterruptedException { 246e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 247e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(deviceId); 248e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono mDatabase.removeDeviceRows(deviceId); 249e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 25020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono mRootScanner.resume(); 251d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 252d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 253a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono int[] getOpenedDeviceIds() { 254e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 255a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono return mMtpManager.getOpenedDeviceIds(); 256a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono } 257a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono } 258a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono 259a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono String getDeviceName(int deviceId) throws IOException { 260a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono synchronized (mDeviceListLock) { 26120754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono for (final MtpDeviceRecord device : mMtpManager.getDevices()) { 26220754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono if (device.deviceId == deviceId) { 26320754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono return device.name; 26420754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 26520754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 26620754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono throw new IOException("Not found the device: " + Integer.toString(deviceId)); 267e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 26850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 26950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono 270e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono /** 271e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono * Finalize the content provider for unit tests. 272e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono */ 273e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono @Override 274e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono public void shutdown() { 275e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 276e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono try { 277e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono for (final int id : mMtpManager.getOpenedDeviceIds()) { 278e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(id); 279e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 280e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } catch (InterruptedException|IOException e) { 281e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // It should fail unit tests by throwing runtime exception. 282e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono throw new RuntimeException(e); 283e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } finally { 284e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.close(); 285e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono super.shutdown(); 286e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 287e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 288e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 289e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono 2905fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono private void notifyChildDocumentsChange(String parentDocumentId) { 2915fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono mResolver.notifyChange( 2925fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId), 2935fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono null, 2945fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono false); 2955fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono } 2964c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 297e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono /** 298be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono * Clears MTP identifier in the database. 299e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono */ 300e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void resume() { 301e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 302e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.getMapper().clearMapping(); 303e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 304e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 305e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 306e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException { 307e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // TODO: Flush the device before closing (if not closed externally). 308e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); 309e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDeviceToolkits.remove(deviceId); 310e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mMtpManager.closeDevice(deviceId); 311a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono if (getOpenedDeviceIds().length == 0) { 312e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mRootScanner.pause(); 313e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 314e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 315e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 3164c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException { 317e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 318e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId); 319e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono if (toolkit == null) { 320e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono throw new FileNotFoundException(); 321e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 322e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono return toolkit; 3234c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3244c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3254c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 3264c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException { 3274c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mPipeManager; 3284c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3294c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 3304c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException { 3314c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader; 3324c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3334c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 3344c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private static class DeviceToolkit { 3354c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final PipeManager mPipeManager; 3364c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final DocumentLoader mDocumentLoader; 3374c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 33847eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) { 3394c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mPipeManager = new PipeManager(); 34047eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono mDocumentLoader = new DocumentLoader(manager, resolver, database); 3414c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3424c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 343c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono} 344