MtpDocumentsProvider.java revision b36b15586a5d3d0de590773ce4392fa3a82af66a
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 19f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport static com.android.internal.util.Preconditions.checkArgument; 20f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 21d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.content.ContentResolver; 223faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.content.res.AssetFileDescriptor; 2317c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hironoimport android.content.res.Resources; 24c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor; 253faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point; 269e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile; 279e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants; 28bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo; 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; 40d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 41c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException; 42d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException; 43b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hironoimport java.util.Arrays; 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 64e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private final Object mDeviceListLock = new Object(); 65e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 662efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private static MtpDocumentsProvider sSingleton; 672efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 682efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private MtpManager mMtpManager; 69d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono private ContentResolver mResolver; 70e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono @GuardedBy("mDeviceListLock") 714c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private Map<Integer, DeviceToolkit> mDeviceToolkits; 728b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono private RootScanner mRootScanner; 7317c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono private Resources mResources; 74dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono private MtpDatabase mDatabase; 75f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private AppFuse mAppFuse; 76d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 772efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono /** 782efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono * Provides singleton instance to MtpDocumentsService. 792efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono */ 802efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static MtpDocumentsProvider getInstance() { 812efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono return sSingleton; 822efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono } 832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 84c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 85c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public boolean onCreate() { 862efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono sSingleton = this; 8717c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = getContext().getResources(); 882efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager = new MtpManager(getContext()); 89d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono mResolver = getContext().getContentResolver(); 904c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 9147eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); 92dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); 93f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono mAppFuse = new AppFuse(TAG, new AppFuseCallback()); 94f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Mount AppFuse on demands. 95e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono try { 96e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono mAppFuse.mount(getContext().getSystemService(StorageManager.class)); 97e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono } catch (IOException e) { 98e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono Log.e(TAG, "Failed to start app fuse.", e); 99e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono return false; 100e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono } 101e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 102c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono return true; 103c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 104c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 105d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono @VisibleForTesting 106b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono boolean onCreateForTesting( 107dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono Resources resources, 108dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono MtpManager mtpManager, 109dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono ContentResolver resolver, 110b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono MtpDatabase database, 111b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono StorageManager storageManager) { 11217c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = resources; 1136baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mMtpManager = mtpManager; 1146baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mResolver = resolver; 1154c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 116dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mDatabase = database; 117dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); 118b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse = new AppFuse(TAG, new AppFuseCallback()); 119b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono // TODO: Mount AppFuse on demands. 120b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono try { 121b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse.mount(storageManager); 122b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono } catch (IOException e) { 123b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono Log.e(TAG, "Failed to start app fuse.", e); 124b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return false; 125b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono } 126e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 127b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return true; 128d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 129d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 130c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 131c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryRoots(String[] projection) throws FileNotFoundException { 13250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono if (projection == null) { 13350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; 13450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 135dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono final Cursor cursor = mDatabase.queryRoots(projection); 13650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono cursor.setNotificationUri( 13750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); 13850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono return cursor; 139c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 140c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 141c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 142c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryDocument(String documentId, String[] projection) 143c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono throws FileNotFoundException { 144e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono if (projection == null) { 145e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 146e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono } 1479e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono return mDatabase.queryDocument(documentId, projection); 148c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 149c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 150c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 151124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono public Cursor queryChildDocuments(String parentDocumentId, 152124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono String[] projection, String sortOrder) throws FileNotFoundException { 153124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono if (projection == null) { 154124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 155124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 15647eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono final Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId); 157124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono try { 1584c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDocumentLoader(parentIdentifier).queryChildDocuments( 1594c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski projection, parentIdentifier); 160124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } catch (IOException exception) { 161124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono throw new FileNotFoundException(exception.getMessage()); 162124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 163c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 164c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 165c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 1668ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono public ParcelFileDescriptor openDocument( 1678ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono String documentId, String mode, CancellationSignal signal) 1688ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throws FileNotFoundException { 1699e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 1708ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono try { 171b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski switch (mode) { 172b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski case "r": 173f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final long fileSize = getFileSize(documentId); 174f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // MTP getPartialObject operation does not support files that are larger than 4GB. 175f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // Fallback to non-seekable file descriptor. 176f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Use getPartialObject64 for MTP devices that support Android vendor 177f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // extension. 178b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono if (fileSize <= 0xffffffffl) { 179f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return mAppFuse.openFile(Integer.parseInt(documentId)); 180f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } else { 181f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return getPipeManager(identifier).readDocument(mMtpManager, identifier); 182f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 183b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski case "w": 18481d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski // TODO: Clear the parent document loader task (if exists) and call notify 18581d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski // when writing is completed. 1864c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getPipeManager(identifier).writeDocument( 1874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getContext(), mMtpManager, identifier); 188f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono case "rw": 189f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono // TODO: Add support for "rw" mode. 190b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski throw new UnsupportedOperationException( 191f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono "The provider does not support 'rw' mode."); 192f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono default: 193f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono throw new IllegalArgumentException("Unknown mode for openDocument: " + mode); 194b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski } 1958ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } catch (IOException error) { 1968ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throw new FileNotFoundException(error.getMessage()); 1978ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } 198d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 199d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 2003faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 2013faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public AssetFileDescriptor openDocumentThumbnail( 2023faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono String documentId, 2033faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono Point sizeHint, 2043faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono CancellationSignal signal) throws FileNotFoundException { 2059e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2063faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 2073faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono return new AssetFileDescriptor( 2084c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getPipeManager(identifier).readThumbnail(mMtpManager, identifier), 209573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono 0, // Start offset. 2103faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono AssetFileDescriptor.UNKNOWN_LENGTH); 2113faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 2123faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 2133faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2143faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2153faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 2163faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 2173faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public void deleteDocument(String documentId) throws FileNotFoundException { 2183faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 2199e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2209e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier parentIdentifier = 2219e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono mDatabase.createIdentifier(mDatabase.getParentId(documentId)); 2223faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); 2239e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono mDatabase.deleteDocument(documentId); 2244c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getDocumentLoader(parentIdentifier).clearTask(parentIdentifier); 2259e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono notifyChildDocumentsChange(parentIdentifier.mDocumentId); 2263faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 2273faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 2283faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2293faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 2303faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 2316baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono @Override 2326baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono public void onTrimMemory(int level) { 233e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 234e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 235e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono toolkit.mDocumentLoader.clearCompletedTasks(); 236e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 237e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 2386baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono } 2396baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono 24087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski @Override 24187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski public String createDocument(String parentDocumentId, String mimeType, String displayName) 24287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throws FileNotFoundException { 24387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski try { 2449e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier parentId = mDatabase.createIdentifier(parentDocumentId); 245df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe(); 246df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski pipe[0].close(); // 0 bytes for a new document. 2479e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ? 2489e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono MtpConstants.FORMAT_ASSOCIATION : 2499e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono MediaFile.getFormatCode(displayName, mimeType); 2509e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo info = new MtpObjectInfo.Builder() 2519e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setStorageId(parentId.mStorageId) 2529e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setParent(parentId.mObjectHandle) 2539e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setFormat(formatCode) 2549e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .setName(displayName) 2559e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono .build(); 2569e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]); 2579e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo infoWithHandle = 2589e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build(); 2599e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final String documentId = mDatabase.putNewDocument( 2609e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono parentId.mDeviceId, parentDocumentId, infoWithHandle); 2614c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getDocumentLoader(parentId).clearTask(parentId); 26287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski notifyChildDocumentsChange(parentDocumentId); 26387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski return documentId; 26487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } catch (IOException error) { 26587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski Log.e(TAG, error.getMessage()); 26687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throw new FileNotFoundException(error.getMessage()); 26787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 26887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 26987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski 2702efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono void openDevice(int deviceId) throws IOException { 271e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 272e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono mMtpManager.openDevice(deviceId); 273e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDeviceToolkits.put( 274e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase)); 275e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 276e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono mRootScanner.resume(); 277d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 278d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 279e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono void closeDevice(int deviceId) throws IOException, InterruptedException { 280e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 281e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(deviceId); 282e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 28320754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono mRootScanner.resume(); 284d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 285d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 286a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono int[] getOpenedDeviceIds() { 287e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 288a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono return mMtpManager.getOpenedDeviceIds(); 289a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono } 290a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono } 291a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono 292a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono String getDeviceName(int deviceId) throws IOException { 293a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono synchronized (mDeviceListLock) { 29420754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono for (final MtpDeviceRecord device : mMtpManager.getDevices()) { 29520754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono if (device.deviceId == deviceId) { 29620754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono return device.name; 29720754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 29820754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 29920754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono throw new IOException("Not found the device: " + Integer.toString(deviceId)); 300e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 30150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 30250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono 303e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono /** 304e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono * Finalize the content provider for unit tests. 305e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono */ 306e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono @Override 307e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono public void shutdown() { 308e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 309e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono try { 310e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono for (final int id : mMtpManager.getOpenedDeviceIds()) { 311e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(id); 312e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 313e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } catch (InterruptedException|IOException e) { 314e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // It should fail unit tests by throwing runtime exception. 315e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono throw new RuntimeException(e); 316e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } finally { 317e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.close(); 318b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono mAppFuse.close(); 319e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono super.shutdown(); 320e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 321e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 322e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 323e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono 3245fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono private void notifyChildDocumentsChange(String parentDocumentId) { 3255fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono mResolver.notifyChange( 3265fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId), 3275fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono null, 3285fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono false); 3295fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono } 3304c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 331e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono /** 332be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono * Clears MTP identifier in the database. 333e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono */ 334e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void resume() { 335e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 336e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.getMapper().clearMapping(); 337e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 338e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 339e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 340e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException { 341e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // TODO: Flush the device before closing (if not closed externally). 342e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); 343e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDeviceToolkits.remove(deviceId); 344e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mMtpManager.closeDevice(deviceId); 345a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono if (getOpenedDeviceIds().length == 0) { 346e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mRootScanner.pause(); 347e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 348e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 349e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 3504c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException { 351e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 352e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId); 353e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono if (toolkit == null) { 354e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono throw new FileNotFoundException(); 355e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 356e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono return toolkit; 3574c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3584c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3594c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 3604c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException { 3614c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mPipeManager; 3624c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3634c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 3644c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException { 3654c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader; 3664c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3674c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 368f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private long getFileSize(String documentId) throws FileNotFoundException { 369f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Cursor cursor = mDatabase.queryDocument( 370f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono documentId, 371f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME)); 372f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono try { 373f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono if (cursor.moveToNext()) { 374f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return cursor.getLong(0); 375f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } else { 376f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono throw new FileNotFoundException(); 377f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 378f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } finally { 379f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono cursor.close(); 380f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 381f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 382f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 3834c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private static class DeviceToolkit { 3844c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final PipeManager mPipeManager; 3854c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final DocumentLoader mDocumentLoader; 3864c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 38747eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) { 3884c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mPipeManager = new PipeManager(); 38947eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono mDocumentLoader = new DocumentLoader(manager, resolver, database); 3904c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 3914c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 392f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 393f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private class AppFuseCallback implements AppFuse.Callback { 394f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final byte[] mBytes = new byte[AppFuse.MAX_READ]; 395f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 396f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 397f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono public byte[] getObjectBytes(int inode, long offset, int size) throws IOException { 398f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode)); 399b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono final long readSize = mMtpManager.getPartialObject( 400b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono identifier.mDeviceId, identifier.mObjectHandle, offset, size, mBytes); 401b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono // TODO: Change signature so that getObjectBytes can return read size without copying 402b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono // bytes. 403b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return Arrays.copyOf(mBytes, (int) readSize); 404f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 405f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 406f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 407f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono public long getFileSize(int inode) throws FileNotFoundException { 408f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); 409f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 410f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 411c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono} 412