MtpDocumentsProvider.java revision 19aa93249edc5dac01025456ce3bb1881f1b11d1
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;
233faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point;
249e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile;
259e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants;
26bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo;
27c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal;
28c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor;
29f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager;
30c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document;
31c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root;
32bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract;
33c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider;
34d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log;
35d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
36e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy;
37d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting;
38d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
39c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException;
40d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException;
414c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap;
424c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map;
43c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
44d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/**
45d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices.
46d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */
47c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider {
482efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String AUTHORITY = "com.android.mtp.documents";
492efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String TAG = "MtpDocumentsProvider";
506baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
51c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
52c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
53c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_AVAILABLE_BYTES,
54c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
556baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
56c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
57c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
58c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
59c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
60c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
6119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono    static final boolean DEBUG = true;
6219aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono
63e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private final Object mDeviceListLock = new Object();
64e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
652efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
662efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
672efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
68d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
69e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    @GuardedBy("mDeviceListLock")
704c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private Map<Integer, DeviceToolkit> mDeviceToolkits;
718b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono    private RootScanner mRootScanner;
7217c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono    private Resources mResources;
73dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    private MtpDatabase mDatabase;
74f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private AppFuse mAppFuse;
75fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    private ServiceIntentSender mIntentSender;
76d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
772efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
782efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
792efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
802efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
812efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
822efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
84c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
85c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
862efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
8717c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
882efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
89d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
904c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
9147eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
92dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
93f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
94fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = new ServiceIntentSender(getContext());
95f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        // TODO: Mount AppFuse on demands.
96e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        try {
97e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            mAppFuse.mount(getContext().getSystemService(StorageManager.class));
98e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        } catch (IOException e) {
99e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", e);
100e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            return false;
101e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        }
102e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
103c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
104c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
105c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
106d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
107b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono    boolean onCreateForTesting(
108dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
109dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
110dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
111b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            MtpDatabase database,
112fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            StorageManager storageManager,
113fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            ServiceIntentSender intentSender) {
11417c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
1156baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
1166baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
1174c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
118dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
119dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
120b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
121fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = intentSender;
122b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        // TODO: Mount AppFuse on demands.
123b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        try {
124b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            mAppFuse.mount(storageManager);
125b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        } catch (IOException e) {
126b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", e);
127b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            return false;
128b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        }
129e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
130b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        return true;
131d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
132d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
133c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
134c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
13550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
13650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
13750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
138dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(projection);
13950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
14050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
14150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
142c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
143c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
144c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
145c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
146c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
147e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
148e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
149e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
1509e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        return mDatabase.queryDocument(documentId, projection);
151c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
152c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
153c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
154124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
155124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
15619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
15719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "queryChildDocuments: " + parentDocumentId);
15819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
159124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
160124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
161124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
1626a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono        Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
163124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
164fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentIdentifier.mDeviceId);
1656a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
1666a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                final Identifier singleStorageIdentifier =
1676a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                        mDatabase.getSingleStorageIdentifier(parentDocumentId);
1686a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                if (singleStorageIdentifier == null) {
1696a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    // Returns storage list from database.
1706a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    return mDatabase.queryChildDocuments(projection, parentDocumentId);
1716a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                }
1726a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                parentIdentifier = singleStorageIdentifier;
1736a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
1746a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            // Returns object list from document loader.
1754c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
1764c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
177124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
1786a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception);
179124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
180124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
181c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
182c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
183c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
1848ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
1858ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
1868ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
1879e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
1888ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
189fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
190b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            switch (mode) {
191b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "r":
192f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    final long fileSize = getFileSize(documentId);
193fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                    // MTP getPartialObject operation does not support files that are larger than
194fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                    // 4GB. Fallback to non-seekable file descriptor.
195f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // TODO: Use getPartialObject64 for MTP devices that support Android vendor
196f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // extension.
197b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono                    if (fileSize <= 0xffffffffl) {
198f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                        return mAppFuse.openFile(Integer.parseInt(documentId));
199f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    } else {
200f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                        return getPipeManager(identifier).readDocument(mMtpManager, identifier);
201f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    }
202b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "w":
20381d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // TODO: Clear the parent document loader task (if exists) and call notify
20481d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // when writing is completed.
2054c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    return getPipeManager(identifier).writeDocument(
2064c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                            getContext(), mMtpManager, identifier);
207f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                case "rw":
208f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // TODO: Add support for "rw" mode.
209b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
210f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                            "The provider does not support 'rw' mode.");
211f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                default:
212f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    throw new IllegalArgumentException("Unknown mode for openDocument: " + mode);
213b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
2148ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
2156a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
2168ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2178ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
218d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
219d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
2203faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2213faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
2223faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
2233faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
2243faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
2259e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2263faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
227fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2283faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
2294c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
230573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
2313faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
2323faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2336a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error);
2343faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2353faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
2363faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
2373faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
2383faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2393faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
2403faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
2419e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
242fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2436a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
2443faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
2459e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
2464c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
2479e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
2486a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
2496a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // If the parent is storage, the object might be appeared as child of device because
2506a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // we skip storage when the device has only one storage.
2516a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                final Identifier deviceIdentifier = mDatabase.getParentIdentifier(
2526a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                        parentIdentifier.mDocumentId);
2536a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                notifyChildDocumentsChange(deviceIdentifier.mDocumentId);
2546a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
2553faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2566a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error);
2573faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2583faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
2593faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
2603faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
2616baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
2626baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
263e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
264e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
265e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                toolkit.mDocumentLoader.clearCompletedTasks();
266e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
267e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
2686baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    }
2696baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono
27087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    @Override
27187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    public String createDocument(String parentDocumentId, String mimeType, String displayName)
27287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throws FileNotFoundException {
27387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        try {
2749e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier parentId = mDatabase.createIdentifier(parentDocumentId);
275fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentId.mDeviceId);
276df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski            final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe();
277df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski            pipe[0].close();  // 0 bytes for a new document.
2789e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
2799e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    MtpConstants.FORMAT_ASSOCIATION :
2809e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    MediaFile.getFormatCode(displayName, mimeType);
2819e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo info = new MtpObjectInfo.Builder()
2829e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setStorageId(parentId.mStorageId)
2839e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setParent(parentId.mObjectHandle)
2849e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setFormat(formatCode)
2859e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setName(displayName)
2869e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .build();
2879e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]);
2889e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo infoWithHandle =
2899e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
2909e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final String documentId = mDatabase.putNewDocument(
2919e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    parentId.mDeviceId, parentDocumentId, infoWithHandle);
2924c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentId).clearTask(parentId);
29387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            notifyChildDocumentsChange(parentDocumentId);
29487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            return documentId;
29587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        } catch (IOException error) {
2966a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(TAG, "createDocument", error);
29787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throw new FileNotFoundException(error.getMessage());
29887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        }
29987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    }
30087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski
3012efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
302e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
303fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            if (mDeviceToolkits.containsKey(deviceId)) {
304fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                return;
305fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
30619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            if (DEBUG) {
30719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono                Log.d(TAG, "Open device " + deviceId);
30819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            }
309e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mMtpManager.openDevice(deviceId);
310e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDeviceToolkits.put(
311e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
312fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            mIntentSender.sendUpdateNotificationIntent();
313fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            try {
314fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                mRootScanner.resume().await();
315fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            } catch (InterruptedException error) {
316fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                Log.e(TAG, "openDevice", error);
317fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
318e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
319d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
320d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
321e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
322e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
323e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            closeDeviceInternal(deviceId);
324e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
32520754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono        mRootScanner.resume();
326fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender.sendUpdateNotificationIntent();
327d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
328d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
329a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    int[] getOpenedDeviceIds() {
330e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
331a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono            return mMtpManager.getOpenedDeviceIds();
332a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        }
333a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    }
334a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono
335a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    String getDeviceName(int deviceId) throws IOException {
336a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        synchronized (mDeviceListLock) {
33720754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            for (final MtpDeviceRecord device : mMtpManager.getDevices()) {
33820754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                if (device.deviceId == deviceId) {
33920754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                    return device.name;
34020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                }
34120754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            }
34220754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            throw new IOException("Not found the device: " + Integer.toString(deviceId));
343e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
34450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
34550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
346e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
347fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     * Resumes root scanner to handle the update of device list.
348fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     */
349fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    void resumeRootScanner() {
350fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mRootScanner.resume();
351fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    }
352fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono
353fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    /**
354e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
355e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
356e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
357e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
358e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
359e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            try {
360e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                for (final int id : mMtpManager.getOpenedDeviceIds()) {
361e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    closeDeviceInternal(id);
362e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                }
363e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } catch (InterruptedException|IOException e) {
364e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                // It should fail unit tests by throwing runtime exception.
365e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                throw new RuntimeException(e);
366e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } finally {
367e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                mDatabase.close();
368b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono                mAppFuse.close();
369e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                super.shutdown();
370e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            }
371e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
372e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
373e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
3745fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
3755fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
3765fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
3775fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
3785fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
3795fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
3804c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
381e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    /**
382be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono     * Clears MTP identifier in the database.
383e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono     */
384e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void resume() {
385e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
386e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDatabase.getMapper().clearMapping();
387e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
388e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
389e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
390e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
391e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        // TODO: Flush the device before closing (if not closed externally).
392fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        if (!mDeviceToolkits.containsKey(deviceId)) {
393fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            return;
394fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        }
39519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
39619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "Close device " + deviceId);
39719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
398e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
399e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mDeviceToolkits.remove(deviceId);
400e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mMtpManager.closeDevice(deviceId);
401a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        if (getOpenedDeviceIds().length == 0) {
402e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mRootScanner.pause();
403e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
404e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
405e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
4064c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
407e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
408e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
409e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
410e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
411e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
412e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
4134c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
4144c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
4154c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
4164c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
4174c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
4184c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
4194c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
4204c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
4214c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
4224c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
4234c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
424f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private long getFileSize(String documentId) throws FileNotFoundException {
425f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(
426f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                documentId,
427f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME));
428f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        try {
429f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            if (cursor.moveToNext()) {
430f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                return cursor.getLong(0);
431f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            } else {
432f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                throw new FileNotFoundException();
433f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            }
434f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        } finally {
435f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            cursor.close();
436f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
437f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
438f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
4394c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private static class DeviceToolkit {
4404c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
4414c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
4424c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
44347eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) {
4444c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            mPipeManager = new PipeManager();
44547eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono            mDocumentLoader = new DocumentLoader(manager, resolver, database);
4464c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
4474c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
448f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
449f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private class AppFuseCallback implements AppFuse.Callback {
450f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
4512f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono        public long readObjectBytes(
4522f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono                int inode, long offset, long size, byte[] buffer) throws IOException {
453f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode));
4542f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono            return mMtpManager.getPartialObject(
4552f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono                    identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
456f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
457f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
458f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
459f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        public long getFileSize(int inode) throws FileNotFoundException {
460f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
461f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
462f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
463c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
464