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; 200f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hironoimport android.content.ContentValues; 21f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.content.Context; 223bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.content.UriPermission; 233faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.content.res.AssetFileDescriptor; 2417c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hironoimport android.content.res.Resources; 25c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor; 260f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hironoimport android.database.DatabaseUtils; 27c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.database.MatrixCursor; 285884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hironoimport android.database.sqlite.SQLiteDiskIOException; 293faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point; 309e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile; 319e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants; 32bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo; 333bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.net.Uri; 34c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.os.Bundle; 35c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal; 36fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hironoimport android.os.FileUtils; 37c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor; 38e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport android.os.ProxyFileDescriptorCallback; 39f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager; 40c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document; 41b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport android.provider.DocumentsContract.Path; 42c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root; 43bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract; 44c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider; 453bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.provider.Settings; 46f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.system.ErrnoException; 47e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport android.system.OsConstants; 48d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log; 49d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 50e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy; 51d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting; 52d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 53c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException; 54d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException; 554c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap; 56b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport java.util.LinkedList; 573bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport java.util.List; 584c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map; 59acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hironoimport java.util.concurrent.TimeoutException; 60e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport libcore.io.IoUtils; 61e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono 62d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/** 63d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices. 64d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */ 65c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider { 662efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String AUTHORITY = "com.android.mtp.documents"; 672efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static final String TAG = "MtpDocumentsProvider"; 686baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, 70c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, 71c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Root.COLUMN_AVAILABLE_BYTES, 72c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 736baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 74c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, 75c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, 76c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 77c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono }; 78c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 79f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono static final boolean DEBUG = false; 8019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono 81e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private final Object mDeviceListLock = new Object(); 82e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private static MtpDocumentsProvider sSingleton; 842efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 852efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono private MtpManager mMtpManager; 86d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono private ContentResolver mResolver; 87e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono @GuardedBy("mDeviceListLock") 884c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private Map<Integer, DeviceToolkit> mDeviceToolkits; 898b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono private RootScanner mRootScanner; 9017c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono private Resources mResources; 91dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono private MtpDatabase mDatabase; 92fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono private ServiceIntentSender mIntentSender; 93f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono private Context mContext; 94e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono private StorageManager mStorageManager; 95d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 962efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono /** 972efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono * Provides singleton instance to MtpDocumentsService. 982efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono */ 992efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono static MtpDocumentsProvider getInstance() { 1002efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono return sSingleton; 1012efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono } 1022efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono 103c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 104c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public boolean onCreate() { 1052efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono sSingleton = this; 106f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono mContext = getContext(); 10717c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = getContext().getResources(); 1082efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono mMtpManager = new MtpManager(getContext()); 109d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono mResolver = getContext().getContentResolver(); 1104c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 11147eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); 112f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); 113fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender = new ServiceIntentSender(getContext()); 114e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mStorageManager = getContext().getSystemService(StorageManager.class); 1153bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 1163bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider 1173bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono // after booting. 1185884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono try { 1195884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1); 1205884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final int lastBootCount = mDatabase.getLastBootCount(); 1215884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono if (bootCount != -1 && bootCount != lastBootCount) { 1225884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mDatabase.setLastBootCount(bootCount); 1235884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final List<UriPermission> permissions = 1245884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mResolver.getOutgoingPersistedUriPermissions(); 1255884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono final Uri[] uris = new Uri[permissions.size()]; 1265884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono for (int i = 0; i < permissions.size(); i++) { 1275884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono uris[i] = permissions.get(i).getUri(); 1285884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono } 1295884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono mDatabase.cleanDatabase(uris); 1303bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono } 1315884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono } catch (SQLiteDiskIOException error) { 1325884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono // It can happen due to disk shortage. 1335884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono Log.e(TAG, "Failed to clean database.", error); 1345884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono return false; 1353bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono } 1363bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 137e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 138c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono return true; 139c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 140c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 141d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono @VisibleForTesting 142b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono boolean onCreateForTesting( 143f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono Context context, 144dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono Resources resources, 145dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono MtpManager mtpManager, 146dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono ContentResolver resolver, 147b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono MtpDatabase database, 148fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono StorageManager storageManager, 149fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono ServiceIntentSender intentSender) { 150f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono mContext = context; 15117c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono mResources = resources; 1526baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mMtpManager = mtpManager; 1536baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono mResolver = resolver; 1544c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); 155dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono mDatabase = database; 156f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); 157fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mIntentSender = intentSender; 158e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mStorageManager = storageManager; 1593bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono 160e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono resume(); 161b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono return true; 162d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 163d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 164c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 165c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryRoots(String[] projection) throws FileNotFoundException { 16650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono if (projection == null) { 16750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; 16850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 169f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono final Cursor cursor = mDatabase.queryRoots(mResources, projection); 17050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono cursor.setNotificationUri( 17150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); 17250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono return cursor; 173c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 174c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 175c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 176c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono public Cursor queryDocument(String documentId, String[] projection) 177c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono throws FileNotFoundException { 178e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono if (projection == null) { 179e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 180e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono } 1810f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono final Cursor cursor = mDatabase.queryDocument(documentId, projection); 1820f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono final int cursorCount = cursor.getCount(); 1830f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono if (cursorCount == 0) { 1840f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono cursor.close(); 1850f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono throw new FileNotFoundException(); 1860f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono } else if (cursorCount != 1) { 1870f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono cursor.close(); 1880f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono Log.wtf(TAG, "Unexpected cursor size: " + cursorCount); 1890f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono return null; 1900f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono } 1910f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono 1920f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 1930f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono if (identifier.mDocumentType != MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) { 1940f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono return cursor; 1950f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono } 1960f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono final String[] storageDocIds = mDatabase.getStorageDocumentIds(documentId); 1970f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono if (storageDocIds.length != 1) { 1980f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono return mDatabase.queryDocument(documentId, projection); 1990f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono } 2000f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono 2010f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono // If the documentId specifies a device having exact one storage, we repalce some device 2020f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono // attributes with the storage attributes. 2030f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono try { 2040f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono final String storageName; 2050f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono final int storageFlags; 2060f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono try (final Cursor storageCursor = mDatabase.queryDocument( 2070f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono storageDocIds[0], 2080f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono MtpDatabase.strings(Document.COLUMN_DISPLAY_NAME, Document.COLUMN_FLAGS))) { 2090f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono if (!storageCursor.moveToNext()) { 2100f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono throw new FileNotFoundException(); 2110f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono } 2120f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono storageName = storageCursor.getString(0); 2130f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono storageFlags = storageCursor.getInt(1); 2140f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono } 2150f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono 2160f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono cursor.moveToNext(); 2170f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono final ContentValues values = new ContentValues(); 2180f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono DatabaseUtils.cursorRowToContentValues(cursor, values); 2190f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono if (values.containsKey(Document.COLUMN_DISPLAY_NAME)) { 2200f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono values.put(Document.COLUMN_DISPLAY_NAME, mResources.getString( 2210f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono R.string.root_name, 2220f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono values.getAsString(Document.COLUMN_DISPLAY_NAME), 2230f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono storageName)); 2240f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono } 2250f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono values.put(Document.COLUMN_FLAGS, storageFlags); 2260f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono final MatrixCursor output = new MatrixCursor(projection, 1); 2270f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono MtpDatabase.putValuesToCursor(values, output); 2280f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono return output; 2290f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono } finally { 2300f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono cursor.close(); 2310f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono } 232c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 233c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 234c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 235124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono public Cursor queryChildDocuments(String parentDocumentId, 236124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono String[] projection, String sortOrder) throws FileNotFoundException { 23719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 23819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "queryChildDocuments: " + parentDocumentId); 23919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 240124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono if (projection == null) { 241124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; 242124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 2436a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId); 244124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono try { 245fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(parentIdentifier.mDeviceId); 2466a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) { 2472965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId); 2482965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono if (storageDocIds.length == 0) { 2492965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // Remote device does not provide storages. Maybe it is locked. 2502965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return createErrorCursor(projection, R.string.error_locked_device); 2512965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono } else if (storageDocIds.length > 1) { 2526a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // Returns storage list from database. 2536a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono return mDatabase.queryChildDocuments(projection, parentDocumentId); 2546a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 2552965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 2562965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // Exact one storage is found. Skip storage and returns object in the single 2572965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono // storage. 2582965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]); 2596a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 2602965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 2616a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // Returns object list from document loader. 2624c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDocumentLoader(parentIdentifier).queryChildDocuments( 2634c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski projection, parentIdentifier); 264c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono } catch (BusyDeviceException exception) { 2652965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return createErrorCursor(projection, R.string.error_busy_device); 266124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } catch (IOException exception) { 2676a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception); 268124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono throw new FileNotFoundException(exception.getMessage()); 269124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono } 270c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono } 271c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono 272c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono @Override 2738ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono public ParcelFileDescriptor openDocument( 2748ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono String documentId, String mode, CancellationSignal signal) 2758ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono throws FileNotFoundException { 2766213cefbc06170f9463abf40c240322be11047bcDaichi Hirono if (DEBUG) { 2776213cefbc06170f9463abf40c240322be11047bcDaichi Hirono Log.d(TAG, "openDocument: " + documentId); 2786213cefbc06170f9463abf40c240322be11047bcDaichi Hirono } 2799e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 2808ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono try { 281fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 2820f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; 283f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // Turn off MODE_CREATE because openDocument does not allow to create new files. 284f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono final int modeFlag = 285f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE; 286f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) { 287f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono long fileSize; 288f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono try { 289f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono fileSize = getFileSize(documentId); 290f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } catch (UnsupportedOperationException exception) { 291f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono fileSize = -1; 292f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 293f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if (MtpDeviceRecord.isPartialReadSupported( 294f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono device.operationsSupported, fileSize)) { 295e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono 296e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return mStorageManager.openProxyFileDescriptor( 297e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono modeFlag, 298e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId))); 299f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else { 300f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // If getPartialObject{|64} are not supported for the device, returns 301f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // non-seekable pipe FD instead. 302f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono return getPipeManager(identifier).readDocument(mMtpManager, identifier); 303f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 304f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) { 305f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // TODO: Clear the parent document loader task (if exists) and call notify 306f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // when writing is completed. 307f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) { 308e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return mStorageManager.openProxyFileDescriptor( 309e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono modeFlag, 310e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId))); 311f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else { 312b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski throw new UnsupportedOperationException( 313f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono "The device does not support writing operation."); 314f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 315f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } else { 316f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono // TODO: Add support for "rw" mode. 317f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono throw new UnsupportedOperationException("The provider does not support 'rw' mode."); 318b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski } 319f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } catch (FileNotFoundException | RuntimeException error) { 320f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocument", error); 321f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono throw error; 3228ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } catch (IOException error) { 3236a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocument", error); 324f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono throw new IllegalStateException(error); 3258ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono } 326d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 327d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 3283faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 3293faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public AssetFileDescriptor openDocumentThumbnail( 3303faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono String documentId, 3313faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono Point sizeHint, 3323faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono CancellationSignal signal) throws FileNotFoundException { 3339e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 3343faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 335fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 3363faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono return new AssetFileDescriptor( 3374c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski getPipeManager(identifier).readThumbnail(mMtpManager, identifier), 338573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono 0, // Start offset. 3393faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono AssetFileDescriptor.UNKNOWN_LENGTH); 3403faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 3416a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error); 3423faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 3433faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 3443faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 3453faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 3463faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono @Override 3473faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono public void deleteDocument(String documentId) throws FileNotFoundException { 3483faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono try { 3499e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(documentId); 350fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(identifier.mDeviceId); 3516a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId); 3523faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); 3539e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono mDatabase.deleteDocument(documentId); 35476be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono getDocumentLoader(parentIdentifier).cancelTask(parentIdentifier); 3559e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono notifyChildDocumentsChange(parentIdentifier.mDocumentId); 3566a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) { 3576a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // If the parent is storage, the object might be appeared as child of device because 3586a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono // we skip storage when the device has only one storage. 3596a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono final Identifier deviceIdentifier = mDatabase.getParentIdentifier( 3606a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono parentIdentifier.mDocumentId); 3616a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono notifyChildDocumentsChange(deviceIdentifier.mDocumentId); 3626a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono } 3633faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } catch (IOException error) { 3646a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error); 3653faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono throw new FileNotFoundException(error.getMessage()); 3663faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 3673faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono } 3683faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono 3696baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono @Override 3706baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono public void onTrimMemory(int level) { 371e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 372e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 373e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono toolkit.mDocumentLoader.clearCompletedTasks(); 374e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 375e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 3766baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono } 3776baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono 37887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski @Override 37987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski public String createDocument(String parentDocumentId, String mimeType, String displayName) 38087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski throws FileNotFoundException { 3816213cefbc06170f9463abf40c240322be11047bcDaichi Hirono if (DEBUG) { 3826213cefbc06170f9463abf40c240322be11047bcDaichi Hirono Log.d(TAG, "createDocument: " + displayName); 3836213cefbc06170f9463abf40c240322be11047bcDaichi Hirono } 384fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final Identifier parentId; 385fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final MtpDeviceRecord record; 386fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final ParcelFileDescriptor[] pipe; 38787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski try { 388fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono parentId = mDatabase.createIdentifier(parentDocumentId); 389fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono openDevice(parentId.mDeviceId); 390fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono record = getDeviceToolkit(parentId.mDeviceId).mDeviceRecord; 3910f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono if (!MtpDeviceRecord.isWritingSupported(record.operationsSupported)) { 392fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new UnsupportedOperationException( 393fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "Writing operation is not supported by the device."); 394fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 39535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono 39635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono final int parentObjectHandle; 39735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono final int storageId; 39835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono switch (parentId.mDocumentType) { 39935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE: 40035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono final String[] storageDocumentIds = 40135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono mDatabase.getStorageDocumentIds(parentId.mDocumentId); 40235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono if (storageDocumentIds.length == 1) { 40335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono final String newDocumentId = 40435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono createDocument(storageDocumentIds[0], mimeType, displayName); 40535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono notifyChildDocumentsChange(parentDocumentId); 40635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono return newDocumentId; 40735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono } else { 40835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono throw new UnsupportedOperationException( 40935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono "Cannot create a file under the device."); 41035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono } 41135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE: 41235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono storageId = parentId.mStorageId; 41335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono parentObjectHandle = -1; 41435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono break; 41535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT: 41635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono storageId = parentId.mStorageId; 41735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono parentObjectHandle = parentId.mObjectHandle; 41835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono break; 41935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono default: 42035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono throw new IllegalArgumentException("Unexpected document type."); 42135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono } 42235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono 423fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe = ParcelFileDescriptor.createReliablePipe(); 424fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono int objectHandle = -1; 425fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MtpObjectInfo info = null; 426fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono try { 427fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe[0].close(); // 0 bytes for a new document. 428fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono 429fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ? 430fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MtpConstants.FORMAT_ASSOCIATION : 431fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono MediaFile.getFormatCode(displayName, mimeType); 432fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono info = new MtpObjectInfo.Builder() 43335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono .setStorageId(storageId) 43435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono .setParent(parentObjectHandle) 435fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setFormat(formatCode) 436fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .setName(displayName) 437fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono .build(); 438fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono 439fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String[] parts = FileUtils.splitFileName(mimeType, displayName); 440fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String baseName = parts[0]; 441fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final String extension = parts[1]; 442fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono for (int i = 0; i <= 32; i++) { 443fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono final MtpObjectInfo infoUniqueName; 444fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono if (i == 0) { 445fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono infoUniqueName = info; 446fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } else { 4474f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono String suffixedName = baseName + " (" + i + " )"; 4484f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono if (!extension.isEmpty()) { 4494f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono suffixedName += "." + extension; 4504f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono } 4514f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono infoUniqueName = 4524f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono new MtpObjectInfo.Builder(info).setName(suffixedName).build(); 453fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 454fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono try { 455fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono objectHandle = mMtpManager.createDocument( 456fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono parentId.mDeviceId, infoUniqueName, pipe[1]); 457fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono break; 458fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } catch (SendObjectInfoFailure exp) { 459fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono // This can be caused when we have an existing file with the same name. 460fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono continue; 461fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 462fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 463fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } finally { 464fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono pipe[1].close(); 465fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } 466fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono if (objectHandle == -1) { 467fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new IllegalArgumentException( 468fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "The file name \"" + displayName + "\" is conflicted with existing files " + 469fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono "and the provider failed to find unique name."); 4700f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono } 4719e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final MtpObjectInfo infoWithHandle = 4729e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build(); 4739e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono final String documentId = mDatabase.putNewDocument( 47461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono parentId.mDeviceId, parentDocumentId, record.operationsSupported, 47564111e08d905525c7f4fe27e69953eb71bd62511Daichi Hirono infoWithHandle, 0l); 47676be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono getDocumentLoader(parentId).cancelTask(parentId); 47787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski notifyChildDocumentsChange(parentDocumentId); 47887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski return documentId; 479fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono } catch (FileNotFoundException | RuntimeException error) { 480fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono Log.e(TAG, "createDocument", error); 481fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw error; 48287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } catch (IOException error) { 4836a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono Log.e(TAG, "createDocument", error); 484fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono throw new IllegalStateException(error); 48587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 48687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski } 48787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski 488b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono @Override 489b690b4de06385a821aed3442e10058986c03badcGarfield Tan public Path findDocumentPath(String parentDocumentId, String childDocumentId) 490b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono throws FileNotFoundException { 491b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final LinkedList<String> ids = new LinkedList<>(); 492b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final Identifier childIdentifier = mDatabase.createIdentifier(childDocumentId); 493b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono 494b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono Identifier i = childIdentifier; 495b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono outer: while (true) { 496b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono if (i.mDocumentId.equals(parentDocumentId)) { 497b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 498b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break; 499b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 500b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono switch (i.mDocumentType) { 501b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT: 502b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 503b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono i = mDatabase.getParentIdentifier(i.mDocumentId); 504b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break; 505b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE: { 506b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono // Check if there is the multiple storage. 507b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final Identifier deviceIdentifier = 508b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono mDatabase.getParentIdentifier(i.mDocumentId); 509b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono final String[] storageIds = 510b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono mDatabase.getStorageDocumentIds(deviceIdentifier.mDocumentId); 511b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono // Add storage's document ID to the path only when the device has multiple 512b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono // storages. 513b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono if (storageIds.length > 1) { 514b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 515b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break outer; 516b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 517b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono i = deviceIdentifier; 518b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break; 519b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 520b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE: 521b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono ids.addFirst(i.mDocumentId); 522b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono break outer; 523b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 524b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 525b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono 526b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono if (parentDocumentId != null) { 527b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono return new Path(null, ids); 528b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } else { 529b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono return new Path(/* Should be same with root ID */ i.mDocumentId, ids); 530b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 531b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono } 532b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono 53329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono @Override 53429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono public boolean isChildDocument(String parentDocumentId, String documentId) { 53529de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono try { 53629de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono Identifier identifier = mDatabase.createIdentifier(documentId); 53729de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono while (true) { 53829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono if (parentDocumentId.equals(identifier.mDocumentId)) { 53929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono return true; 54029de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } 54129de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono if (identifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) { 54229de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono return false; 54329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } 54429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono identifier = mDatabase.getParentIdentifier(identifier.mDocumentId); 54529de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } 54629de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } catch (FileNotFoundException error) { 54729de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono return false; 54829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } 54929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono } 55029de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono 5512efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono void openDevice(int deviceId) throws IOException { 552e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 553fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono if (mDeviceToolkits.containsKey(deviceId)) { 554fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono return; 555fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 55619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 55719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "Open device " + deviceId); 55819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 5590f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord device = mMtpManager.openDevice(deviceId); 5604e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono final DeviceToolkit toolkit = 56161ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono new DeviceToolkit(mMtpManager, mResolver, mDatabase, device); 5624e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono mDeviceToolkits.put(deviceId, toolkit); 56398f48479e95b04479b5512c587da6bc9fbd73b7eDaichi Hirono mIntentSender.sendUpdateNotificationIntent(getOpenedDeviceRecordsCache()); 564fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono try { 565fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mRootScanner.resume().await(); 566fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } catch (InterruptedException error) { 567fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono Log.e(TAG, "openDevice", error); 568fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 5694e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono // Resume document loader to remap disconnected document ID. Must be invoked after the 5704e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono // root scanner resumes. 5714e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono toolkit.mDocumentLoader.resume(); 572e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 573d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 574d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 575e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono void closeDevice(int deviceId) throws IOException, InterruptedException { 576e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 577e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(deviceId); 57898f48479e95b04479b5512c587da6bc9fbd73b7eDaichi Hirono mIntentSender.sendUpdateNotificationIntent(getOpenedDeviceRecordsCache()); 579e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 58020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono mRootScanner.resume(); 581d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono } 582d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono 5830f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono MtpDeviceRecord[] getOpenedDeviceRecordsCache() { 584e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 5850f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final MtpDeviceRecord[] records = new MtpDeviceRecord[mDeviceToolkits.size()]; 5860f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono int i = 0; 5870f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { 5880f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono records[i] = toolkit.mDeviceRecord; 5890f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono i++; 59020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono } 5910f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono return records; 592e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 59350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono } 59450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono 595e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono /** 5961e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * Obtains document ID for the given device ID. 5971e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @param deviceId 5981e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @return document ID 5991e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono * @throws FileNotFoundException device ID has not been build. 6001e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono */ 6011e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono public String getDeviceDocumentId(int deviceId) throws FileNotFoundException { 6021e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono return mDatabase.getDeviceDocumentId(deviceId); 6031e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono } 6041e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono 6051e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono /** 606fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono * Resumes root scanner to handle the update of device list. 607fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono */ 608fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono void resumeRootScanner() { 609ebd24051599280443435606cab220de33b9356adDaichi Hirono if (DEBUG) { 610ebd24051599280443435606cab220de33b9356adDaichi Hirono Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner"); 611ebd24051599280443435606cab220de33b9356adDaichi Hirono } 612fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono mRootScanner.resume(); 613fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 614fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono 615fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono /** 616e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono * Finalize the content provider for unit tests. 617e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono */ 618e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono @Override 619e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono public void shutdown() { 620e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 621e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono try { 6220f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono // Copy the opened key set because it will be modified when closing devices. 6230f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono final Integer[] keySet = 6240f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono mDeviceToolkits.keySet().toArray(new Integer[mDeviceToolkits.size()]); 6250f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono for (final int id : keySet) { 626e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono closeDeviceInternal(id); 627e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 6282e9a57b0d4839b322787dad36bfcb64f211f7ac4Daichi Hirono mRootScanner.pause(); 629acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hirono } catch (InterruptedException | IOException | TimeoutException e) { 630e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // It should fail unit tests by throwing runtime exception. 631e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono throw new RuntimeException(e); 632e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } finally { 633e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.close(); 634e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono super.shutdown(); 635e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 636e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 637e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 638e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono 6395fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono private void notifyChildDocumentsChange(String parentDocumentId) { 6405fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono mResolver.notifyChange( 6415fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId), 6425fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono null, 6435fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono false); 6445fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono } 6454c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 646e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono /** 647be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono * Clears MTP identifier in the database. 648e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono */ 649e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void resume() { 650e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 651e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDatabase.getMapper().clearMapping(); 652e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 653e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 654e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 655e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException { 656e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono // TODO: Flush the device before closing (if not closed externally). 657fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono if (!mDeviceToolkits.containsKey(deviceId)) { 658fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono return; 659fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono } 66019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono if (DEBUG) { 66119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono Log.d(TAG, "Close device " + deviceId); 66219aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono } 66324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono getDeviceToolkit(deviceId).close(); 664e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mDeviceToolkits.remove(deviceId); 665e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono mMtpManager.closeDevice(deviceId); 666e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono } 667e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono 6684c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException { 669e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono synchronized (mDeviceListLock) { 670e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId); 671e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono if (toolkit == null) { 672e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono throw new FileNotFoundException(); 673e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono } 674e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono return toolkit; 6754c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 6764c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 6774c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 6784c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException { 6794c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mPipeManager; 6804c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 6814c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 6824c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException { 6834c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader; 6844c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 6854c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 686f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono private long getFileSize(String documentId) throws FileNotFoundException { 687f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono final Cursor cursor = mDatabase.queryDocument( 688f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono documentId, 689f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME)); 690f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono try { 691f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono if (cursor.moveToNext()) { 69277a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono if (cursor.isNull(0)) { 69377a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono throw new UnsupportedOperationException(); 69477a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono } 695f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono return cursor.getLong(0); 696f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } else { 697f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono throw new FileNotFoundException(); 698f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 699f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } finally { 700f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono cursor.close(); 701f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 702f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 703f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 7042965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono /** 7052965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * Creates empty cursor with specific error message. 7062965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * 7072965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @param projection Column names. 7082965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @param stringResId String resource ID of error message. 7092965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono * @return Empty cursor with error message. 7102965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono */ 7112965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono private Cursor createErrorCursor(String[] projection, int stringResId) { 7122965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final Bundle bundle = new Bundle(); 7132965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId)); 7142965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono final Cursor cursor = new MatrixCursor(projection); 7152965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono cursor.setExtras(bundle); 7162965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono return cursor; 7172965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono } 7182965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono 71924ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono private static class DeviceToolkit implements AutoCloseable { 7204c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final PipeManager mPipeManager; 7214c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski public final DocumentLoader mDocumentLoader; 7220f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono public final MtpDeviceRecord mDeviceRecord; 7234c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski 72461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono public DeviceToolkit(MtpManager manager, 72561ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono ContentResolver resolver, 72661ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono MtpDatabase database, 72761ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono MtpDeviceRecord record) { 728f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono mPipeManager = new PipeManager(database); 72961ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono mDocumentLoader = new DocumentLoader(record, manager, resolver, database); 7300f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono mDeviceRecord = record; 7314c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 73224ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono 73324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono @Override 73424ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono public void close() throws InterruptedException { 73524ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono mPipeManager.close(); 73624ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono mDocumentLoader.close(); 73724ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono } 7384c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski } 739f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 740e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono private class MtpProxyFileDescriptorCallback extends ProxyFileDescriptorCallback { 741e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono private final int mInode; 742e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono private MtpFileWriter mWriter; 743f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono 744e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono MtpProxyFileDescriptorCallback(int inode) { 745e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mInode = inode; 746f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 747f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono 748f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 749e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono public long onGetSize() throws ErrnoException { 750e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono try { 751e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return getFileSize(String.valueOf(mInode)); 752e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } catch (FileNotFoundException e) { 753e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono Log.e(TAG, e.getMessage(), e); 754e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono throw new ErrnoException("onGetSize", OsConstants.ENOENT); 75577a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono } 756e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 75777a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono 758e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono @Override 759e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono public int onRead(long offset, int size, byte[] data) throws ErrnoException { 760e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono try { 761e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono final Identifier identifier = mDatabase.createIdentifier(Integer.toString(mInode)); 762e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; 763e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono if (MtpDeviceRecord.isSupported( 764e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) { 765e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono 766e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return (int) mMtpManager.getPartialObject64( 767e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono identifier.mDeviceId, identifier.mObjectHandle, offset, size, data); 76877a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono 769e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 770e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported( 771e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) { 772e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return (int) mMtpManager.getPartialObject( 773e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono identifier.mDeviceId, identifier.mObjectHandle, offset, size, data); 774e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 775e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono throw new ErrnoException("onRead", OsConstants.ENOTSUP); 776e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } catch (IOException e) { 777e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono Log.e(TAG, e.getMessage(), e); 778e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono throw new ErrnoException("onRead", OsConstants.EIO); 779e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 780f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 781f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono 782f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono @Override 783e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono public int onWrite(long offset, int size, byte[] data) throws ErrnoException { 784e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono try { 785e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono if (mWriter == null) { 786e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mWriter = new MtpFileWriter(mContext, String.valueOf(mInode)); 787e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 788e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono return mWriter.write(offset, size, data); 789e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } catch (IOException e) { 790e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono Log.e(TAG, e.getMessage(), e); 791e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono throw new ErrnoException("onWrite", OsConstants.EIO); 792f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 793f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 79409ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono 79509ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono @Override 796e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono public void onFsync() throws ErrnoException { 797e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono tryFsync(); 798f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 799f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono 800f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono @Override 801e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono public void onRelease() { 802f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono try { 803e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono tryFsync(); 804e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } catch (ErrnoException error) { 805e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono // Cannot recover from the error at onRelease. Client app should use fsync to 806e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono // ensure the provider writes data correctly. 807e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono Log.e(TAG, "Cannot recover from the error at onRelease.", error); 808f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } finally { 809e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono if (mWriter != null) { 810e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono IoUtils.closeQuietly(mWriter); 811e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 812e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 813e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 814e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono 815e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono private void tryFsync() throws ErrnoException { 816e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono try { 817e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono if (mWriter != null) { 818e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono final MtpDeviceRecord device = 819e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono getDeviceToolkit(mDatabase.createIdentifier( 820e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mWriter.getDocumentId()).mDeviceId).mDeviceRecord; 821e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono mWriter.flush(mMtpManager, mDatabase, device.operationsSupported); 822e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } 823e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono } catch (IOException e) { 824e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono Log.e(TAG, e.getMessage(), e); 825e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono throw new ErrnoException("onWrite", OsConstants.EIO); 826f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono } 82709ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono } 828f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono } 829c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono} 830