MtpDocumentsProvider.java revision 4f04fd358d84f7ea8a9a26bf4c2ef4bef553c732
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;
203bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.content.UriPermission;
213faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.content.res.AssetFileDescriptor;
2217c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hironoimport android.content.res.Resources;
23c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor;
24c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.database.MatrixCursor;
255884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hironoimport android.database.sqlite.SQLiteDiskIOException;
263faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point;
279e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile;
289e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants;
29bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo;
303bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.net.Uri;
31c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.os.Bundle;
32c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal;
33fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hironoimport android.os.FileUriExposedException;
34fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hironoimport android.os.FileUtils;
35c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor;
36f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager;
37c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document;
38c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root;
39bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract;
40c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider;
413bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.provider.Settings;
42d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log;
43d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
44e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy;
45d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting;
46d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
47c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException;
48d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException;
494c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap;
503bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport java.util.List;
514c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map;
52acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hironoimport java.util.concurrent.TimeoutException;
53c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
54d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/**
55d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices.
56d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */
57c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider {
582efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String AUTHORITY = "com.android.mtp.documents";
592efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String TAG = "MtpDocumentsProvider";
606baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
61c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
62c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
63c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_AVAILABLE_BYTES,
64c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
656baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
66c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
67c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
68c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
70c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
71f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono    static final boolean DEBUG = false;
7219aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono
73e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private final Object mDeviceListLock = new Object();
74e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
752efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
762efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
772efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
78d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
79e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    @GuardedBy("mDeviceListLock")
804c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private Map<Integer, DeviceToolkit> mDeviceToolkits;
818b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono    private RootScanner mRootScanner;
8217c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono    private Resources mResources;
83dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    private MtpDatabase mDatabase;
84f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private AppFuse mAppFuse;
85fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    private ServiceIntentSender mIntentSender;
86d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
872efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
882efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
892efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
902efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
912efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
922efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
932efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
94c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
95c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
962efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
9717c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
982efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
99d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
1004c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
10147eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
102f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
103f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
104fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = new ServiceIntentSender(getContext());
1053bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
1063bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider
1073bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // after booting.
1085884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        try {
1095884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1);
1105884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int lastBootCount = mDatabase.getLastBootCount();
1115884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            if (bootCount != -1 && bootCount != lastBootCount) {
1125884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.setLastBootCount(bootCount);
1135884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final List<UriPermission> permissions =
1145884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                        mResolver.getOutgoingPersistedUriPermissions();
1155884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final Uri[] uris = new Uri[permissions.size()];
1165884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                for (int i = 0; i < permissions.size(); i++) {
1175884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                    uris[i] = permissions.get(i).getUri();
1185884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                }
1195884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.cleanDatabase(uris);
1203bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono            }
1215884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        } catch (SQLiteDiskIOException error) {
1225884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            // It can happen due to disk shortage.
1235884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            Log.e(TAG, "Failed to clean database.", error);
1245884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            return false;
1253bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        }
1263bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
127f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        // TODO: Mount AppFuse on demands.
128e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        try {
129e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            mAppFuse.mount(getContext().getSystemService(StorageManager.class));
1305884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        } catch (IOException error) {
1315884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", error);
132e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            return false;
133e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        }
1345884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono
135e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
136c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
137c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
138c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
139d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
140b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono    boolean onCreateForTesting(
141dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
142dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
143dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
144b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            MtpDatabase database,
145fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            StorageManager storageManager,
146fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            ServiceIntentSender intentSender) {
14717c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
1486baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
1496baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
1504c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
151dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
152f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
153b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
154fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = intentSender;
1553bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
156b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        // TODO: Mount AppFuse on demands.
157b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        try {
158b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            mAppFuse.mount(storageManager);
159b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        } catch (IOException e) {
160b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", e);
161b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            return false;
162b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        }
163e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
164b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        return true;
165d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
166d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
167c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
168c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
16950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
17050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
17150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
172f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(mResources, projection);
17350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
17450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
17550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
176c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
177c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
178c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
179c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
180c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
181e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
182e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
183e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
1849e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        return mDatabase.queryDocument(documentId, projection);
185c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
186c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
187c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
188124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
189124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
19019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
19119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "queryChildDocuments: " + parentDocumentId);
19219aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
193124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
194124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
195124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
1966a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono        Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
197124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
198fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentIdentifier.mDeviceId);
1996a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
2002965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId);
2012965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                if (storageDocIds.length == 0) {
2022965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    // Remote device does not provide storages. Maybe it is locked.
2032965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    return createErrorCursor(projection, R.string.error_locked_device);
2042965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                } else if (storageDocIds.length > 1) {
2056a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    // Returns storage list from database.
2066a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    return mDatabase.queryChildDocuments(projection, parentDocumentId);
2076a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                }
2082965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2092965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // Exact one storage is found. Skip storage and returns object in the single
2102965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // storage.
2112965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]);
2126a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
2132965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2146a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            // Returns object list from document loader.
2154c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
2164c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
217c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono        } catch (BusyDeviceException exception) {
2182965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono            return createErrorCursor(projection, R.string.error_busy_device);
219124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
2206a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception);
221124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
222124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
223c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
224c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
225c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
2268ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
2278ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
2288ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
2296213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
2306213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "openDocument: " + documentId);
2316213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
2329e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2338ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
234fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2350f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
236b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            switch (mode) {
237b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "r":
238f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    final long fileSize = getFileSize(documentId);
239fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                    // MTP getPartialObject operation does not support files that are larger than
240fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                    // 4GB. Fallback to non-seekable file descriptor.
241f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // TODO: Use getPartialObject64 for MTP devices that support Android vendor
242f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // extension.
2430f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                    if (MtpDeviceRecord.isPartialReadSupported(
2440f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                            device.operationsSupported, fileSize)) {
245f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                        return mAppFuse.openFile(Integer.parseInt(documentId));
246f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    } else {
247f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                        return getPipeManager(identifier).readDocument(mMtpManager, identifier);
248f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    }
249b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "w":
25081d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // TODO: Clear the parent document loader task (if exists) and call notify
25181d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // when writing is completed.
2520f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                    if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
2530f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                        return getPipeManager(identifier).writeDocument(
25461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                                getContext(), mMtpManager, identifier, device.operationsSupported);
2550f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                    } else {
2560f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                        throw new UnsupportedOperationException(
2570f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                                "The device does not support writing operation.");
2580f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                    }
259f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                case "rw":
260f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // TODO: Add support for "rw" mode.
261b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
262f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                            "The provider does not support 'rw' mode.");
263f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                default:
264f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    throw new IllegalArgumentException("Unknown mode for openDocument: " + mode);
265b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
2668ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
2676a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
2688ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2698ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
270d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
271d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
2723faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2733faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
2743faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
2753faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
2763faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
2779e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2783faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
279fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2803faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
2814c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
282573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
2833faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
2843faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2856a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error);
2863faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2873faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
2883faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
2893faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
2903faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2913faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
2923faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
2939e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
294fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2956a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
2963faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
2979e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
2984c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
2999e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
3006a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
3016a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // If the parent is storage, the object might be appeared as child of device because
3026a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // we skip storage when the device has only one storage.
3036a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                final Identifier deviceIdentifier = mDatabase.getParentIdentifier(
3046a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                        parentIdentifier.mDocumentId);
3056a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                notifyChildDocumentsChange(deviceIdentifier.mDocumentId);
3066a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
3073faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
3086a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error);
3093faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
3103faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
3113faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
3123faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
3136baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
3146baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
315e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
316e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
317e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                toolkit.mDocumentLoader.clearCompletedTasks();
318e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
319e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
3206baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    }
3216baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono
32287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    @Override
32387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    public String createDocument(String parentDocumentId, String mimeType, String displayName)
32487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throws FileNotFoundException {
3256213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
3266213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "createDocument: " + displayName);
3276213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
328fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final Identifier parentId;
329fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final MtpDeviceRecord record;
330fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final ParcelFileDescriptor[] pipe;
33187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        try {
332fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            parentId = mDatabase.createIdentifier(parentDocumentId);
333fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentId.mDeviceId);
334fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            record = getDeviceToolkit(parentId.mDeviceId).mDeviceRecord;
3350f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            if (!MtpDeviceRecord.isWritingSupported(record.operationsSupported)) {
336fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new UnsupportedOperationException(
337fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "Writing operation is not supported by the device.");
338fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
339fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            pipe = ParcelFileDescriptor.createReliablePipe();
340fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            int objectHandle = -1;
341fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            MtpObjectInfo info = null;
342fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            try {
343fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[0].close();  // 0 bytes for a new document.
344fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
345fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
346fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MtpConstants.FORMAT_ASSOCIATION :
347fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MediaFile.getFormatCode(displayName, mimeType);
348fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                info = new MtpObjectInfo.Builder()
349fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setStorageId(parentId.mStorageId)
350fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setParent(parentId.mObjectHandle)
351fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setFormat(formatCode)
352fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setName(displayName)
353fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .build();
354fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
355fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String[] parts = FileUtils.splitFileName(mimeType, displayName);
356fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String baseName = parts[0];
357fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String extension = parts[1];
358fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                for (int i = 0; i <= 32; i++) {
359fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    final MtpObjectInfo infoUniqueName;
360fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    if (i == 0) {
361fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        infoUniqueName = info;
362fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } else {
3634f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        String suffixedName = baseName + " (" + i + " )";
3644f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        if (!extension.isEmpty()) {
3654f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                            suffixedName += "." + extension;
3664f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        }
3674f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        infoUniqueName =
3684f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                                new MtpObjectInfo.Builder(info).setName(suffixedName).build();
369fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
370fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    try {
371fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        objectHandle = mMtpManager.createDocument(
372fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                                parentId.mDeviceId, infoUniqueName, pipe[1]);
373fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        break;
374fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } catch (SendObjectInfoFailure exp) {
375fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        // This can be caused when we have an existing file with the same name.
376fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        continue;
377fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
378fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                }
379fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            } finally {
380fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[1].close();
381fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
382fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            if (objectHandle == -1) {
383fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new IllegalArgumentException(
384fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "The file name \"" + displayName + "\" is conflicted with existing files " +
385fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "and the provider failed to find unique name.");
3860f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            }
3879e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo infoWithHandle =
3889e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
3899e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final String documentId = mDatabase.putNewDocument(
39061ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    parentId.mDeviceId, parentDocumentId, record.operationsSupported,
39161ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    infoWithHandle);
3924c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentId).clearTask(parentId);
39387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            notifyChildDocumentsChange(parentDocumentId);
39487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            return documentId;
395fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        } catch (FileNotFoundException | RuntimeException error) {
396fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            Log.e(TAG, "createDocument", error);
397fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw error;
39887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        } catch (IOException error) {
3996a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(TAG, "createDocument", error);
400fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw new IllegalStateException(error);
40187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        }
40287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    }
40387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski
4042efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
405e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
406fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            if (mDeviceToolkits.containsKey(deviceId)) {
407fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                return;
408fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
40919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            if (DEBUG) {
41019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono                Log.d(TAG, "Open device " + deviceId);
41119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            }
4120f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = mMtpManager.openDevice(deviceId);
4134e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            final DeviceToolkit toolkit =
41461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    new DeviceToolkit(mMtpManager, mResolver, mDatabase, device);
4154e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            mDeviceToolkits.put(deviceId, toolkit);
416fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            mIntentSender.sendUpdateNotificationIntent();
417fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            try {
418fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                mRootScanner.resume().await();
419fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            } catch (InterruptedException error) {
420fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                Log.e(TAG, "openDevice", error);
421fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
4224e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // Resume document loader to remap disconnected document ID. Must be invoked after the
4234e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // root scanner resumes.
4244e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            toolkit.mDocumentLoader.resume();
425e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
426d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
427d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
428e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
429e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
430e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            closeDeviceInternal(deviceId);
431e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
43220754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono        mRootScanner.resume();
433fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender.sendUpdateNotificationIntent();
434d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
435d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
4360f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono    MtpDeviceRecord[] getOpenedDeviceRecordsCache() {
437e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
4380f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord[] records = new MtpDeviceRecord[mDeviceToolkits.size()];
4390f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            int i = 0;
4400f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
4410f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                records[i] = toolkit.mDeviceRecord;
4420f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                i++;
44320754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            }
4440f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            return records;
445e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
44650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
44750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
448e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
4491e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * Obtains document ID for the given device ID.
4501e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @param deviceId
4511e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @return document ID
4521e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @throws FileNotFoundException device ID has not been build.
4531e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     */
4541e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    public String getDeviceDocumentId(int deviceId) throws FileNotFoundException {
4551e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono        return mDatabase.getDeviceDocumentId(deviceId);
4561e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    }
4571e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono
4581e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    /**
459fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     * Resumes root scanner to handle the update of device list.
460fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     */
461fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    void resumeRootScanner() {
462ebd24051599280443435606cab220de33b9356adDaichi Hirono        if (DEBUG) {
463ebd24051599280443435606cab220de33b9356adDaichi Hirono            Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner");
464ebd24051599280443435606cab220de33b9356adDaichi Hirono        }
465fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mRootScanner.resume();
466fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    }
467fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono
468fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    /**
469e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
470e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
471e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
472e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
473e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
474e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            try {
4750f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                // Copy the opened key set because it will be modified when closing devices.
4760f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                final Integer[] keySet =
4770f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                        mDeviceToolkits.keySet().toArray(new Integer[mDeviceToolkits.size()]);
4780f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                for (final int id : keySet) {
479e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    closeDeviceInternal(id);
480e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                }
4812e9a57b0d4839b322787dad36bfcb64f211f7ac4Daichi Hirono                mRootScanner.pause();
482acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hirono            } catch (InterruptedException | IOException | TimeoutException e) {
483e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                // It should fail unit tests by throwing runtime exception.
484e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                throw new RuntimeException(e);
485e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } finally {
486e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                mDatabase.close();
487b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono                mAppFuse.close();
488e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                super.shutdown();
489e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            }
490e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
491e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
492e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
4935fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
4945fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
4955fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
4965fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
4975fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
4985fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
4994c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
500e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    /**
501be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono     * Clears MTP identifier in the database.
502e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono     */
503e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void resume() {
504e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
505e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDatabase.getMapper().clearMapping();
506e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
507e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
508e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
509e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
510e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        // TODO: Flush the device before closing (if not closed externally).
511fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        if (!mDeviceToolkits.containsKey(deviceId)) {
512fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            return;
513fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        }
51419aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
51519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "Close device " + deviceId);
51619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
51724ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        getDeviceToolkit(deviceId).close();
518e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mDeviceToolkits.remove(deviceId);
519e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mMtpManager.closeDevice(deviceId);
520e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
521e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
5224c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
523e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
524e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
525e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
526e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
527e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
528e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
5294c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
5304c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
5314c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
5324c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
5334c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
5344c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
5354c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
5364c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
5374c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
5384c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
5394c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
540f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private long getFileSize(String documentId) throws FileNotFoundException {
541f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(
542f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                documentId,
543f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME));
544f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        try {
545f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            if (cursor.moveToNext()) {
546f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                return cursor.getLong(0);
547f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            } else {
548f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                throw new FileNotFoundException();
549f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            }
550f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        } finally {
551f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            cursor.close();
552f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
553f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
554f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
5552965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    /**
5562965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * Creates empty cursor with specific error message.
5572965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     *
5582965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param projection Column names.
5592965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param stringResId String resource ID of error message.
5602965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @return Empty cursor with error message.
5612965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     */
5622965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    private Cursor createErrorCursor(String[] projection, int stringResId) {
5632965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Bundle bundle = new Bundle();
5642965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId));
5652965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Cursor cursor = new MatrixCursor(projection);
5662965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        cursor.setExtras(bundle);
5672965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        return cursor;
5682965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    }
5692965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
57024ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono    private static class DeviceToolkit implements AutoCloseable {
5714c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
5724c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
5730f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono        public final MtpDeviceRecord mDeviceRecord;
5744c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
57561ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono        public DeviceToolkit(MtpManager manager,
57661ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             ContentResolver resolver,
57761ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDatabase database,
57861ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDeviceRecord record) {
579f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono            mPipeManager = new PipeManager(database);
58061ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono            mDocumentLoader = new DocumentLoader(record, manager, resolver, database);
5810f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            mDeviceRecord = record;
5824c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
58324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono
58424ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        @Override
58524ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        public void close() throws InterruptedException {
58624ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mPipeManager.close();
58724ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mDocumentLoader.close();
58824ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        }
5894c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
590f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
591f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private class AppFuseCallback implements AppFuse.Callback {
592f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
5932f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono        public long readObjectBytes(
5942f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono                int inode, long offset, long size, byte[] buffer) throws IOException {
595f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode));
5960f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
5970f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            if (MtpDeviceRecord.isPartialReadSupported(record.operationsSupported, offset)) {
5980f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                return mMtpManager.getPartialObject(
5990f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                        identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
6000f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            } else {
6010f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                throw new UnsupportedOperationException();
6020f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            }
603f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
604f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
605f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
606f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        public long getFileSize(int inode) throws FileNotFoundException {
607f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
608f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
609f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
610c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
611