MtpDocumentsProvider.java revision 50d17aa871d9ca645a8e7af64df8866b85aee245
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;
20c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor;
2150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hironoimport android.database.MatrixCursor;
22c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal;
23c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor;
24d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.provider.DocumentsContract;
25c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document;
26c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root;
27c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider;
28d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log;
29d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
30d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting;
31d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
32c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException;
33d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException;
34c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
35d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/**
36d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices.
37d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */
38c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider {
392efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String AUTHORITY = "com.android.mtp.documents";
402efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String TAG = "MtpDocumentsProvider";
41c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
42c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
43c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
44c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_AVAILABLE_BYTES,
45c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
46c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
47c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
48c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
49c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
50c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
51c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
522efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
532efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
542efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
55d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
56d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
572efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
582efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
592efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
602efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
612efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
622efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
632efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
64c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
65c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
662efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
672efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
68d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
70c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
71c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
72d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
732efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void onCreateForTesting(MtpManager mtpManager, ContentResolver resolver) {
742efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        this.mMtpManager = mtpManager;
75d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        this.mResolver = resolver;
76d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
77d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
78c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
79c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
8050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
8150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
8250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
8350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        final MatrixCursor cursor = new MatrixCursor(projection);
8450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        for (final int deviceId : mMtpManager.getOpenedDeviceIds()) {
8550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            try {
8650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                final MtpRoot[] roots = mMtpManager.getRoots(deviceId);
8750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                // TODO: Add retry logic here.
8850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
8950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                for (final MtpRoot root : roots) {
9050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                    final String rootId = Identifier.createRootId(deviceId, root.mStorageId);
9150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                    final MatrixCursor.RowBuilder builder = cursor.newRow();
9250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                    builder.add(Root.COLUMN_ROOT_ID, rootId);
9350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                    builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD);
9450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                    builder.add(Root.COLUMN_TITLE, root.mDescription);
9550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                    builder.add(
9650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                            Root.COLUMN_DOCUMENT_ID,
9750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                            Identifier.createDocumentId(rootId, MtpDocument.DUMMY_HANDLE_FOR_ROOT));
9850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                    builder.add(Root.COLUMN_AVAILABLE_BYTES , root.mFreeSpace);
9950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                }
10050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            } catch (IOException error) {
10150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                Log.d(TAG, error.getMessage());
10250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            }
10350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
10450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
10550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
10650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
107c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
108c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
109c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
110c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
111c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
112d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        throw new FileNotFoundException();
113c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
114c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
115c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
116c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
117c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            String[] projection, String sortOrder)
118c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
119d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        throw new FileNotFoundException();
120c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
121c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
122c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
123c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public ParcelFileDescriptor openDocument(String documentId, String mode,
124c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            CancellationSignal signal) throws FileNotFoundException {
125d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        throw new FileNotFoundException();
126d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
127d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
1282efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
1292efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager.openDevice(deviceId);
13050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        notifyRootsChange();
131d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
132d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
1332efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void closeDevice(int deviceId) throws IOException {
1342efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager.closeDevice(deviceId);
13550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        notifyRootsChange();
136d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
137d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
138d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    void closeAllDevices() {
139d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        boolean closed = false;
1402efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        for (int deviceId : mMtpManager.getOpenedDeviceIds()) {
141d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono            try {
1422efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono                mMtpManager.closeDevice(deviceId);
143d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono                closed = true;
144d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono            } catch (IOException d) {
145d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono                Log.d(TAG, "Failed to close the MTP device: " + deviceId);
146d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono            }
147d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        }
148d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        if (closed) {
14950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            notifyRootsChange();
150d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        }
151d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
152d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
15350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    boolean hasOpenedDevices() {
15450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return mMtpManager.getOpenedDeviceIds().length != 0;
15550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
15650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
15750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    private void notifyRootsChange() {
158d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver.notifyChange(
159d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono                DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY),
160d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono                null,
161d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono                false);
162c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
163c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
164