MtpDocumentsProvider.java revision 20754c5a112e418c11cc88176283db2c4bf2efd6
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;
29c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document;
30c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root;
31bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract;
32c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider;
33d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log;
34d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
35e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy;
36d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting;
37d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
38c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException;
39d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException;
404c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap;
414c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map;
42c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
43d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/**
44d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices.
45d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */
46c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider {
472efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String AUTHORITY = "com.android.mtp.documents";
482efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String TAG = "MtpDocumentsProvider";
496baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
50c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
51c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
52c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_AVAILABLE_BYTES,
53c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
546baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
55c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
56c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
57c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
58c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
59c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
60e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private final Object mDeviceListLock = new Object();
61e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
622efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
632efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
642efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
65d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
66e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    @GuardedBy("mDeviceListLock")
674c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private Map<Integer, DeviceToolkit> mDeviceToolkits;
688b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono    private RootScanner mRootScanner;
6917c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono    private Resources mResources;
70dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    private MtpDatabase mDatabase;
71d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
722efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
732efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
742efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
752efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
762efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
772efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
782efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
79c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
80c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
812efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
8217c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
84d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
854c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
8647eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
87dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
88e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
89c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
90c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
91c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
92d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
93dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    void onCreateForTesting(
94dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
95dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
96dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
97dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpDatabase database) {
9817c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
996baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
1006baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
1014c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
102dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
103dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
104e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
105d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
106d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
107c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
108c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
10950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
11050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
11150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
112dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(projection);
11350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
11450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
11550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
116c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
117c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
118c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
119c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
120c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
121e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
122e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
123e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
1249e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        return mDatabase.queryDocument(documentId, projection);
125c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
126c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
127c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
128124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
129124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
130124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
131124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
132124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
13347eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        final Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
134124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
1354c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
1364c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
137124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
138124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
139124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
140c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
141c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
142c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
1438ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
1448ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
1458ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
1469e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
1478ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
148b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            switch (mode) {
149b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "r":
1504c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    return getPipeManager(identifier).readDocument(mMtpManager, identifier);
151b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "w":
15281d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // TODO: Clear the parent document loader task (if exists) and call notify
15381d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // when writing is completed.
1544c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    return getPipeManager(identifier).writeDocument(
1554c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                            getContext(), mMtpManager, identifier);
156b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                default:
157b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    // TODO: Add support for seekable files.
158b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
159b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                            "The provider does not support seekable file.");
160b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
1618ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
1628ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            throw new FileNotFoundException(error.getMessage());
1638ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
164d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
165d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
1663faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
1673faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
1683faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
1693faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
1703faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
1719e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
1723faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
1733faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
1744c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
175573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
1763faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
1773faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
1783faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
1793faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
1803faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
1813faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
1823faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
1833faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
1843faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
1859e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
1869e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier parentIdentifier =
1879e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    mDatabase.createIdentifier(mDatabase.getParentId(documentId));
1883faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
1899e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
1904c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
1919e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
1923faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
1933faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
1943faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
1953faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
1963faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
1976baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
1986baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
199e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
200e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
201e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                toolkit.mDocumentLoader.clearCompletedTasks();
202e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
203e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
2046baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    }
2056baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono
20687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    @Override
20787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    public String createDocument(String parentDocumentId, String mimeType, String displayName)
20887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throws FileNotFoundException {
20987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        try {
2109e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier parentId = mDatabase.createIdentifier(parentDocumentId);
211df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski            final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe();
212df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski            pipe[0].close();  // 0 bytes for a new document.
2139e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
2149e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    MtpConstants.FORMAT_ASSOCIATION :
2159e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    MediaFile.getFormatCode(displayName, mimeType);
2169e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo info = new MtpObjectInfo.Builder()
2179e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setStorageId(parentId.mStorageId)
2189e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setParent(parentId.mObjectHandle)
2199e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setFormat(formatCode)
2209e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setName(displayName)
2219e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .build();
2229e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]);
2239e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo infoWithHandle =
2249e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
2259e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final String documentId = mDatabase.putNewDocument(
2269e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    parentId.mDeviceId, parentDocumentId, infoWithHandle);
2274c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentId).clearTask(parentId);
22887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            notifyChildDocumentsChange(parentDocumentId);
22987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            return documentId;
23087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        } catch (IOException error) {
23187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            Log.e(TAG, error.getMessage());
23287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throw new FileNotFoundException(error.getMessage());
23387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        }
23487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    }
23587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski
2362efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
237e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
238e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mMtpManager.openDevice(deviceId);
239e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDeviceToolkits.put(
240e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
241e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
242e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        mRootScanner.resume();
243d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
244d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
245e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
246e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
247e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            closeDeviceInternal(deviceId);
248e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mDatabase.removeDeviceRows(deviceId);
249e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
25020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono        mRootScanner.resume();
251d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
252d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
253a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    int[] getOpenedDeviceIds() {
254e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
255a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono            return mMtpManager.getOpenedDeviceIds();
256a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        }
257a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    }
258a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono
259a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    String getDeviceName(int deviceId) throws IOException {
260a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        synchronized (mDeviceListLock) {
26120754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            for (final MtpDeviceRecord device : mMtpManager.getDevices()) {
26220754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                if (device.deviceId == deviceId) {
26320754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                    return device.name;
26420754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                }
26520754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            }
26620754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            throw new IOException("Not found the device: " + Integer.toString(deviceId));
267e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
26850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
26950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
270e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
271e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
272e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
273e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
274e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
275e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
276e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            try {
277e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                for (final int id : mMtpManager.getOpenedDeviceIds()) {
278e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    closeDeviceInternal(id);
279e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                }
280e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } catch (InterruptedException|IOException e) {
281e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                // It should fail unit tests by throwing runtime exception.
282e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                throw new RuntimeException(e);
283e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } finally {
284e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                mDatabase.close();
285e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                super.shutdown();
286e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            }
287e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
288e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
289e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
2905fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
2915fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
2925fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
2935fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
2945fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
2955fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
2964c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
297e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    /**
298be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono     * Clears MTP identifier in the database.
299e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono     */
300e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void resume() {
301e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
302e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDatabase.getMapper().clearMapping();
303e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
304e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
305e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
306e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
307e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        // TODO: Flush the device before closing (if not closed externally).
308e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
309e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mDeviceToolkits.remove(deviceId);
310e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mMtpManager.closeDevice(deviceId);
311a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        if (getOpenedDeviceIds().length == 0) {
312e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mRootScanner.pause();
313e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
314e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
315e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
3164c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
317e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
318e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
319e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
320e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
321e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
322e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
3234c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
3244c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
3254c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
3264c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
3274c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
3284c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
3294c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
3304c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
3314c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
3324c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
3334c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
3344c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private static class DeviceToolkit {
3354c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
3364c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
3374c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
33847eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) {
3394c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            mPipeManager = new PipeManager();
34047eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono            mDocumentLoader = new DocumentLoader(manager, resolver, database);
3414c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
3424c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
343c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
344