MtpDocumentsProvider.java revision 50d17aa871d9ca645a8e7af64df8866b85aee245
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; 20c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor; 2150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hironoimport android.database.MatrixCursor; 22c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal; 23c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor; 24d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.provider.DocumentsContract; 25c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document; 26c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root; 27c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider; 28d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log; 29d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 30d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting; 31d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 32c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException; 33d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException; 34c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 35d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/** 36d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices. 37d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */ 38c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider { 392efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String AUTHORITY = "com.android.mtp.documents"; 402efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String TAG = "MtpDocumentsProvider"; 41c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 42c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, 43c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, 44c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_AVAILABLE_BYTES, 45c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 46c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 47c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, 48c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, 49c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 50c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 51c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 522efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private static MtpDocumentsProvider sSingleton; 532efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 542efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private MtpManager mMtpManager; 55d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono private ContentResolver mResolver; 56d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 572efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono /** 582efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono * Provides singleton instance to MtpDocumentsService. 592efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono */ 602efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static MtpDocumentsProvider getInstance() { 612efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono return sSingleton; 622efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono } 632efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 64c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 65c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public boolean onCreate() { 662efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono sSingleton = this; 672efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager = new MtpManager(getContext()); 68d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono mResolver = getContext().getContentResolver(); 69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono return true; 70c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 71c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 72d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono @VisibleForTesting 732efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono void onCreateForTesting(MtpManager mtpManager, ContentResolver resolver) { 742efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono this.mMtpManager = mtpManager; 75d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono this.mResolver = resolver; 76d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 77d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 78c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 79c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryRoots(String[] projection) throws FileNotFoundException { 8050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono if (projection == null) { 8150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; 8250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 8350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono final MatrixCursor cursor = new MatrixCursor(projection); 8450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono for (final int deviceId : mMtpManager.getOpenedDeviceIds()) { 8550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono try { 8650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono final MtpRoot[] roots = mMtpManager.getRoots(deviceId); 8750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono // TODO: Add retry logic here. 8850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono 8950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono for (final MtpRoot root : roots) { 9050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono final String rootId = Identifier.createRootId(deviceId, root.mStorageId); 9150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono final MatrixCursor.RowBuilder builder = cursor.newRow(); 9250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono builder.add(Root.COLUMN_ROOT_ID, rootId); 9350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD); 9450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono builder.add(Root.COLUMN_TITLE, root.mDescription); 9550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono builder.add( 9650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono Root.COLUMN_DOCUMENT_ID, 9750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono Identifier.createDocumentId(rootId, MtpDocument.DUMMY_HANDLE_FOR_ROOT)); 9850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono builder.add(Root.COLUMN_AVAILABLE_BYTES , root.mFreeSpace); 9950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 10050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } catch (IOException error) { 10150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono Log.d(TAG, error.getMessage()); 10250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 10350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 10450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono cursor.setNotificationUri( 10550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); 10650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono return cursor; 107c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 108c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 109c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 110c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryDocument(String documentId, String[] projection) 111c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono throws FileNotFoundException { 112d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono throw new FileNotFoundException(); 113c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 114c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 115c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 116c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryChildDocuments(String parentDocumentId, 117c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono String[] projection, String sortOrder) 118c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono throws FileNotFoundException { 119d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono throw new FileNotFoundException(); 120c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 121c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 122c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 123c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public ParcelFileDescriptor openDocument(String documentId, String mode, 124c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono CancellationSignal signal) throws FileNotFoundException { 125d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono throw new FileNotFoundException(); 126d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 127d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 1282efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono void openDevice(int deviceId) throws IOException { 1292efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager.openDevice(deviceId); 13050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono notifyRootsChange(); 131d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 132d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 1332efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono void closeDevice(int deviceId) throws IOException { 1342efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager.closeDevice(deviceId); 13550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono notifyRootsChange(); 136d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 137d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 138d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono void closeAllDevices() { 139d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono boolean closed = false; 1402efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono for (int deviceId : mMtpManager.getOpenedDeviceIds()) { 141d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono try { 1422efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager.closeDevice(deviceId); 143d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono closed = true; 144d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } catch (IOException d) { 145d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono Log.d(TAG, "Failed to close the MTP device: " + deviceId); 146d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 147d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 148d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono if (closed) { 14950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono notifyRootsChange(); 150d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 151d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 152d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 15350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono boolean hasOpenedDevices() { 15450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono return mMtpManager.getOpenedDeviceIds().length != 0; 15550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 15650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono 15750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono private void notifyRootsChange() { 158d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono mResolver.notifyChange( 159d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY), 160d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono null, 161d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono false); 162c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 163c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono} 164