MtpDocumentsProvider.java revision 2f310f6d5d352817f42384394b50a660ad6e0bf8
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;
73d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
742efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
752efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
762efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
772efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
782efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
792efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
802efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
81c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
82c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
8417c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
852efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
86d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
8847eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
89dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
90f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
91f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        // TODO: Mount AppFuse on demands.
92e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        try {
93e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            mAppFuse.mount(getContext().getSystemService(StorageManager.class));
94e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        } catch (IOException e) {
95e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", e);
96e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            return false;
97e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        }
98e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
99c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
100c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
101c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
102d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
103b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono    boolean onCreateForTesting(
104dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
105dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
106dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
107b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            MtpDatabase database,
108b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            StorageManager storageManager) {
10917c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
1106baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
1116baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
1124c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
113dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
114dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
115b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
116b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        // TODO: Mount AppFuse on demands.
117b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        try {
118b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            mAppFuse.mount(storageManager);
119b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        } catch (IOException e) {
120b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", e);
121b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            return false;
122b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        }
123e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
124b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        return true;
125d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
126d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
127c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
128c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
12950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
13050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
13150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
132dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(projection);
13350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
13450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
13550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
136c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
137c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
138c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
139c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
140c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
141e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
142e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
143e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
1449e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        return mDatabase.queryDocument(documentId, projection);
145c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
146c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
147c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
148124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
149124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
150124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
151124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
152124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
15347eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        final Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
154124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
1554c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
1564c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
157124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
158124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
159124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
160c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
161c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
162c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
1638ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
1648ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
1658ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
1669e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
1678ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
168b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            switch (mode) {
169b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "r":
170f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    final long fileSize = getFileSize(documentId);
171f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // MTP getPartialObject operation does not support files that are larger than 4GB.
172f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // Fallback to non-seekable file descriptor.
173f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // TODO: Use getPartialObject64 for MTP devices that support Android vendor
174f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // extension.
175b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono                    if (fileSize <= 0xffffffffl) {
176f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                        return mAppFuse.openFile(Integer.parseInt(documentId));
177f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    } else {
178f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                        return getPipeManager(identifier).readDocument(mMtpManager, identifier);
179f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    }
180b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                case "w":
18181d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // TODO: Clear the parent document loader task (if exists) and call notify
18281d74743107b372424fb8f7439357bdd608f8cafTomasz Mikolajewski                    // when writing is completed.
1834c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    return getPipeManager(identifier).writeDocument(
1844c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                            getContext(), mMtpManager, identifier);
185f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                case "rw":
186f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    // TODO: Add support for "rw" mode.
187b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
188f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                            "The provider does not support 'rw' mode.");
189f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                default:
190f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                    throw new IllegalArgumentException("Unknown mode for openDocument: " + mode);
191b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
1928ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
1938ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            throw new FileNotFoundException(error.getMessage());
1948ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
195d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
196d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
1973faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
1983faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
1993faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
2003faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
2013faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
2029e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2033faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
2043faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
2054c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
206573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
2073faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
2083faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2093faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2103faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
2113faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
2123faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
2133faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2143faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
2153faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
2169e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
2179e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier parentIdentifier =
2189e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    mDatabase.createIdentifier(mDatabase.getParentId(documentId));
2193faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
2209e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
2214c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
2229e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
2233faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2243faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
2253faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
2263faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
2273faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
2286baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
2296baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
230e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
231e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
232e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                toolkit.mDocumentLoader.clearCompletedTasks();
233e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
234e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
2356baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    }
2366baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono
23787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    @Override
23887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    public String createDocument(String parentDocumentId, String mimeType, String displayName)
23987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throws FileNotFoundException {
24087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        try {
2419e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier parentId = mDatabase.createIdentifier(parentDocumentId);
242df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski            final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe();
243df544176b10f536969de1ed143b0ba57123fcb93Tomasz Mikolajewski            pipe[0].close();  // 0 bytes for a new document.
2449e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
2459e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    MtpConstants.FORMAT_ASSOCIATION :
2469e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    MediaFile.getFormatCode(displayName, mimeType);
2479e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo info = new MtpObjectInfo.Builder()
2489e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setStorageId(parentId.mStorageId)
2499e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setParent(parentId.mObjectHandle)
2509e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setFormat(formatCode)
2519e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .setName(displayName)
2529e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    .build();
2539e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]);
2549e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo infoWithHandle =
2559e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
2569e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final String documentId = mDatabase.putNewDocument(
2579e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    parentId.mDeviceId, parentDocumentId, infoWithHandle);
2584c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentId).clearTask(parentId);
25987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            notifyChildDocumentsChange(parentDocumentId);
26087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            return documentId;
26187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        } catch (IOException error) {
26287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            Log.e(TAG, error.getMessage());
26387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throw new FileNotFoundException(error.getMessage());
26487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        }
26587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    }
26687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski
2672efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
268e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
269e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            mMtpManager.openDevice(deviceId);
270e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDeviceToolkits.put(
271e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
272e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
273e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        mRootScanner.resume();
274d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
275d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
276e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
277e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
278e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            closeDeviceInternal(deviceId);
279e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
28020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono        mRootScanner.resume();
281d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
282d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
283a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    int[] getOpenedDeviceIds() {
284e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
285a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono            return mMtpManager.getOpenedDeviceIds();
286a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        }
287a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    }
288a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono
289a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono    String getDeviceName(int deviceId) throws IOException {
290a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        synchronized (mDeviceListLock) {
29120754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            for (final MtpDeviceRecord device : mMtpManager.getDevices()) {
29220754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                if (device.deviceId == deviceId) {
29320754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                    return device.name;
29420754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono                }
29520754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            }
29620754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            throw new IOException("Not found the device: " + Integer.toString(deviceId));
297e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
29850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
29950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
300e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
301e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
302e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
303e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
304e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
305e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
306e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            try {
307e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                for (final int id : mMtpManager.getOpenedDeviceIds()) {
308e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    closeDeviceInternal(id);
309e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                }
310e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } catch (InterruptedException|IOException e) {
311e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                // It should fail unit tests by throwing runtime exception.
312e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                throw new RuntimeException(e);
313e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } finally {
314e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                mDatabase.close();
315b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono                mAppFuse.close();
316e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                super.shutdown();
317e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            }
318e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
319e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
320e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
3215fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
3225fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
3235fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
3245fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
3255fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
3265fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
3274c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
328e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    /**
329be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono     * Clears MTP identifier in the database.
330e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono     */
331e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void resume() {
332e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
333e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDatabase.getMapper().clearMapping();
334e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
335e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
336e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
337e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
338e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        // TODO: Flush the device before closing (if not closed externally).
339e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
340e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mDeviceToolkits.remove(deviceId);
341e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mMtpManager.closeDevice(deviceId);
342a57d9ed09003acd8b2beb0494a2bd32f7030cc11Daichi Hirono        if (getOpenedDeviceIds().length == 0) {
343e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mRootScanner.pause();
344e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
345e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
346e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
3474c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
348e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
349e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
350e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
351e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
352e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
353e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
3544c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
3554c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
3564c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
3574c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
3584c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
3594c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
3604c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
3614c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
3624c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
3634c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
3644c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
365f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private long getFileSize(String documentId) throws FileNotFoundException {
366f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(
367f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                documentId,
368f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME));
369f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        try {
370f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            if (cursor.moveToNext()) {
371f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                return cursor.getLong(0);
372f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            } else {
373f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                throw new FileNotFoundException();
374f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            }
375f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        } finally {
376f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            cursor.close();
377f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
378f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
379f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
3804c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private static class DeviceToolkit {
3814c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
3824c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
3834c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
38447eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) {
3854c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            mPipeManager = new PipeManager();
38647eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono            mDocumentLoader = new DocumentLoader(manager, resolver, database);
3874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
3884c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
389f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
390f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private class AppFuseCallback implements AppFuse.Callback {
391f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
3922f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono        public long readObjectBytes(
3932f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono                int inode, long offset, long size, byte[] buffer) throws IOException {
394f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode));
3952f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono            return mMtpManager.getPartialObject(
3962f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono                    identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
397f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
398f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
399f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
400f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        public long getFileSize(int inode) throws FileNotFoundException {
401f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
402f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
403f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
404c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
405