MtpDocumentsProvider.java revision 2f310f6d5d352817f42384394b50a660ad6e0bf8
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; 29f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager; 30c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document; 31c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root; 32bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract; 33c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider; 34d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log; 35d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 36e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy; 37d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting; 38d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 39c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException; 40d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException; 414c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap; 424c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map; 43c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 44d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/** 45d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices. 46d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */ 47c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider { 482efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String AUTHORITY = "com.android.mtp.documents"; 492efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String TAG = "MtpDocumentsProvider"; 506baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 51c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, 52c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, 53c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_AVAILABLE_BYTES, 54c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 556baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 56c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, 57c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, 58c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 59c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 60c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 61e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private final Object mDeviceListLock = new Object(); 62e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 632efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private static MtpDocumentsProvider sSingleton; 642efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 652efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private MtpManager mMtpManager; 66d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono private ContentResolver mResolver; 67e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono @GuardedBy("mDeviceListLock") 684c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private Map<Integer, DeviceToolkit> mDeviceToolkits; 698b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono private RootScanner mRootScanner; 7017c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono private Resources mResources; 71dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono private MtpDatabase mDatabase; 72f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private AppFuse mAppFuse; 73d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 742efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono /** 752efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono * Provides singleton instance to MtpDocumentsService. 762efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono */ 772efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static MtpDocumentsProvider getInstance() { 782efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono return sSingleton; 792efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono } 802efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 81c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 82c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public boolean onCreate() { 832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono sSingleton = this; 8417c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = getContext().getResources(); 852efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager = new MtpManager(getContext()); 86d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono mResolver = getContext().getContentResolver(); 874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 8847eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); 89dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); 90f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono mAppFuse = new AppFuse(TAG, new AppFuseCallback()); 91f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Mount AppFuse on demands. 92e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono try { 93e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono mAppFuse.mount(getContext().getSystemService(StorageManager.class)); 94e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono } catch (IOException e) { 95e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono Log.e(TAG, "Failed to start app fuse.", e); 96e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono return false; 97e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono } 98e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 99c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono return true; 100c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 101c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 102d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono @VisibleForTesting 103b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono boolean onCreateForTesting( 104dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono Resources resources, 105dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono MtpManager mtpManager, 106dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono ContentResolver resolver, 107b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono MtpDatabase database, 108b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono StorageManager storageManager) { 10917c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = resources; 1106baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mMtpManager = mtpManager; 1116baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mResolver = resolver; 1124c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 113dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mDatabase = database; 114dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); 115b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse = new AppFuse(TAG, new AppFuseCallback()); 116b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono // TODO: Mount AppFuse on demands. 117b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono try { 118b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse.mount(storageManager); 119b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono } catch (IOException e) { 120b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono Log.e(TAG, "Failed to start app fuse.", e); 121b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return false; 122b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono } 123e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 124b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return true; 125d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 126d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 127c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 128c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryRoots(String[] projection) throws FileNotFoundException { 12950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono if (projection == null) { 13050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; 13150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 132dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono final Cursor cursor = mDatabase.queryRoots(projection); 13350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono cursor.setNotificationUri( 13450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); 13550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono return cursor; 136c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 137c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 138c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 139c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryDocument(String documentId, String[] projection) 140c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono throws FileNotFoundException { 141e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono if (projection == null) { 142e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 143e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono } 1449e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono return mDatabase.queryDocument(documentId, projection); 145c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 146c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 147c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 148124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono public Cursor queryChildDocuments(String parentDocumentId, 149124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono String[] projection, String sortOrder) throws FileNotFoundException { 150124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono if (projection == null) { 151124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 152124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 15347eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono final Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId); 154124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono try { 1554c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDocumentLoader(parentIdentifier).queryChildDocuments( 1564c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski projection, parentIdentifier); 157124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } catch (IOException exception) { 158124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono throw new FileNotFoundException(exception.getMessage()); 159124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 160c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 161c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 162c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 1638ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono public ParcelFileDescriptor openDocument( 1648ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono String documentId, String mode, CancellationSignal signal) 1658ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throws FileNotFoundException { 1669e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 1678ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono try { 168b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski switch (mode) { 169b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski case "r": 170f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final long fileSize = getFileSize(documentId); 171f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // MTP getPartialObject operation does not support files that are larger than 4GB. 172f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // Fallback to non-seekable file descriptor. 173f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Use getPartialObject64 for MTP devices that support Android vendor 174f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // extension. 175b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono if (fileSize <= 0xffffffffl) { 176f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return mAppFuse.openFile(Integer.parseInt(documentId)); 177f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } else { 178f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return getPipeManager(identifier).readDocument(mMtpManager, identifier); 179f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 180b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski case "w": 18181d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski // TODO: Clear the parent document loader task (if exists) and call notify 18281d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski // when writing is completed. 1834c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getPipeManager(identifier).writeDocument( 1844c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getContext(), mMtpManager, identifier); 185f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono case "rw": 186f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Add support for "rw" mode. 187b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski throw new UnsupportedOperationException( 188f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono "The provider does not support 'rw' mode."); 189f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono default: 190f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono throw new IllegalArgumentException("Unknown mode for openDocument: " + mode); 191b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski } 1928ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } catch (IOException error) { 1938ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throw new FileNotFoundException(error.getMessage()); 1948ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } 195d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 196d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 1973faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 1983faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public AssetFileDescriptor openDocumentThumbnail( 1993faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono String documentId, 2003faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono Point sizeHint, 2013faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono CancellationSignal signal) throws FileNotFoundException { 2029e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2033faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 2043faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono return new AssetFileDescriptor( 2054c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getPipeManager(identifier).readThumbnail(mMtpManager, identifier), 206573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono 0, // Start offset. 2073faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono AssetFileDescriptor.UNKNOWN_LENGTH); 2083faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 2093faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 2103faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2113faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2123faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 2133faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 2143faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public void deleteDocument(String documentId) throws FileNotFoundException { 2153faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 2169e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2179e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier parentIdentifier = 2189e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono mDatabase.createIdentifier(mDatabase.getParentId(documentId)); 2193faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); 2209e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono mDatabase.deleteDocument(documentId); 2214c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getDocumentLoader(parentIdentifier).clearTask(parentIdentifier); 2229e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono notifyChildDocumentsChange(parentIdentifier.mDocumentId); 2233faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 2243faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 2253faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2263faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2273faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 2286baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono @Override 2296baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono public void onTrimMemory(int level) { 230e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 231e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 232e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono toolkit.mDocumentLoader.clearCompletedTasks(); 233e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 234e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 2356baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono } 2366baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono 23787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski @Override 23887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski public String createDocument(String parentDocumentId, String mimeType, String displayName) 23987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throws FileNotFoundException { 24087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski try { 2419e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier parentId = mDatabase.createIdentifier(parentDocumentId); 242df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe(); 243df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski pipe[0].close(); // 0 bytes for a new document. 2449e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ? 2459e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono MtpConstants.FORMAT_ASSOCIATION : 2469e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono MediaFile.getFormatCode(displayName, mimeType); 2479e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo info = new MtpObjectInfo.Builder() 2489e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setStorageId(parentId.mStorageId) 2499e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setParent(parentId.mObjectHandle) 2509e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setFormat(formatCode) 2519e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setName(displayName) 2529e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .build(); 2539e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]); 2549e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo infoWithHandle = 2559e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build(); 2569e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final String documentId = mDatabase.putNewDocument( 2579e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono parentId.mDeviceId, parentDocumentId, infoWithHandle); 2584c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getDocumentLoader(parentId).clearTask(parentId); 25987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski notifyChildDocumentsChange(parentDocumentId); 26087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski return documentId; 26187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } catch (IOException error) { 26287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski Log.e(TAG, error.getMessage()); 26387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throw new FileNotFoundException(error.getMessage()); 26487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 26587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 26687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski 2672efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono void openDevice(int deviceId) throws IOException { 268e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 269e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono mMtpManager.openDevice(deviceId); 270e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDeviceToolkits.put( 271e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase)); 272e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 273e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono mRootScanner.resume(); 274d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 275d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 276e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono void closeDevice(int deviceId) throws IOException, InterruptedException { 277e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 278e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(deviceId); 279e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 28020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono mRootScanner.resume(); 281d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 282d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 283a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono int[] getOpenedDeviceIds() { 284e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 285a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono return mMtpManager.getOpenedDeviceIds(); 286a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono } 287a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono } 288a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono 289a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono String getDeviceName(int deviceId) throws IOException { 290a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono synchronized (mDeviceListLock) { 29120754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono for (final MtpDeviceRecord device : mMtpManager.getDevices()) { 29220754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono if (device.deviceId == deviceId) { 29320754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono return device.name; 29420754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 29520754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 29620754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono throw new IOException("Not found the device: " + Integer.toString(deviceId)); 297e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 29850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 29950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono 300e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono /** 301e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono * Finalize the content provider for unit tests. 302e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono */ 303e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono @Override 304e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono public void shutdown() { 305e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 306e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono try { 307e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono for (final int id : mMtpManager.getOpenedDeviceIds()) { 308e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(id); 309e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 310e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } catch (InterruptedException|IOException e) { 311e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // It should fail unit tests by throwing runtime exception. 312e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono throw new RuntimeException(e); 313e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } finally { 314e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.close(); 315b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse.close(); 316e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono super.shutdown(); 317e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 318e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 319e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 320e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono 3215fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono private void notifyChildDocumentsChange(String parentDocumentId) { 3225fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono mResolver.notifyChange( 3235fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId), 3245fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono null, 3255fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono false); 3265fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono } 3274c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 328e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono /** 329be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono * Clears MTP identifier in the database. 330e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono */ 331e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void resume() { 332e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 333e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.getMapper().clearMapping(); 334e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 335e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 336e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 337e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException { 338e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // TODO: Flush the device before closing (if not closed externally). 339e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); 340e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDeviceToolkits.remove(deviceId); 341e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mMtpManager.closeDevice(deviceId); 342a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono if (getOpenedDeviceIds().length == 0) { 343e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mRootScanner.pause(); 344e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 345e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 346e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 3474c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException { 348e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 349e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId); 350e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono if (toolkit == null) { 351e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono throw new FileNotFoundException(); 352e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 353e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono return toolkit; 3544c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3554c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3564c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 3574c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException { 3584c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mPipeManager; 3594c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3604c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 3614c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException { 3624c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader; 3634c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3644c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 365f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private long getFileSize(String documentId) throws FileNotFoundException { 366f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Cursor cursor = mDatabase.queryDocument( 367f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono documentId, 368f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME)); 369f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono try { 370f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono if (cursor.moveToNext()) { 371f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return cursor.getLong(0); 372f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } else { 373f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono throw new FileNotFoundException(); 374f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 375f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } finally { 376f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono cursor.close(); 377f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 378f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 379f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 3804c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private static class DeviceToolkit { 3814c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final PipeManager mPipeManager; 3824c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final DocumentLoader mDocumentLoader; 3834c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 38447eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) { 3854c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mPipeManager = new PipeManager(); 38647eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono mDocumentLoader = new DocumentLoader(manager, resolver, database); 3874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3884c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 389f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 390f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private class AppFuseCallback implements AppFuse.Callback { 391f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 3922f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono public long readObjectBytes( 3932f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono int inode, long offset, long size, byte[] buffer) throws IOException { 394f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode)); 3952f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono return mMtpManager.getPartialObject( 3962f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer); 397f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 398f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 399f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 400f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono public long getFileSize(int inode) throws FileNotFoundException { 401f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); 402f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 403f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 404c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono} 405