MtpDocumentsProvider.java revision 203be491ef479deea1f39f8b63f3e1916501a37a
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;
36e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport android.os.ProxyFileDescriptorCallback;
37f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager;
38c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document;
39b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport android.provider.DocumentsContract.Path;
40c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root;
41bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract;
42c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider;
433bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.provider.Settings;
44f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.system.ErrnoException;
45e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport android.system.OsConstants;
46d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log;
47d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
48e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy;
49d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting;
50d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
51c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException;
52d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException;
534c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap;
54b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport java.util.LinkedList;
553bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport java.util.List;
564c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map;
57acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hironoimport java.util.concurrent.TimeoutException;
58c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
59e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport libcore.io.IoUtils;
60e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
61d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/**
62d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices.
63d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */
64c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider {
652efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String AUTHORITY = "com.android.mtp.documents";
662efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String TAG = "MtpDocumentsProvider";
676baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
68c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
70c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_AVAILABLE_BYTES,
71c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
726baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
73c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
74c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
75c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
76c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
77c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
78f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono    static final boolean DEBUG = false;
7919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono
80e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private final Object mDeviceListLock = new Object();
81e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
822efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
842efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
85d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
86e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    @GuardedBy("mDeviceListLock")
874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private Map<Integer, DeviceToolkit> mDeviceToolkits;
888b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono    private RootScanner mRootScanner;
8917c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono    private Resources mResources;
90dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    private MtpDatabase mDatabase;
91fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    private ServiceIntentSender mIntentSender;
92f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono    private Context mContext;
93e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono    private StorageManager mStorageManager;
94d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
952efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
962efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
972efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
982efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
992efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
1002efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
1012efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
102c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
103c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
1042efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
105f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        mContext = getContext();
10617c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
1072efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
108d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
1094c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
11047eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
111f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
112fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = new ServiceIntentSender(getContext());
113e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        mStorageManager = getContext().getSystemService(StorageManager.class);
1143bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
1153bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider
1163bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // after booting.
1175884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        try {
1185884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1);
1195884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int lastBootCount = mDatabase.getLastBootCount();
1205884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            if (bootCount != -1 && bootCount != lastBootCount) {
1215884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.setLastBootCount(bootCount);
1225884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final List<UriPermission> permissions =
1235884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                        mResolver.getOutgoingPersistedUriPermissions();
1245884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final Uri[] uris = new Uri[permissions.size()];
1255884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                for (int i = 0; i < permissions.size(); i++) {
1265884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                    uris[i] = permissions.get(i).getUri();
1275884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                }
1285884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.cleanDatabase(uris);
1293bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono            }
1305884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        } catch (SQLiteDiskIOException error) {
1315884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            // It can happen due to disk shortage.
1325884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            Log.e(TAG, "Failed to clean database.", error);
1335884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            return false;
1343bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        }
1353bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
136e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
137c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
138c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
139c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
140d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
141b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono    boolean onCreateForTesting(
142f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            Context context,
143dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
144dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
145dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
146b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            MtpDatabase database,
147fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            StorageManager storageManager,
148fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            ServiceIntentSender intentSender) {
149f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        mContext = context;
15017c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
1516baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
1526baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
1534c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
154dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
155f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
156fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = intentSender;
157e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        mStorageManager = storageManager;
1583bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
159e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
160b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        return true;
161d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
162d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
163c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
164c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
16550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
16650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
16750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
168f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(mResources, projection);
16950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
17050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
17150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
172c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
173c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
174c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
175c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
176c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
177e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
178e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
179e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
1809e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        return mDatabase.queryDocument(documentId, projection);
181c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
182c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
183c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
184124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
185124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
18619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
18719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "queryChildDocuments: " + parentDocumentId);
18819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
189124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
190124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
191124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
1926a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono        Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
193124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
194fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentIdentifier.mDeviceId);
1956a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
1962965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId);
1972965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                if (storageDocIds.length == 0) {
1982965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    // Remote device does not provide storages. Maybe it is locked.
1992965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    return createErrorCursor(projection, R.string.error_locked_device);
2002965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                } else if (storageDocIds.length > 1) {
2016a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    // Returns storage list from database.
2026a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    return mDatabase.queryChildDocuments(projection, parentDocumentId);
2036a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                }
2042965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2052965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // Exact one storage is found. Skip storage and returns object in the single
2062965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // storage.
2072965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]);
2086a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
2092965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2106a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            // Returns object list from document loader.
2114c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
2124c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
213c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono        } catch (BusyDeviceException exception) {
2142965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono            return createErrorCursor(projection, R.string.error_busy_device);
215124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
2166a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception);
217124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
218124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
219c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
220c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
221c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
2228ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
2238ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
2248ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
2256213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
2266213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "openDocument: " + documentId);
2276213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
2289e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2298ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
230fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2310f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
232f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            // Turn off MODE_CREATE because openDocument does not allow to create new files.
233f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final int modeFlag =
234f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE;
235f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) {
236f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                long fileSize;
237f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                try {
238f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    fileSize = getFileSize(documentId);
239f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } catch (UnsupportedOperationException exception) {
240f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    fileSize = -1;
241f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
242f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                if (MtpDeviceRecord.isPartialReadSupported(
243f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                        device.operationsSupported, fileSize)) {
244e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
245e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    return mStorageManager.openProxyFileDescriptor(
246e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            modeFlag,
247e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId)));
248f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } else {
249f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    // If getPartialObject{|64} are not supported for the device, returns
250f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    // non-seekable pipe FD instead.
251f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    return getPipeManager(identifier).readDocument(mMtpManager, identifier);
252f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
253f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) {
254f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // TODO: Clear the parent document loader task (if exists) and call notify
255f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // when writing is completed.
256f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
257e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    return mStorageManager.openProxyFileDescriptor(
258e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            modeFlag,
259e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId)));
260f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } else {
261b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
262f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                            "The device does not support writing operation.");
263f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
264f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else {
265f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // TODO: Add support for "rw" mode.
266f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                throw new UnsupportedOperationException("The provider does not support 'rw' mode.");
267b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
268f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        } catch (FileNotFoundException | RuntimeException error) {
269f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
270f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            throw error;
2718ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
2726a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
273f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            throw new IllegalStateException(error);
2748ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
275d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
276d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
2773faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2783faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
2793faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
2803faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
2813faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
2829e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2833faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
284fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2853faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
2864c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
287573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
2883faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
2893faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2906a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error);
2913faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2923faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
2933faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
2943faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
2953faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2963faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
2973faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
2989e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
299fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
3006a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
3013faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
3029e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
30376be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono            getDocumentLoader(parentIdentifier).cancelTask(parentIdentifier);
3049e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
3056a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
3066a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // If the parent is storage, the object might be appeared as child of device because
3076a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // we skip storage when the device has only one storage.
3086a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                final Identifier deviceIdentifier = mDatabase.getParentIdentifier(
3096a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                        parentIdentifier.mDocumentId);
3106a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                notifyChildDocumentsChange(deviceIdentifier.mDocumentId);
3116a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
3123faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
3136a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error);
3143faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
3153faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
3163faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
3173faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
3186baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
3196baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
320e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
321e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
322e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                toolkit.mDocumentLoader.clearCompletedTasks();
323e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
324e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
3256baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    }
3266baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono
32787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    @Override
32887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    public String createDocument(String parentDocumentId, String mimeType, String displayName)
32987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throws FileNotFoundException {
3306213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
3316213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "createDocument: " + displayName);
3326213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
333fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final Identifier parentId;
334fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final MtpDeviceRecord record;
335fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final ParcelFileDescriptor[] pipe;
33687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        try {
337fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            parentId = mDatabase.createIdentifier(parentDocumentId);
338fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentId.mDeviceId);
339fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            record = getDeviceToolkit(parentId.mDeviceId).mDeviceRecord;
3400f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            if (!MtpDeviceRecord.isWritingSupported(record.operationsSupported)) {
341fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new UnsupportedOperationException(
342fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "Writing operation is not supported by the device.");
343fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
34435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono
34535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            final int parentObjectHandle;
34635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            final int storageId;
34735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            switch (parentId.mDocumentType) {
34835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE:
34935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    final String[] storageDocumentIds =
35035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                            mDatabase.getStorageDocumentIds(parentId.mDocumentId);
35135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    if (storageDocumentIds.length == 1) {
35235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        final String newDocumentId =
35335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                                createDocument(storageDocumentIds[0], mimeType, displayName);
35435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        notifyChildDocumentsChange(parentDocumentId);
35535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        return newDocumentId;
35635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    } else {
35735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        throw new UnsupportedOperationException(
35835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                                "Cannot create a file under the device.");
35935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    }
36035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE:
36135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    storageId = parentId.mStorageId;
36235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    parentObjectHandle = -1;
36335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    break;
36435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT:
36535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    storageId = parentId.mStorageId;
36635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    parentObjectHandle = parentId.mObjectHandle;
36735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    break;
36835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                default:
36935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    throw new IllegalArgumentException("Unexpected document type.");
37035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            }
37135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono
372fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            pipe = ParcelFileDescriptor.createReliablePipe();
373fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            int objectHandle = -1;
374fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            MtpObjectInfo info = null;
375fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            try {
376fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[0].close();  // 0 bytes for a new document.
377fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
378fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
379fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MtpConstants.FORMAT_ASSOCIATION :
380fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MediaFile.getFormatCode(displayName, mimeType);
381fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                info = new MtpObjectInfo.Builder()
38235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        .setStorageId(storageId)
38335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        .setParent(parentObjectHandle)
384fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setFormat(formatCode)
385fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setName(displayName)
386fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .build();
387fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
388fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String[] parts = FileUtils.splitFileName(mimeType, displayName);
389fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String baseName = parts[0];
390fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String extension = parts[1];
391fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                for (int i = 0; i <= 32; i++) {
392fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    final MtpObjectInfo infoUniqueName;
393fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    if (i == 0) {
394fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        infoUniqueName = info;
395fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } else {
3964f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        String suffixedName = baseName + " (" + i + " )";
3974f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        if (!extension.isEmpty()) {
3984f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                            suffixedName += "." + extension;
3994f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        }
4004f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        infoUniqueName =
4014f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                                new MtpObjectInfo.Builder(info).setName(suffixedName).build();
402fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
403fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    try {
404fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        objectHandle = mMtpManager.createDocument(
405fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                                parentId.mDeviceId, infoUniqueName, pipe[1]);
406fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        break;
407fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } catch (SendObjectInfoFailure exp) {
408fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        // This can be caused when we have an existing file with the same name.
409fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        continue;
410fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
411fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                }
412fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            } finally {
413fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[1].close();
414fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
415fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            if (objectHandle == -1) {
416fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new IllegalArgumentException(
417fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "The file name \"" + displayName + "\" is conflicted with existing files " +
418fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "and the provider failed to find unique name.");
4190f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            }
4209e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo infoWithHandle =
4219e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
4229e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final String documentId = mDatabase.putNewDocument(
42361ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    parentId.mDeviceId, parentDocumentId, record.operationsSupported,
42464111e08d905525c7f4fe27e69953eb71bd62511Daichi Hirono                    infoWithHandle, 0l);
42576be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono            getDocumentLoader(parentId).cancelTask(parentId);
42687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            notifyChildDocumentsChange(parentDocumentId);
42787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            return documentId;
428fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        } catch (FileNotFoundException | RuntimeException error) {
429fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            Log.e(TAG, "createDocument", error);
430fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw error;
43187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        } catch (IOException error) {
4326a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(TAG, "createDocument", error);
433fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw new IllegalStateException(error);
43487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        }
43587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    }
43687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski
437b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono    @Override
438b690b4de06385a821aed3442e10058986c03badcGarfield Tan    public Path findDocumentPath(String parentDocumentId, String childDocumentId)
439b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            throws FileNotFoundException {
440b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        final LinkedList<String> ids = new LinkedList<>();
441b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        final Identifier childIdentifier = mDatabase.createIdentifier(childDocumentId);
442b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
443b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        Identifier i = childIdentifier;
444b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        outer: while (true) {
445b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            if (i.mDocumentId.equals(parentDocumentId)) {
446b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                ids.addFirst(i.mDocumentId);
447b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                break;
448b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            }
449b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            switch (i.mDocumentType) {
450b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT:
451b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    ids.addFirst(i.mDocumentId);
452b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    i = mDatabase.getParentIdentifier(i.mDocumentId);
453b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break;
454b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE: {
455b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // Check if there is the multiple storage.
456b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    final Identifier deviceIdentifier =
457b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                            mDatabase.getParentIdentifier(i.mDocumentId);
458b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    final String[] storageIds =
459b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                            mDatabase.getStorageDocumentIds(deviceIdentifier.mDocumentId);
460b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // Add storage's document ID to the path only when the device has multiple
461b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // storages.
462b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    if (storageIds.length > 1) {
463b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                        ids.addFirst(i.mDocumentId);
464b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                        break outer;
465b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    }
466b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    i = deviceIdentifier;
467b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break;
468b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                }
469b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE:
470b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    ids.addFirst(i.mDocumentId);
471b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break outer;
472b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            }
473b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        }
474b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
475b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        if (parentDocumentId != null) {
476b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            return new Path(null, ids);
477b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        } else {
478b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            return new Path(/* Should be same with root ID */ i.mDocumentId, ids);
479b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        }
480b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono    }
481b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
48229de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    @Override
48329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    public boolean isChildDocument(String parentDocumentId, String documentId) {
48429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        try {
48529de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            Identifier identifier = mDatabase.createIdentifier(documentId);
48629de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            while (true) {
48729de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                if (parentDocumentId.equals(identifier.mDocumentId)) {
48829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                    return true;
48929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                }
49029de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                if (identifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
49129de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                    return false;
49229de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                }
49329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                identifier = mDatabase.getParentIdentifier(identifier.mDocumentId);
49429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            }
49529de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        } catch (FileNotFoundException error) {
49629de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            return false;
49729de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        }
49829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    }
49929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono
5002efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
501e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
502fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            if (mDeviceToolkits.containsKey(deviceId)) {
503fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                return;
504fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
50519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            if (DEBUG) {
50619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono                Log.d(TAG, "Open device " + deviceId);
50719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            }
5080f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = mMtpManager.openDevice(deviceId);
5094e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            final DeviceToolkit toolkit =
51061ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    new DeviceToolkit(mMtpManager, mResolver, mDatabase, device);
5114e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            mDeviceToolkits.put(deviceId, toolkit);
512203be491ef479deea1f39f8b63f3e1916501a37aDaichi Hirono            mIntentSender.sendUpdateNotificationIntent(getOpenedDeviceRecordsCache());
513fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            try {
514fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                mRootScanner.resume().await();
515fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            } catch (InterruptedException error) {
516fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                Log.e(TAG, "openDevice", error);
517fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
5184e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // Resume document loader to remap disconnected document ID. Must be invoked after the
5194e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // root scanner resumes.
5204e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            toolkit.mDocumentLoader.resume();
521e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
522d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
523d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
524e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
525e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
526e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            closeDeviceInternal(deviceId);
527203be491ef479deea1f39f8b63f3e1916501a37aDaichi Hirono            mIntentSender.sendUpdateNotificationIntent(getOpenedDeviceRecordsCache());
528e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
52920754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono        mRootScanner.resume();
530d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
531d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
5320f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono    MtpDeviceRecord[] getOpenedDeviceRecordsCache() {
533e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
5340f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord[] records = new MtpDeviceRecord[mDeviceToolkits.size()];
5350f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            int i = 0;
5360f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
5370f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                records[i] = toolkit.mDeviceRecord;
5380f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                i++;
53920754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            }
5400f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            return records;
541e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
54250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
54350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
544e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
5451e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * Obtains document ID for the given device ID.
5461e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @param deviceId
5471e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @return document ID
5481e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @throws FileNotFoundException device ID has not been build.
5491e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     */
5501e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    public String getDeviceDocumentId(int deviceId) throws FileNotFoundException {
5511e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono        return mDatabase.getDeviceDocumentId(deviceId);
5521e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    }
5531e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono
5541e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    /**
555fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     * Resumes root scanner to handle the update of device list.
556fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     */
557fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    void resumeRootScanner() {
558ebd24051599280443435606cab220de33b9356adDaichi Hirono        if (DEBUG) {
559ebd24051599280443435606cab220de33b9356adDaichi Hirono            Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner");
560ebd24051599280443435606cab220de33b9356adDaichi Hirono        }
561fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mRootScanner.resume();
562fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    }
563fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono
564fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    /**
565e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
566e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
567e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
568e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
569e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
570e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            try {
5710f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                // Copy the opened key set because it will be modified when closing devices.
5720f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                final Integer[] keySet =
5730f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                        mDeviceToolkits.keySet().toArray(new Integer[mDeviceToolkits.size()]);
5740f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                for (final int id : keySet) {
575e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    closeDeviceInternal(id);
576e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                }
5772e9a57b0d4839b322787dad36bfcb64f211f7ac4Daichi Hirono                mRootScanner.pause();
578acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hirono            } catch (InterruptedException | IOException | TimeoutException e) {
579e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                // It should fail unit tests by throwing runtime exception.
580e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                throw new RuntimeException(e);
581e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } finally {
582e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                mDatabase.close();
583e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                super.shutdown();
584e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            }
585e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
586e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
587e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
5885fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
5895fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
5905fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
5915fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
5925fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
5935fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
5944c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
595e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    /**
596be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono     * Clears MTP identifier in the database.
597e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono     */
598e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void resume() {
599e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
600e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDatabase.getMapper().clearMapping();
601e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
602e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
603e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
604e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
605e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        // TODO: Flush the device before closing (if not closed externally).
606fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        if (!mDeviceToolkits.containsKey(deviceId)) {
607fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            return;
608fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        }
60919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
61019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "Close device " + deviceId);
61119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
61224ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        getDeviceToolkit(deviceId).close();
613e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mDeviceToolkits.remove(deviceId);
614e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mMtpManager.closeDevice(deviceId);
615e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
616e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
6174c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
618e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
619e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
620e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
621e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
622e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
623e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
6244c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
6254c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
6264c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
6274c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
6284c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
6294c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
6304c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
6314c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
6324c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
6334c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
6344c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
635f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private long getFileSize(String documentId) throws FileNotFoundException {
636f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(
637f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                documentId,
638f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME));
639f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        try {
640f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            if (cursor.moveToNext()) {
64177a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                if (cursor.isNull(0)) {
64277a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                    throw new UnsupportedOperationException();
64377a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                }
644f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                return cursor.getLong(0);
645f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            } else {
646f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                throw new FileNotFoundException();
647f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            }
648f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        } finally {
649f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            cursor.close();
650f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
651f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
652f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
6532965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    /**
6542965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * Creates empty cursor with specific error message.
6552965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     *
6562965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param projection Column names.
6572965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param stringResId String resource ID of error message.
6582965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @return Empty cursor with error message.
6592965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     */
6602965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    private Cursor createErrorCursor(String[] projection, int stringResId) {
6612965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Bundle bundle = new Bundle();
6622965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId));
6632965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Cursor cursor = new MatrixCursor(projection);
6642965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        cursor.setExtras(bundle);
6652965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        return cursor;
6662965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    }
6672965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
66824ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono    private static class DeviceToolkit implements AutoCloseable {
6694c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
6704c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
6710f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono        public final MtpDeviceRecord mDeviceRecord;
6724c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
67361ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono        public DeviceToolkit(MtpManager manager,
67461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             ContentResolver resolver,
67561ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDatabase database,
67661ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDeviceRecord record) {
677f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono            mPipeManager = new PipeManager(database);
67861ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono            mDocumentLoader = new DocumentLoader(record, manager, resolver, database);
6790f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            mDeviceRecord = record;
6804c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
68124ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono
68224ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        @Override
68324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        public void close() throws InterruptedException {
68424ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mPipeManager.close();
68524ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mDocumentLoader.close();
68624ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        }
6874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
688f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
689e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono    private class MtpProxyFileDescriptorCallback extends ProxyFileDescriptorCallback {
690e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        private final int mInode;
691e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        private MtpFileWriter mWriter;
692f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
693e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        MtpProxyFileDescriptorCallback(int inode) {
694e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            mInode = inode;
695f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        }
696f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
697f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
698e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public long onGetSize() throws ErrnoException {
699e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
700e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                return getFileSize(String.valueOf(mInode));
701e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (FileNotFoundException e) {
702e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
703e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onGetSize", OsConstants.ENOENT);
70477a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono            }
705e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        }
70677a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
707e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        @Override
708e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public int onRead(long offset, int size, byte[] data) throws ErrnoException {
709e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
710e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                final Identifier identifier = mDatabase.createIdentifier(Integer.toString(mInode));
711e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
712e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (MtpDeviceRecord.isSupported(
713e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                        record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) {
714e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
715e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                        return (int) mMtpManager.getPartialObject64(
716e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                                identifier.mDeviceId, identifier.mObjectHandle, offset, size, data);
71777a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
718e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
719e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported(
720e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                        record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) {
721e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    return (int) mMtpManager.getPartialObject(
722e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            identifier.mDeviceId, identifier.mObjectHandle, offset, size, data);
723e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
724e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onRead", OsConstants.ENOTSUP);
725e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (IOException e) {
726e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
727e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onRead", OsConstants.EIO);
728e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            }
729f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
730f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
731f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
732e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
733e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
734e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (mWriter == null) {
735e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    mWriter = new MtpFileWriter(mContext, String.valueOf(mInode));
736e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
737e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                return mWriter.write(offset, size, data);
738e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (IOException e) {
739e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
740e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onWrite", OsConstants.EIO);
741f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
742f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
74309ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono
74409ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono        @Override
745e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public void onFsync() throws ErrnoException {
746e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            tryFsync();
747f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        }
748f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
749f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        @Override
750e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public void onRelease() {
751f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            try {
752e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                tryFsync();
753e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (ErrnoException error) {
754e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                // Cannot recover from the error at onRelease. Client app should use fsync to
755e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                // ensure the provider writes data correctly.
756e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, "Cannot recover from the error at onRelease.", error);
757f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } finally {
758e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (mWriter != null) {
759e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    IoUtils.closeQuietly(mWriter);
760e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
761e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            }
762e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        }
763e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
764e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        private void tryFsync() throws ErrnoException {
765e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
766e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (mWriter != null) {
767e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    final MtpDeviceRecord device =
768e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            getDeviceToolkit(mDatabase.createIdentifier(
769e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                                    mWriter.getDocumentId()).mDeviceId).mDeviceRecord;
770e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    mWriter.flush(mMtpManager, mDatabase, device.operationsSupported);
771e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
772e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (IOException e) {
773e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
774e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onWrite", OsConstants.EIO);
775f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
77609ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono        }
777f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
778c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
779