MtpDocumentsProvider.java revision 29de7693f4fab188e695b9ecda1de0345496bd10
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;
20f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.content.Context;
213bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.content.UriPermission;
223faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.content.res.AssetFileDescriptor;
2317c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hironoimport android.content.res.Resources;
24c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor;
25c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.database.MatrixCursor;
265884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hironoimport android.database.sqlite.SQLiteDiskIOException;
273faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point;
289e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile;
299e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants;
30bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo;
313bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.net.Uri;
32c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.os.Bundle;
33c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal;
34fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hironoimport android.os.FileUtils;
35c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor;
36f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager;
37c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document;
38b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport android.provider.DocumentsContract.Path;
39c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root;
40bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract;
41c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider;
423bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.provider.Settings;
43f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.system.ErrnoException;
44d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log;
45d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
46e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy;
47d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting;
48d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
49c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException;
50d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException;
514c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap;
52b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport java.util.LinkedList;
533bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport java.util.List;
544c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map;
55acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hironoimport java.util.concurrent.TimeoutException;
56c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
57d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/**
58d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices.
59d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */
60c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider {
612efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String AUTHORITY = "com.android.mtp.documents";
622efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String TAG = "MtpDocumentsProvider";
636baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
64c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
65c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
66c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_AVAILABLE_BYTES,
67c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
686baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
70c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
71c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
72c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
73c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
74f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono    static final boolean DEBUG = false;
7519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono
76e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private final Object mDeviceListLock = new Object();
77e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
782efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
792efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
802efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
81d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
82e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    @GuardedBy("mDeviceListLock")
834c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private Map<Integer, DeviceToolkit> mDeviceToolkits;
848b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono    private RootScanner mRootScanner;
8517c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono    private Resources mResources;
86dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    private MtpDatabase mDatabase;
87f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private AppFuse mAppFuse;
88fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    private ServiceIntentSender mIntentSender;
89f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono    private Context mContext;
90d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
912efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
922efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
932efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
942efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
952efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
962efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
972efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
98c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
99c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
1002efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
101f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        mContext = getContext();
10217c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
1032efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
104d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
1054c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
10647eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
107f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
108f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
109fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = new ServiceIntentSender(getContext());
1103bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
1113bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider
1123bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // after booting.
1135884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        try {
1145884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1);
1155884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int lastBootCount = mDatabase.getLastBootCount();
1165884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            if (bootCount != -1 && bootCount != lastBootCount) {
1175884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.setLastBootCount(bootCount);
1185884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final List<UriPermission> permissions =
1195884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                        mResolver.getOutgoingPersistedUriPermissions();
1205884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final Uri[] uris = new Uri[permissions.size()];
1215884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                for (int i = 0; i < permissions.size(); i++) {
1225884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                    uris[i] = permissions.get(i).getUri();
1235884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                }
1245884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.cleanDatabase(uris);
1253bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono            }
1265884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        } catch (SQLiteDiskIOException error) {
1275884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            // It can happen due to disk shortage.
1285884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            Log.e(TAG, "Failed to clean database.", error);
1295884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            return false;
1303bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        }
1313bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
132f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        // TODO: Mount AppFuse on demands.
133e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        try {
134e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            mAppFuse.mount(getContext().getSystemService(StorageManager.class));
1355884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        } catch (IOException error) {
1365884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", error);
137e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            return false;
138e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        }
1395884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono
140e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
141c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
142c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
143c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
144d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
145b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono    boolean onCreateForTesting(
146f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            Context context,
147dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
148dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
149dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
150b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            MtpDatabase database,
151fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            StorageManager storageManager,
152fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            ServiceIntentSender intentSender) {
153f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        mContext = context;
15417c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
1556baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
1566baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
1574c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
158dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
159f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
160b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
161fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = intentSender;
1623bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
163b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        // TODO: Mount AppFuse on demands.
164b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        try {
165b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            mAppFuse.mount(storageManager);
166b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        } catch (IOException e) {
167b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", e);
168b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            return false;
169b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        }
170e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
171b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        return true;
172d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
173d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
174c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
175c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
17650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
17750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
17850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
179f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(mResources, projection);
18050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
18150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
18250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
183c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
184c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
185c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
186c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
187c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
188e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
189e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
190e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
1919e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        return mDatabase.queryDocument(documentId, projection);
192c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
193c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
194c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
195124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
196124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
19719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
19819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "queryChildDocuments: " + parentDocumentId);
19919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
200124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
201124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
202124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
2036a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono        Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
204124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
205fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentIdentifier.mDeviceId);
2066a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
2072965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId);
2082965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                if (storageDocIds.length == 0) {
2092965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    // Remote device does not provide storages. Maybe it is locked.
2102965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    return createErrorCursor(projection, R.string.error_locked_device);
2112965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                } else if (storageDocIds.length > 1) {
2126a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    // Returns storage list from database.
2136a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    return mDatabase.queryChildDocuments(projection, parentDocumentId);
2146a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                }
2152965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2162965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // Exact one storage is found. Skip storage and returns object in the single
2172965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // storage.
2182965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]);
2196a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
2202965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2216a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            // Returns object list from document loader.
2224c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
2234c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
224c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono        } catch (BusyDeviceException exception) {
2252965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono            return createErrorCursor(projection, R.string.error_busy_device);
226124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
2276a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception);
228124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
229124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
230c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
231c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
232c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
2338ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
2348ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
2358ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
2366213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
2376213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "openDocument: " + documentId);
2386213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
2399e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2408ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
241fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2420f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
243f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            // Turn off MODE_CREATE because openDocument does not allow to create new files.
244f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final int modeFlag =
245f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE;
246f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) {
247f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                long fileSize;
248f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                try {
249f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    fileSize = getFileSize(documentId);
250f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } catch (UnsupportedOperationException exception) {
251f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    fileSize = -1;
252f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
253f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                if (MtpDeviceRecord.isPartialReadSupported(
254f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                        device.operationsSupported, fileSize)) {
255f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
256f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } else {
257f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    // If getPartialObject{|64} are not supported for the device, returns
258f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    // non-seekable pipe FD instead.
259f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    return getPipeManager(identifier).readDocument(mMtpManager, identifier);
260f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
261f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) {
262f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // TODO: Clear the parent document loader task (if exists) and call notify
263f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // when writing is completed.
264f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
265f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
266f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } else {
267b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
268f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                            "The device does not support writing operation.");
269f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
270f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else {
271f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // TODO: Add support for "rw" mode.
272f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                throw new UnsupportedOperationException("The provider does not support 'rw' mode.");
273b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
274f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        } catch (FileNotFoundException | RuntimeException error) {
275f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
276f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            throw error;
2778ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
2786a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
279f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            throw new IllegalStateException(error);
2808ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
281d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
282d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
2833faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2843faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
2853faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
2863faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
2873faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
2889e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2893faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
290fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2913faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
2924c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
293573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
2943faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
2953faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2966a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error);
2973faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2983faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
2993faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
3003faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
3013faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
3023faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
3033faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
3049e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
305fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
3066a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
3073faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
3089e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
30976be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono            getDocumentLoader(parentIdentifier).cancelTask(parentIdentifier);
3109e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
3116a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
3126a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // If the parent is storage, the object might be appeared as child of device because
3136a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // we skip storage when the device has only one storage.
3146a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                final Identifier deviceIdentifier = mDatabase.getParentIdentifier(
3156a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                        parentIdentifier.mDocumentId);
3166a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                notifyChildDocumentsChange(deviceIdentifier.mDocumentId);
3176a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
3183faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
3196a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error);
3203faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
3213faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
3223faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
3233faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
3246baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
3256baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
326e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
327e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
328e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                toolkit.mDocumentLoader.clearCompletedTasks();
329e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
330e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
3316baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    }
3326baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono
33387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    @Override
33487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    public String createDocument(String parentDocumentId, String mimeType, String displayName)
33587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throws FileNotFoundException {
3366213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
3376213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "createDocument: " + displayName);
3386213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
339fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final Identifier parentId;
340fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final MtpDeviceRecord record;
341fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final ParcelFileDescriptor[] pipe;
34287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        try {
343fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            parentId = mDatabase.createIdentifier(parentDocumentId);
344fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentId.mDeviceId);
345fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            record = getDeviceToolkit(parentId.mDeviceId).mDeviceRecord;
3460f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            if (!MtpDeviceRecord.isWritingSupported(record.operationsSupported)) {
347fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new UnsupportedOperationException(
348fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "Writing operation is not supported by the device.");
349fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
35035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono
35135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            final int parentObjectHandle;
35235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            final int storageId;
35335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            switch (parentId.mDocumentType) {
35435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE:
35535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    final String[] storageDocumentIds =
35635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                            mDatabase.getStorageDocumentIds(parentId.mDocumentId);
35735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    if (storageDocumentIds.length == 1) {
35835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        final String newDocumentId =
35935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                                createDocument(storageDocumentIds[0], mimeType, displayName);
36035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        notifyChildDocumentsChange(parentDocumentId);
36135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        return newDocumentId;
36235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    } else {
36335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        throw new UnsupportedOperationException(
36435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                                "Cannot create a file under the device.");
36535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    }
36635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE:
36735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    storageId = parentId.mStorageId;
36835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    parentObjectHandle = -1;
36935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    break;
37035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT:
37135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    storageId = parentId.mStorageId;
37235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    parentObjectHandle = parentId.mObjectHandle;
37335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    break;
37435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                default:
37535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    throw new IllegalArgumentException("Unexpected document type.");
37635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            }
37735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono
378fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            pipe = ParcelFileDescriptor.createReliablePipe();
379fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            int objectHandle = -1;
380fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            MtpObjectInfo info = null;
381fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            try {
382fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[0].close();  // 0 bytes for a new document.
383fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
384fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
385fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MtpConstants.FORMAT_ASSOCIATION :
386fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MediaFile.getFormatCode(displayName, mimeType);
387fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                info = new MtpObjectInfo.Builder()
38835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        .setStorageId(storageId)
38935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        .setParent(parentObjectHandle)
390fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setFormat(formatCode)
391fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setName(displayName)
392fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .build();
393fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
394fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String[] parts = FileUtils.splitFileName(mimeType, displayName);
395fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String baseName = parts[0];
396fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String extension = parts[1];
397fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                for (int i = 0; i <= 32; i++) {
398fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    final MtpObjectInfo infoUniqueName;
399fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    if (i == 0) {
400fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        infoUniqueName = info;
401fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } else {
4024f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        String suffixedName = baseName + " (" + i + " )";
4034f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        if (!extension.isEmpty()) {
4044f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                            suffixedName += "." + extension;
4054f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        }
4064f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        infoUniqueName =
4074f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                                new MtpObjectInfo.Builder(info).setName(suffixedName).build();
408fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
409fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    try {
410fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        objectHandle = mMtpManager.createDocument(
411fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                                parentId.mDeviceId, infoUniqueName, pipe[1]);
412fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        break;
413fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } catch (SendObjectInfoFailure exp) {
414fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        // This can be caused when we have an existing file with the same name.
415fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        continue;
416fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
417fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                }
418fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            } finally {
419fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[1].close();
420fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
421fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            if (objectHandle == -1) {
422fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new IllegalArgumentException(
423fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "The file name \"" + displayName + "\" is conflicted with existing files " +
424fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "and the provider failed to find unique name.");
4250f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            }
4269e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo infoWithHandle =
4279e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
4289e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final String documentId = mDatabase.putNewDocument(
42961ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    parentId.mDeviceId, parentDocumentId, record.operationsSupported,
43064111e08d905525c7f4fe27e69953eb71bd62511Daichi Hirono                    infoWithHandle, 0l);
43176be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono            getDocumentLoader(parentId).cancelTask(parentId);
43287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            notifyChildDocumentsChange(parentDocumentId);
43387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            return documentId;
434fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        } catch (FileNotFoundException | RuntimeException error) {
435fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            Log.e(TAG, "createDocument", error);
436fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw error;
43787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        } catch (IOException error) {
4386a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(TAG, "createDocument", error);
439fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw new IllegalStateException(error);
44087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        }
44187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    }
44287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski
443b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono    @Override
444f46a463e414a9c79db49a272c79320d688494842Garfield Tan    public Path findDocumentPath(String childDocumentId, String parentDocumentId)
445b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            throws FileNotFoundException {
446b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        final LinkedList<String> ids = new LinkedList<>();
447b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        final Identifier childIdentifier = mDatabase.createIdentifier(childDocumentId);
448b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
449b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        Identifier i = childIdentifier;
450b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        outer: while (true) {
451b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            if (i.mDocumentId.equals(parentDocumentId)) {
452b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                ids.addFirst(i.mDocumentId);
453b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                break;
454b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            }
455b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            switch (i.mDocumentType) {
456b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT:
457b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    ids.addFirst(i.mDocumentId);
458b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    i = mDatabase.getParentIdentifier(i.mDocumentId);
459b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break;
460b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE: {
461b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // Check if there is the multiple storage.
462b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    final Identifier deviceIdentifier =
463b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                            mDatabase.getParentIdentifier(i.mDocumentId);
464b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    final String[] storageIds =
465b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                            mDatabase.getStorageDocumentIds(deviceIdentifier.mDocumentId);
466b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // Add storage's document ID to the path only when the device has multiple
467b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // storages.
468b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    if (storageIds.length > 1) {
469b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                        ids.addFirst(i.mDocumentId);
470b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                        break outer;
471b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    }
472b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    i = deviceIdentifier;
473b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break;
474b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                }
475b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE:
476b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    ids.addFirst(i.mDocumentId);
477b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break outer;
478b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            }
479b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        }
480b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
481b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        if (parentDocumentId != null) {
482b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            return new Path(null, ids);
483b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        } else {
484b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            return new Path(/* Should be same with root ID */ i.mDocumentId, ids);
485b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        }
486b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono    }
487b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
48829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    @Override
48929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    public boolean isChildDocument(String parentDocumentId, String documentId) {
49029de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        try {
49129de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            Identifier identifier = mDatabase.createIdentifier(documentId);
49229de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            while (true) {
49329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                if (parentDocumentId.equals(identifier.mDocumentId)) {
49429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                    return true;
49529de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                }
49629de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                if (identifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
49729de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                    return false;
49829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                }
49929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                identifier = mDatabase.getParentIdentifier(identifier.mDocumentId);
50029de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            }
50129de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        } catch (FileNotFoundException error) {
50229de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            return false;
50329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        }
50429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    }
50529de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono
5062efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
507e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
508fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            if (mDeviceToolkits.containsKey(deviceId)) {
509fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                return;
510fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
51119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            if (DEBUG) {
51219aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono                Log.d(TAG, "Open device " + deviceId);
51319aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            }
5140f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = mMtpManager.openDevice(deviceId);
5154e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            final DeviceToolkit toolkit =
51661ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    new DeviceToolkit(mMtpManager, mResolver, mDatabase, device);
5174e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            mDeviceToolkits.put(deviceId, toolkit);
518fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            mIntentSender.sendUpdateNotificationIntent();
519fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            try {
520fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                mRootScanner.resume().await();
521fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            } catch (InterruptedException error) {
522fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                Log.e(TAG, "openDevice", error);
523fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
5244e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // Resume document loader to remap disconnected document ID. Must be invoked after the
5254e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // root scanner resumes.
5264e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            toolkit.mDocumentLoader.resume();
527e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
528d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
529d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
530e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
531e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
532e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            closeDeviceInternal(deviceId);
533e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
53420754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono        mRootScanner.resume();
535fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender.sendUpdateNotificationIntent();
536d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
537d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
5380f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono    MtpDeviceRecord[] getOpenedDeviceRecordsCache() {
539e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
5400f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord[] records = new MtpDeviceRecord[mDeviceToolkits.size()];
5410f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            int i = 0;
5420f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
5430f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                records[i] = toolkit.mDeviceRecord;
5440f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                i++;
54520754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            }
5460f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            return records;
547e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
54850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
54950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
550e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
5511e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * Obtains document ID for the given device ID.
5521e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @param deviceId
5531e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @return document ID
5541e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @throws FileNotFoundException device ID has not been build.
5551e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     */
5561e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    public String getDeviceDocumentId(int deviceId) throws FileNotFoundException {
5571e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono        return mDatabase.getDeviceDocumentId(deviceId);
5581e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    }
5591e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono
5601e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    /**
561fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     * Resumes root scanner to handle the update of device list.
562fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     */
563fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    void resumeRootScanner() {
564ebd24051599280443435606cab220de33b9356adDaichi Hirono        if (DEBUG) {
565ebd24051599280443435606cab220de33b9356adDaichi Hirono            Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner");
566ebd24051599280443435606cab220de33b9356adDaichi Hirono        }
567fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mRootScanner.resume();
568fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    }
569fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono
570fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    /**
571e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
572e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
573e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
574e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
575e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
576e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            try {
5770f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                // Copy the opened key set because it will be modified when closing devices.
5780f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                final Integer[] keySet =
5790f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                        mDeviceToolkits.keySet().toArray(new Integer[mDeviceToolkits.size()]);
5800f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                for (final int id : keySet) {
581e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    closeDeviceInternal(id);
582e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                }
5832e9a57b0d4839b322787dad36bfcb64f211f7ac4Daichi Hirono                mRootScanner.pause();
584acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hirono            } catch (InterruptedException | IOException | TimeoutException e) {
585e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                // It should fail unit tests by throwing runtime exception.
586e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                throw new RuntimeException(e);
587e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } finally {
588e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                mDatabase.close();
589b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono                mAppFuse.close();
590e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                super.shutdown();
591e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            }
592e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
593e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
594e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
5955fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
5965fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
5975fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
5985fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
5995fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
6005fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
6014c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
602e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    /**
603be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono     * Clears MTP identifier in the database.
604e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono     */
605e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void resume() {
606e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
607e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDatabase.getMapper().clearMapping();
608e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
609e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
610e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
611e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
612e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        // TODO: Flush the device before closing (if not closed externally).
613fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        if (!mDeviceToolkits.containsKey(deviceId)) {
614fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            return;
615fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        }
61619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
61719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "Close device " + deviceId);
61819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
61924ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        getDeviceToolkit(deviceId).close();
620e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mDeviceToolkits.remove(deviceId);
621e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mMtpManager.closeDevice(deviceId);
622e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
623e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
6244c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
625e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
626e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
627e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
628e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
629e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
630e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
6314c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
6324c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
6334c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
6344c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
6354c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
6364c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
6374c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
6384c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
6394c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
6404c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
6414c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
642f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private long getFileSize(String documentId) throws FileNotFoundException {
643f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(
644f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                documentId,
645f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME));
646f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        try {
647f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            if (cursor.moveToNext()) {
64877a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                if (cursor.isNull(0)) {
64977a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                    throw new UnsupportedOperationException();
65077a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                }
651f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                return cursor.getLong(0);
652f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            } else {
653f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                throw new FileNotFoundException();
654f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            }
655f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        } finally {
656f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            cursor.close();
657f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
658f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
659f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
6602965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    /**
6612965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * Creates empty cursor with specific error message.
6622965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     *
6632965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param projection Column names.
6642965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param stringResId String resource ID of error message.
6652965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @return Empty cursor with error message.
6662965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     */
6672965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    private Cursor createErrorCursor(String[] projection, int stringResId) {
6682965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Bundle bundle = new Bundle();
6692965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId));
6702965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Cursor cursor = new MatrixCursor(projection);
6712965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        cursor.setExtras(bundle);
6722965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        return cursor;
6732965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    }
6742965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
67524ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono    private static class DeviceToolkit implements AutoCloseable {
6764c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
6774c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
6780f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono        public final MtpDeviceRecord mDeviceRecord;
6794c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
68061ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono        public DeviceToolkit(MtpManager manager,
68161ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             ContentResolver resolver,
68261ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDatabase database,
68361ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDeviceRecord record) {
684f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono            mPipeManager = new PipeManager(database);
68561ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono            mDocumentLoader = new DocumentLoader(record, manager, resolver, database);
6860f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            mDeviceRecord = record;
6874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
68824ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono
68924ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        @Override
69024ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        public void close() throws InterruptedException {
69124ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mPipeManager.close();
69224ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mDocumentLoader.close();
69324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        }
6944c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
695f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
696f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private class AppFuseCallback implements AppFuse.Callback {
697f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        private final Map<Long, MtpFileWriter> mWriters = new HashMap<>();
698f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
699f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        @Override
700f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        public long getFileSize(int inode) throws FileNotFoundException {
701f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
702f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        }
703f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
704f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
7052f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono        public long readObjectBytes(
7062f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono                int inode, long offset, long size, byte[] buffer) throws IOException {
707f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode));
7080f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
70977a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
71077a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono            if (MtpDeviceRecord.isSupported(
71177a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                    record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) {
71277a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                return mMtpManager.getPartialObject64(
71377a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                        identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
71477a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono            }
71577a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
71677a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono            if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported(
71777a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                    record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) {
7180f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                return mMtpManager.getPartialObject(
7190f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                        identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
7200f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            }
72177a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
72277a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono            throw new UnsupportedOperationException();
723f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
724f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
725f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
726f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        public int writeObjectBytes(
727f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                long fileHandle, int inode, long offset, int size, byte[] bytes)
728f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                throws IOException, ErrnoException {
729f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final MtpFileWriter writer;
730f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            if (mWriters.containsKey(fileHandle)) {
731f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                writer = mWriters.get(fileHandle);
732f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else {
733f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                writer = new MtpFileWriter(mContext, String.valueOf(inode));
734f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                mWriters.put(fileHandle, writer);
735f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
736f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            return writer.write(offset, size, bytes);
737f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
73809ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono
73909ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono        @Override
740f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        public void flushFileHandle(long fileHandle) throws IOException, ErrnoException {
741f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final MtpFileWriter writer = mWriters.get(fileHandle);
742f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            if (writer == null) {
743f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // File handle for reading.
744f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                return;
745f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
746f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final MtpDeviceRecord device = getDeviceToolkit(
747f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    mDatabase.createIdentifier(writer.getDocumentId()).mDeviceId).mDeviceRecord;
748f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            writer.flush(mMtpManager, mDatabase, device.operationsSupported);
749f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        }
750f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
751f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        @Override
752f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        public void closeFileHandle(long fileHandle) throws IOException, ErrnoException {
753f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final MtpFileWriter writer = mWriters.get(fileHandle);
754f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            if (writer == null) {
755f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // File handle for reading.
756f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                return;
757f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
758f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            try {
759f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                writer.close();
760f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } finally {
761f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                mWriters.remove(fileHandle);
762f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
76309ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono        }
764f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
765c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
766