MtpDocumentsProvider.java revision 9e8a4fa78f5b9e3964dca84ad4047210d35c4013
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;
2350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hironoimport android.database.MatrixCursor;
243faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point;
259e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile;
269e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants;
27bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo;
28c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal;
29c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor;
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
612efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
622efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
632efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
64d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
65e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @GuardedBy("mDeviceToolkits")
664c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private Map<Integer, DeviceToolkit> mDeviceToolkits;
678b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono    private RootScanner mRootScanner;
6817c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono    private Resources mResources;
69dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    private MtpDatabase mDatabase;
70d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
712efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
722efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
732efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
742efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
752efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
762efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
772efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
78c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
79c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
802efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
8117c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
822efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
83d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
844c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
8547eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
86dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
87c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
88c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
89c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
90d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
91dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    void onCreateForTesting(
92dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
93dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
94dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
95dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpDatabase database) {
9617c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
976baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
986baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
994c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
100dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
101dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
102d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
103d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
104c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
105c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
10650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
10750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
10850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
109dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(projection);
11050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
11150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
11250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
113c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
114c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
115c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
116c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
117c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
118e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
119e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
120e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
1219e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        return mDatabase.queryDocument(documentId, projection);
122c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
123c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
124c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
125124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
126124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
127124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
128124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
129124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
13047eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        final Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
131124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
1324c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
1334c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
134124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
135124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
136124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
137c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
138c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
139c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
1408ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
1418ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
1428ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
1439e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
1448ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
145b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            switch (mode) {
146b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "r":
1474c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    return getPipeManager(identifier).readDocument(mMtpManager, identifier);
148b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "w":
14981d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // TODO: Clear the parent document loader task (if exists) and call notify
15081d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // when writing is completed.
1514c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    return getPipeManager(identifier).writeDocument(
1524c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                            getContext(), mMtpManager, identifier);
153b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                default:
154b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    // TODO: Add support for seekable files.
155b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
156b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                            "The provider does not support seekable file.");
157b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
1588ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
1598ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            throw new FileNotFoundException(error.getMessage());
1608ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
161d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
162d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
1633faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
1643faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
1653faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
1663faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
1673faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
1689e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
1693faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
1703faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
1714c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
172573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
1733faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
1743faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
1753faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
1763faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
1773faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
1783faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
1793faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
1803faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
1813faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
1829e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
1839e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier parentIdentifier =
1849e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    mDatabase.createIdentifier(mDatabase.getParentId(documentId));
1853faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
1869e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
1874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
1889e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
1893faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
1909e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            for (final StackTraceElement element : error.getStackTrace()) {
1919e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                Log.e("hirono", element.toString());
1929e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            }
1933faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
1943faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
1953faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
1963faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
1976baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
1986baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
199e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        synchronized (mDeviceToolkits) {
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 {
237e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        synchronized (mDeviceToolkits) {
238e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mMtpManager.openDevice(deviceId);
239e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
240e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
241e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        mRootScanner.resume();
242d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
243d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
244e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
2454c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        // TODO: Flush the device before closing (if not closed externally).
246e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        synchronized (mDeviceToolkits) {
247e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
248e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mDeviceToolkits.remove(deviceId);
249e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mDatabase.removeDeviceRows(deviceId);
250e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mMtpManager.closeDevice(deviceId);
251e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
252dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mRootScanner.notifyChange();
253e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        if (!hasOpenedDevices()) {
254e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mRootScanner.pause();
255e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
256d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
257d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
258e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    synchronized void closeAllDevices() throws InterruptedException {
259d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        boolean closed = false;
2602efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        for (int deviceId : mMtpManager.getOpenedDeviceIds()) {
261d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono            try {
262dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono                mDatabase.removeDeviceRows(deviceId);
2632efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono                mMtpManager.closeDevice(deviceId);
2644c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
265d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono                closed = true;
266d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono            } catch (IOException d) {
267d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono                Log.d(TAG, "Failed to close the MTP device: " + deviceId);
268d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono            }
269d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        }
270d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        if (closed) {
271dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            mRootScanner.notifyChange();
272e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mRootScanner.pause();
273d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        }
274d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
275d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
27650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    boolean hasOpenedDevices() {
27750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return mMtpManager.getOpenedDeviceIds().length != 0;
27850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
27950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
280e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
281e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
282e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
283e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
284e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
285e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        try {
286e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            closeAllDevices();
287e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        } catch (InterruptedException e) {
288e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            // It should fail unit tests by throwing runtime exception.
289e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            throw new RuntimeException(e.getMessage());
290e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        } finally {
291e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mDatabase.close();
292e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            super.shutdown();
293e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
294e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
295e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
2965fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
2975fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
2985fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
2995fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
3005fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
3015fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
3024c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
3034c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
304e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        synchronized (mDeviceToolkits) {
305e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
306e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
307e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
308e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
309e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
3104c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
3114c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
3124c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
3134c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
3144c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
3154c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
3164c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
3174c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
3184c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
3194c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
3204c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
3214c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private static class DeviceToolkit {
3224c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
3234c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
3244c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
32547eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) {
3264c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            mPipeManager = new PipeManager();
32747eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono            mDocumentLoader = new DocumentLoader(manager, resolver, database);
3284c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
3294c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
330c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
331