MtpDocumentsProvider.java revision fda7474c5faae1e36a9274d8a5fe83e42ec6503b
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
61e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private final Object mDeviceListLock = new Object();
62e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
632efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
642efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
652efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
66d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
67e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    @GuardedBy("mDeviceListLock")
684c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private Map<Integer, DeviceToolkit> mDeviceToolkits;
698b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono    private RootScanner mRootScanner;
7017c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono    private Resources mResources;
71dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    private MtpDatabase mDatabase;
72f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private AppFuse mAppFuse;
73fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    private ServiceIntentSender mIntentSender;
74d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
752efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
762efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
772efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
782efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
792efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
802efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
812efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
82c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
83c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
842efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
8517c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
862efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
87d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
884c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
8947eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
90dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
91f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
92fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = new ServiceIntentSender(getContext());
93f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        // TODO: Mount AppFuse on demands.
94e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        try {
95e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            mAppFuse.mount(getContext().getSystemService(StorageManager.class));
96e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        } catch (IOException e) {
97e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", e);
98e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            return false;
99e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        }
100e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
101c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
102c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
103c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
104d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
105b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono    boolean onCreateForTesting(
106dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
107dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
108dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
109b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            MtpDatabase database,
110fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            StorageManager storageManager,
111fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            ServiceIntentSender intentSender) {
11217c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
1136baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
1146baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
1154c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
116dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
117dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
118b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
119fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = intentSender;
120b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        // TODO: Mount AppFuse on demands.
121b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        try {
122b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            mAppFuse.mount(storageManager);
123b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        } catch (IOException e) {
124b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", e);
125b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            return false;
126b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        }
127e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
128b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        return true;
129d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
130d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
131c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
132c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
13350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
13450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
13550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
136dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(projection);
13750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
13850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
13950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
140c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
141c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
142c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
143c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
144c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
145e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
146e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
147e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
1489e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        return mDatabase.queryDocument(documentId, projection);
149c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
150c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
151c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
152124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
153124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
154124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
155124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
156124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
1576a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono        Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
158124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
159fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentIdentifier.mDeviceId);
1606a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
1616a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                final Identifier singleStorageIdentifier =
1626a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                        mDatabase.getSingleStorageIdentifier(parentDocumentId);
1636a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                if (singleStorageIdentifier == null) {
1646a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    // Returns storage list from database.
1656a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    return mDatabase.queryChildDocuments(projection, parentDocumentId);
1666a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                }
1676a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                parentIdentifier = singleStorageIdentifier;
1686a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
1696a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            // Returns object list from document loader.
1704c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
1714c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
172124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
1736a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception);
174124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
175124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
176c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
177c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
178c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
1798ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
1808ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
1818ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
1829e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
1838ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
184fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
185b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            switch (mode) {
186b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "r":
187f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    final long fileSize = getFileSize(documentId);
188fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                    // MTP getPartialObject operation does not support files that are larger than
189fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                    // 4GB. Fallback to non-seekable file descriptor.
190f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // TODO: Use getPartialObject64 for MTP devices that support Android vendor
191f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // extension.
192b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono                    if (fileSize <= 0xffffffffl) {
193f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                        return mAppFuse.openFile(Integer.parseInt(documentId));
194f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    } else {
195f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                        return getPipeManager(identifier).readDocument(mMtpManager, identifier);
196f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    }
197b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "w":
19881d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // TODO: Clear the parent document loader task (if exists) and call notify
19981d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // when writing is completed.
2004c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    return getPipeManager(identifier).writeDocument(
2014c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                            getContext(), mMtpManager, identifier);
202f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                case "rw":
203f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // TODO: Add support for "rw" mode.
204b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
205f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                            "The provider does not support 'rw' mode.");
206f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                default:
207f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    throw new IllegalArgumentException("Unknown mode for openDocument: " + mode);
208b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
2098ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
2106a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
2118ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2128ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
213d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
214d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
2153faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2163faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
2173faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
2183faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
2193faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
2209e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2213faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
222fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2233faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
2244c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
225573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
2263faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
2273faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2286a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error);
2293faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2303faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
2313faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
2323faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
2333faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2343faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
2353faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
2369e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
237fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2386a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
2393faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
2409e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
2414c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
2429e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
2436a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
2446a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // If the parent is storage, the object might be appeared as child of device because
2456a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // we skip storage when the device has only one storage.
2466a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                final Identifier deviceIdentifier = mDatabase.getParentIdentifier(
2476a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                        parentIdentifier.mDocumentId);
2486a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                notifyChildDocumentsChange(deviceIdentifier.mDocumentId);
2496a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
2503faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2516a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error);
2523faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2533faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
2543faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
2553faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
2566baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
2576baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
258e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
259e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
260e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                toolkit.mDocumentLoader.clearCompletedTasks();
261e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
262e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
2636baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    }
2646baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono
26587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    @Override
26687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    public String createDocument(String parentDocumentId, String mimeType, String displayName)
26787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throws FileNotFoundException {
26887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        try {
2699e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier parentId = mDatabase.createIdentifier(parentDocumentId);
270fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentId.mDeviceId);
271df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski            final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe();
272df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski            pipe[0].close();  // 0 bytes for a new document.
2739e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
2749e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    MtpConstants.FORMAT_ASSOCIATION :
2759e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    MediaFile.getFormatCode(displayName, mimeType);
2769e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo info = new MtpObjectInfo.Builder()
2779e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setStorageId(parentId.mStorageId)
2789e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setParent(parentId.mObjectHandle)
2799e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setFormat(formatCode)
2809e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setName(displayName)
2819e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .build();
2829e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]);
2839e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo infoWithHandle =
2849e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
2859e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final String documentId = mDatabase.putNewDocument(
2869e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    parentId.mDeviceId, parentDocumentId, infoWithHandle);
2874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentId).clearTask(parentId);
28887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            notifyChildDocumentsChange(parentDocumentId);
28987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            return documentId;
29087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        } catch (IOException error) {
2916a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(TAG, "createDocument", error);
29287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throw new FileNotFoundException(error.getMessage());
29387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        }
29487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    }
29587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski
2962efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
297e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
298fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            if (mDeviceToolkits.containsKey(deviceId)) {
299fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                return;
300fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
301e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mMtpManager.openDevice(deviceId);
302e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDeviceToolkits.put(
303e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
304fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            mIntentSender.sendUpdateNotificationIntent();
305fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            try {
306fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                mRootScanner.resume().await();
307fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            } catch (InterruptedException error) {
308fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                Log.e(TAG, "openDevice", error);
309fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
310e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
311d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
312d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
313e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
314e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
315e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            closeDeviceInternal(deviceId);
316e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
31720754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono        mRootScanner.resume();
318fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender.sendUpdateNotificationIntent();
319d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
320d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
321a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    int[] getOpenedDeviceIds() {
322e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
323a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono            return mMtpManager.getOpenedDeviceIds();
324a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        }
325a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    }
326a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono
327a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    String getDeviceName(int deviceId) throws IOException {
328a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        synchronized (mDeviceListLock) {
32920754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            for (final MtpDeviceRecord device : mMtpManager.getDevices()) {
33020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                if (device.deviceId == deviceId) {
33120754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                    return device.name;
33220754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                }
33320754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            }
33420754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            throw new IOException("Not found the device: " + Integer.toString(deviceId));
335e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
33650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
33750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
338e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
339fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     * Resumes root scanner to handle the update of device list.
340fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     */
341fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    void resumeRootScanner() {
342fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mRootScanner.resume();
343fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    }
344fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono
345fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    /**
346e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
347e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
348e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
349e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
350e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
351e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            try {
352e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                for (final int id : mMtpManager.getOpenedDeviceIds()) {
353e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    closeDeviceInternal(id);
354e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                }
355e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } catch (InterruptedException|IOException e) {
356e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                // It should fail unit tests by throwing runtime exception.
357e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                throw new RuntimeException(e);
358e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } finally {
359e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                mDatabase.close();
360b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono                mAppFuse.close();
361e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                super.shutdown();
362e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            }
363e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
364e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
365e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
3665fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
3675fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
3685fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
3695fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
3705fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
3715fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
3724c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
373e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    /**
374be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono     * Clears MTP identifier in the database.
375e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono     */
376e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void resume() {
377e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
378e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDatabase.getMapper().clearMapping();
379e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
380e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
381e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
382e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
383e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        // TODO: Flush the device before closing (if not closed externally).
384fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        if (!mDeviceToolkits.containsKey(deviceId)) {
385fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            return;
386fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        }
387e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
388e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mDeviceToolkits.remove(deviceId);
389e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mMtpManager.closeDevice(deviceId);
390a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        if (getOpenedDeviceIds().length == 0) {
391e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mRootScanner.pause();
392e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
393e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
394e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
3954c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
396e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
397e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
398e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
399e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
400e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
401e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
4024c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
4034c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
4044c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
4054c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
4064c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
4074c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
4084c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
4094c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
4104c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
4114c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
4124c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
413f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private long getFileSize(String documentId) throws FileNotFoundException {
414f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(
415f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                documentId,
416f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME));
417f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        try {
418f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            if (cursor.moveToNext()) {
419f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                return cursor.getLong(0);
420f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            } else {
421f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                throw new FileNotFoundException();
422f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            }
423f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        } finally {
424f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            cursor.close();
425f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
426f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
427f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
4284c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private static class DeviceToolkit {
4294c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
4304c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
4314c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
43247eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) {
4334c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            mPipeManager = new PipeManager();
43447eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono            mDocumentLoader = new DocumentLoader(manager, resolver, database);
4354c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
4364c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
437f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
438f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private class AppFuseCallback implements AppFuse.Callback {
439f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
4402f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono        public long readObjectBytes(
4412f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono                int inode, long offset, long size, byte[] buffer) throws IOException {
442f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode));
4432f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono            return mMtpManager.getPartialObject(
4442f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono                    identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
445f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
446f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
447f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
448f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        public long getFileSize(int inode) throws FileNotFoundException {
449f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
450f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
451f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
452c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
453