MtpDocumentsProvider.java revision 4e94b8deaa646f176bad9b80d5924ce64142743e
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;
203faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.content.res.AssetFileDescriptor;
2117c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hironoimport android.content.res.Resources;
22c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor;
23c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.database.MatrixCursor;
243faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point;
259e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile;
269e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants;
27bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo;
28c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.os.Bundle;
29c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal;
30c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor;
31f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager;
32c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document;
33c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root;
34bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract;
35c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider;
36d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log;
37d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
38e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy;
39d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting;
40c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport com.android.mtp.exceptions.BusyDeviceException;
41d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
42c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException;
43d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException;
444c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap;
454c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map;
46c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
47d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/**
48d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices.
49d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */
50c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider {
512efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String AUTHORITY = "com.android.mtp.documents";
522efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String TAG = "MtpDocumentsProvider";
536baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
54c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
55c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
56c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_AVAILABLE_BYTES,
57c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
586baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
59c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
60c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
61c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
62c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
63c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
64f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono    static final boolean DEBUG = false;
6519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono
66e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private final Object mDeviceListLock = new Object();
67e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
682efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
692efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
702efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
71d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
72e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    @GuardedBy("mDeviceListLock")
734c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private Map<Integer, DeviceToolkit> mDeviceToolkits;
748b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono    private RootScanner mRootScanner;
7517c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono    private Resources mResources;
76dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    private MtpDatabase mDatabase;
77f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private AppFuse mAppFuse;
78fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    private ServiceIntentSender mIntentSender;
79d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
802efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
812efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
822efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
842efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
852efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
862efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
87c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
88c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
892efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
9017c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
912efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
92d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
934c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
9447eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
95f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
96f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
97fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = new ServiceIntentSender(getContext());
98f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        // TODO: Mount AppFuse on demands.
99e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        try {
100e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            mAppFuse.mount(getContext().getSystemService(StorageManager.class));
101e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        } catch (IOException e) {
102e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", e);
103e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            return false;
104e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        }
105e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
106c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
107c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
108c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
109d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
110b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono    boolean onCreateForTesting(
111dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
112dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
113dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
114b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            MtpDatabase database,
115fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            StorageManager storageManager,
116fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            ServiceIntentSender intentSender) {
11717c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
1186baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
1196baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
1204c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
121dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
122f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
123b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
124fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = intentSender;
125b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        // TODO: Mount AppFuse on demands.
126b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        try {
127b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            mAppFuse.mount(storageManager);
128b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        } catch (IOException e) {
129b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", e);
130b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            return false;
131b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        }
132e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
133b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        return true;
134d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
135d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
136c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
137c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
13850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
13950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
14050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
141f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(mResources, projection);
14250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
14350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
14450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
145c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
146c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
147c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
148c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
149c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
150e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
151e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
152e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
1539e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        return mDatabase.queryDocument(documentId, projection);
154c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
155c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
156c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
157124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
158124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
15919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
16019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "queryChildDocuments: " + parentDocumentId);
16119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
162124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
163124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
164124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
1656a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono        Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
166124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
167fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentIdentifier.mDeviceId);
1686a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
1692965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId);
1702965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                if (storageDocIds.length == 0) {
1712965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    // Remote device does not provide storages. Maybe it is locked.
1722965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    return createErrorCursor(projection, R.string.error_locked_device);
1732965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                } else if (storageDocIds.length > 1) {
1746a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    // Returns storage list from database.
1756a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    return mDatabase.queryChildDocuments(projection, parentDocumentId);
1766a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                }
1772965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
1782965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // Exact one storage is found. Skip storage and returns object in the single
1792965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // storage.
1802965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]);
1816a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
1822965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
1836a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            // Returns object list from document loader.
1844c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
1854c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
186c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono        } catch (BusyDeviceException exception) {
1872965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono            return createErrorCursor(projection, R.string.error_busy_device);
188124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
1896a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception);
190124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
191124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
192c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
193c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
194c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
1958ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
1968ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
1978ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
1986213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
1996213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "openDocument: " + documentId);
2006213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
2019e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2028ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
203fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
204b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            switch (mode) {
205b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "r":
206f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    final long fileSize = getFileSize(documentId);
207fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                    // MTP getPartialObject operation does not support files that are larger than
208fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                    // 4GB. Fallback to non-seekable file descriptor.
209f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // TODO: Use getPartialObject64 for MTP devices that support Android vendor
210f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // extension.
211b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono                    if (fileSize <= 0xffffffffl) {
212f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                        return mAppFuse.openFile(Integer.parseInt(documentId));
213f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    } else {
214f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                        return getPipeManager(identifier).readDocument(mMtpManager, identifier);
215f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    }
216b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "w":
21781d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // TODO: Clear the parent document loader task (if exists) and call notify
21881d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // when writing is completed.
2194c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    return getPipeManager(identifier).writeDocument(
2204c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                            getContext(), mMtpManager, identifier);
221f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                case "rw":
222f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // TODO: Add support for "rw" mode.
223b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
224f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                            "The provider does not support 'rw' mode.");
225f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                default:
226f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    throw new IllegalArgumentException("Unknown mode for openDocument: " + mode);
227b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
2288ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
2296a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
2308ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2318ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
232d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
233d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
2343faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2353faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
2363faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
2373faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
2383faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
2399e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2403faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
241fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2423faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
2434c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
244573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
2453faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
2463faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2476a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error);
2483faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2493faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
2503faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
2513faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
2523faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2533faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
2543faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
2559e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
256fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2576a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
2583faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
2599e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
2604c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
2619e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
2626a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
2636a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // If the parent is storage, the object might be appeared as child of device because
2646a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // we skip storage when the device has only one storage.
2656a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                final Identifier deviceIdentifier = mDatabase.getParentIdentifier(
2666a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                        parentIdentifier.mDocumentId);
2676a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                notifyChildDocumentsChange(deviceIdentifier.mDocumentId);
2686a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
2693faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2706a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error);
2713faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2723faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
2733faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
2743faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
2756baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
2766baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
277e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
278e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
279e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                toolkit.mDocumentLoader.clearCompletedTasks();
280e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
281e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
2826baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    }
2836baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono
28487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    @Override
28587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    public String createDocument(String parentDocumentId, String mimeType, String displayName)
28687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throws FileNotFoundException {
2876213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
2886213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "createDocument: " + displayName);
2896213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
29087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        try {
2919e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier parentId = mDatabase.createIdentifier(parentDocumentId);
292fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentId.mDeviceId);
293df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski            final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe();
294df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski            pipe[0].close();  // 0 bytes for a new document.
2959e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
2969e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    MtpConstants.FORMAT_ASSOCIATION :
2979e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    MediaFile.getFormatCode(displayName, mimeType);
2989e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo info = new MtpObjectInfo.Builder()
2999e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setStorageId(parentId.mStorageId)
3009e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setParent(parentId.mObjectHandle)
3019e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setFormat(formatCode)
3029e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setName(displayName)
3039e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .build();
3049e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]);
3059e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo infoWithHandle =
3069e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
3079e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final String documentId = mDatabase.putNewDocument(
3089e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    parentId.mDeviceId, parentDocumentId, infoWithHandle);
3094c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentId).clearTask(parentId);
31087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            notifyChildDocumentsChange(parentDocumentId);
31187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            return documentId;
31287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        } catch (IOException error) {
3136a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(TAG, "createDocument", error);
31487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throw new FileNotFoundException(error.getMessage());
31587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        }
31687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    }
31787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski
3182efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
319e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
320fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            if (mDeviceToolkits.containsKey(deviceId)) {
321fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                return;
322fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
32319aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            if (DEBUG) {
32419aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono                Log.d(TAG, "Open device " + deviceId);
32519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            }
326e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mMtpManager.openDevice(deviceId);
3274e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            final DeviceToolkit toolkit =
3284e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono                    new DeviceToolkit(deviceId, mMtpManager, mResolver, mDatabase);
3294e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            mDeviceToolkits.put(deviceId, toolkit);
330fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            mIntentSender.sendUpdateNotificationIntent();
331fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            try {
332fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                mRootScanner.resume().await();
333fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            } catch (InterruptedException error) {
334fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                Log.e(TAG, "openDevice", error);
335fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
3364e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // Resume document loader to remap disconnected document ID. Must be invoked after the
3374e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // root scanner resumes.
3384e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            toolkit.mDocumentLoader.resume();
339e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
340d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
341d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
342e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
343e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
344e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            closeDeviceInternal(deviceId);
345e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
34620754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono        mRootScanner.resume();
347fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender.sendUpdateNotificationIntent();
348d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
349d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
350a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    int[] getOpenedDeviceIds() {
351e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
352a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono            return mMtpManager.getOpenedDeviceIds();
353a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        }
354a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    }
355a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono
356a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    String getDeviceName(int deviceId) throws IOException {
357a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        synchronized (mDeviceListLock) {
35820754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            for (final MtpDeviceRecord device : mMtpManager.getDevices()) {
35920754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                if (device.deviceId == deviceId) {
36020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                    return device.name;
36120754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                }
36220754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            }
36320754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            throw new IOException("Not found the device: " + Integer.toString(deviceId));
364e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
36550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
36650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
367e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
3681e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * Obtains document ID for the given device ID.
3691e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @param deviceId
3701e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @return document ID
3711e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @throws FileNotFoundException device ID has not been build.
3721e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     */
3731e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    public String getDeviceDocumentId(int deviceId) throws FileNotFoundException {
3741e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono        return mDatabase.getDeviceDocumentId(deviceId);
3751e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    }
3761e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono
3771e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    /**
378fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     * Resumes root scanner to handle the update of device list.
379fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     */
380fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    void resumeRootScanner() {
381ebd24051599280443435606cab220de33b9356adDaichi Hirono        if (DEBUG) {
382ebd24051599280443435606cab220de33b9356adDaichi Hirono            Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner");
383ebd24051599280443435606cab220de33b9356adDaichi Hirono        }
384fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mRootScanner.resume();
385fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    }
386fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono
387fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    /**
388e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
389e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
390e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
391e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
392e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
393e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            try {
394e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                for (final int id : mMtpManager.getOpenedDeviceIds()) {
395e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    closeDeviceInternal(id);
396e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                }
397e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } catch (InterruptedException|IOException e) {
398e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                // It should fail unit tests by throwing runtime exception.
399e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                throw new RuntimeException(e);
400e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } finally {
401e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                mDatabase.close();
402b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono                mAppFuse.close();
403e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                super.shutdown();
404e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            }
405e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
406e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
407e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
4085fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
4095fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
4105fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
4115fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
4125fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
4135fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
4144c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
415e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    /**
416be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono     * Clears MTP identifier in the database.
417e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono     */
418e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void resume() {
419e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
420e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDatabase.getMapper().clearMapping();
421e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
422e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
423e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
424e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
425e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        // TODO: Flush the device before closing (if not closed externally).
426fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        if (!mDeviceToolkits.containsKey(deviceId)) {
427fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            return;
428fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        }
42919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
43019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "Close device " + deviceId);
43119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
4324e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono        getDeviceToolkit(deviceId).mDocumentLoader.close();
433e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mDeviceToolkits.remove(deviceId);
434e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mMtpManager.closeDevice(deviceId);
435a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        if (getOpenedDeviceIds().length == 0) {
436e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mRootScanner.pause();
437e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
438e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
439e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
4404c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
441e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
442e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
443e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
444e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
445e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
446e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
4474c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
4484c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
4494c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
4504c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
4514c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
4524c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
4534c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
4544c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
4554c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
4564c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
4574c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
458f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private long getFileSize(String documentId) throws FileNotFoundException {
459f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(
460f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                documentId,
461f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME));
462f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        try {
463f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            if (cursor.moveToNext()) {
464f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                return cursor.getLong(0);
465f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            } else {
466f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                throw new FileNotFoundException();
467f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            }
468f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        } finally {
469f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            cursor.close();
470f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
471f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
472f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
4732965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    /**
4742965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * Creates empty cursor with specific error message.
4752965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     *
4762965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param projection Column names.
4772965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param stringResId String resource ID of error message.
4782965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @return Empty cursor with error message.
4792965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     */
4802965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    private Cursor createErrorCursor(String[] projection, int stringResId) {
4812965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Bundle bundle = new Bundle();
4822965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId));
4832965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Cursor cursor = new MatrixCursor(projection);
4842965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        cursor.setExtras(bundle);
4852965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        return cursor;
4862965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    }
4872965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
4884c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private static class DeviceToolkit {
4894c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
4904c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
4914c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
4924e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono        public DeviceToolkit(
4934e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono                int deviceId, MtpManager manager, ContentResolver resolver, MtpDatabase database) {
494f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono            mPipeManager = new PipeManager(database);
4954e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            mDocumentLoader = new DocumentLoader(deviceId, manager, resolver, database);
4964c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
4974c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
498f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
499f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private class AppFuseCallback implements AppFuse.Callback {
500f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
5012f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono        public long readObjectBytes(
5022f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono                int inode, long offset, long size, byte[] buffer) throws IOException {
503f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode));
5042f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono            return mMtpManager.getPartialObject(
5052f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono                    identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
506f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
507f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
508f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
509f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        public long getFileSize(int inode) throws FileNotFoundException {
510f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
511f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
512f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
513c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
514