MtpDocumentsProvider.java revision f4e7fa80384ac72d0228ca5de6e949a9162cefbf
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;
20f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.content.Context;
213bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.content.UriPermission;
223faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.content.res.AssetFileDescriptor;
2317c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hironoimport android.content.res.Resources;
24c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor;
25c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.database.MatrixCursor;
265884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hironoimport android.database.sqlite.SQLiteDiskIOException;
273faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point;
289e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile;
299e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants;
30bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo;
313bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.net.Uri;
32c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.os.Bundle;
33c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal;
34fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hironoimport android.os.FileUtils;
35c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor;
36f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager;
37c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document;
38c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root;
39bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract;
40c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider;
413bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.provider.Settings;
42f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.system.ErrnoException;
43f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.system.Os;
44f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.system.OsConstants;
45d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log;
46d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
47e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy;
48d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting;
49d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
50f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport java.io.File;
51f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport java.io.FileDescriptor;
52c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException;
53d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException;
544c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap;
553bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport java.util.List;
564c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map;
57acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hironoimport java.util.concurrent.TimeoutException;
58c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
59d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/**
60d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices.
61d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */
62c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider {
632efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String AUTHORITY = "com.android.mtp.documents";
642efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String TAG = "MtpDocumentsProvider";
656baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
66c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
67c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
68c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_AVAILABLE_BYTES,
69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
706baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
71c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
72c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
73c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
74c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
75c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
76f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono    static final boolean DEBUG = false;
7719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono
78e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private final Object mDeviceListLock = new Object();
79e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
802efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
812efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
822efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
83d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
84e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    @GuardedBy("mDeviceListLock")
854c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private Map<Integer, DeviceToolkit> mDeviceToolkits;
868b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono    private RootScanner mRootScanner;
8717c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono    private Resources mResources;
88dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    private MtpDatabase mDatabase;
89f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private AppFuse mAppFuse;
90fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    private ServiceIntentSender mIntentSender;
91f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono    private Context mContext;
92d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
932efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
942efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
952efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
962efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
972efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
982efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
992efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
100c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
101c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
1022efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
103f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        mContext = getContext();
10417c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
1052efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
106d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
1074c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
10847eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
109f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
110f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
111fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = new ServiceIntentSender(getContext());
1123bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
1133bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider
1143bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // after booting.
1155884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        try {
1165884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1);
1175884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int lastBootCount = mDatabase.getLastBootCount();
1185884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            if (bootCount != -1 && bootCount != lastBootCount) {
1195884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.setLastBootCount(bootCount);
1205884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final List<UriPermission> permissions =
1215884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                        mResolver.getOutgoingPersistedUriPermissions();
1225884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final Uri[] uris = new Uri[permissions.size()];
1235884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                for (int i = 0; i < permissions.size(); i++) {
1245884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                    uris[i] = permissions.get(i).getUri();
1255884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                }
1265884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.cleanDatabase(uris);
1273bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono            }
1285884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        } catch (SQLiteDiskIOException error) {
1295884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            // It can happen due to disk shortage.
1305884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            Log.e(TAG, "Failed to clean database.", error);
1315884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            return false;
1323bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        }
1333bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
134f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        // TODO: Mount AppFuse on demands.
135e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        try {
136e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            mAppFuse.mount(getContext().getSystemService(StorageManager.class));
1375884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        } catch (IOException error) {
1385884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", error);
139e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono            return false;
140e6054c0ff07005c73bd10a362f039933dc00f8bdDaichi Hirono        }
1415884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono
142e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
143c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
144c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
145c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
146d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
147b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono    boolean onCreateForTesting(
148f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            Context context,
149dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
150dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
151dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
152b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            MtpDatabase database,
153fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            StorageManager storageManager,
154fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            ServiceIntentSender intentSender) {
155f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        mContext = context;
15617c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
1576baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
1586baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
1594c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
160dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
161f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
162b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
163fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = intentSender;
1643bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
165b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        // TODO: Mount AppFuse on demands.
166b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        try {
167b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            mAppFuse.mount(storageManager);
168b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        } catch (IOException e) {
169b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            Log.e(TAG, "Failed to start app fuse.", e);
170b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            return false;
171b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        }
172e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
173b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        return true;
174d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
175d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
176c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
177c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
17850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
17950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
18050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
181f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(mResources, projection);
18250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
18350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
18450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
185c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
186c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
187c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
188c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
189c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
190e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
191e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
192e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
1939e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        return mDatabase.queryDocument(documentId, projection);
194c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
195c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
196c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
197124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
198124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
19919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
20019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "queryChildDocuments: " + parentDocumentId);
20119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
202124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
203124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
204124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
2056a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono        Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
206124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
207fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentIdentifier.mDeviceId);
2086a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
2092965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId);
2102965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                if (storageDocIds.length == 0) {
2112965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    // Remote device does not provide storages. Maybe it is locked.
2122965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    return createErrorCursor(projection, R.string.error_locked_device);
2132965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                } else if (storageDocIds.length > 1) {
2146a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    // Returns storage list from database.
2156a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    return mDatabase.queryChildDocuments(projection, parentDocumentId);
2166a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                }
2172965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2182965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // Exact one storage is found. Skip storage and returns object in the single
2192965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // storage.
2202965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]);
2216a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
2222965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2236a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            // Returns object list from document loader.
2244c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
2254c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
226c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono        } catch (BusyDeviceException exception) {
2272965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono            return createErrorCursor(projection, R.string.error_busy_device);
228124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
2296a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception);
230124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
231124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
232c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
233c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
234c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
2358ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
2368ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
2378ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
2386213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
2396213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "openDocument: " + documentId);
2406213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
2419e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2428ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
243fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2440f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
245f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            // Turn off MODE_CREATE because openDocument does not allow to create new files.
246f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final int modeFlag =
247f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE;
248f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) {
249f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                long fileSize;
250f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                try {
251f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    fileSize = getFileSize(documentId);
252f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } catch (UnsupportedOperationException exception) {
253f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    fileSize = -1;
254f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
255f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                if (MtpDeviceRecord.isPartialReadSupported(
256f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                        device.operationsSupported, fileSize)) {
257f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
258f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } else {
259f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    // If getPartialObject{|64} are not supported for the device, returns
260f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    // non-seekable pipe FD instead.
261f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    return getPipeManager(identifier).readDocument(mMtpManager, identifier);
262f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
263f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) {
264f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // TODO: Clear the parent document loader task (if exists) and call notify
265f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // when writing is completed.
266f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
267f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
268f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } else {
269b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
270f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                            "The device does not support writing operation.");
271f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
272f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else {
273f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // TODO: Add support for "rw" mode.
274f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                throw new UnsupportedOperationException("The provider does not support 'rw' mode.");
275b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
276f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        } catch (FileNotFoundException | RuntimeException error) {
277f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
278f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            throw error;
2798ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
2806a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
281f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            throw new IllegalStateException(error);
2828ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
283d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
284d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
2853faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
2863faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
2873faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
2883faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
2893faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
2909e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2913faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
292fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2933faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
2944c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
295573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
2963faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
2973faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
2986a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error);
2993faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
3003faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
3013faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
3023faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
3033faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
3043faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
3053faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
3069e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
307fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
3086a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
3093faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
3109e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
3114c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
3129e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
3136a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
3146a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // If the parent is storage, the object might be appeared as child of device because
3156a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // we skip storage when the device has only one storage.
3166a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                final Identifier deviceIdentifier = mDatabase.getParentIdentifier(
3176a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                        parentIdentifier.mDocumentId);
3186a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                notifyChildDocumentsChange(deviceIdentifier.mDocumentId);
3196a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
3203faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
3216a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error);
3223faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
3233faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
3243faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
3253faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
3266baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
3276baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
328e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
329e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
330e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                toolkit.mDocumentLoader.clearCompletedTasks();
331e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
332e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
3336baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    }
3346baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono
33587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    @Override
33687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    public String createDocument(String parentDocumentId, String mimeType, String displayName)
33787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throws FileNotFoundException {
3386213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
3396213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "createDocument: " + displayName);
3406213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
341fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final Identifier parentId;
342fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final MtpDeviceRecord record;
343fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final ParcelFileDescriptor[] pipe;
34487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        try {
345fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            parentId = mDatabase.createIdentifier(parentDocumentId);
346fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentId.mDeviceId);
347fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            record = getDeviceToolkit(parentId.mDeviceId).mDeviceRecord;
3480f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            if (!MtpDeviceRecord.isWritingSupported(record.operationsSupported)) {
349fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new UnsupportedOperationException(
350fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "Writing operation is not supported by the device.");
351fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
352fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            pipe = ParcelFileDescriptor.createReliablePipe();
353fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            int objectHandle = -1;
354fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            MtpObjectInfo info = null;
355fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            try {
356fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[0].close();  // 0 bytes for a new document.
357fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
358fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
359fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MtpConstants.FORMAT_ASSOCIATION :
360fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MediaFile.getFormatCode(displayName, mimeType);
361fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                info = new MtpObjectInfo.Builder()
362fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setStorageId(parentId.mStorageId)
363fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setParent(parentId.mObjectHandle)
364fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setFormat(formatCode)
365fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setName(displayName)
366fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .build();
367fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
368fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String[] parts = FileUtils.splitFileName(mimeType, displayName);
369fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String baseName = parts[0];
370fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String extension = parts[1];
371fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                for (int i = 0; i <= 32; i++) {
372fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    final MtpObjectInfo infoUniqueName;
373fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    if (i == 0) {
374fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        infoUniqueName = info;
375fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } else {
3764f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        String suffixedName = baseName + " (" + i + " )";
3774f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        if (!extension.isEmpty()) {
3784f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                            suffixedName += "." + extension;
3794f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        }
3804f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        infoUniqueName =
3814f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                                new MtpObjectInfo.Builder(info).setName(suffixedName).build();
382fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
383fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    try {
384fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        objectHandle = mMtpManager.createDocument(
385fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                                parentId.mDeviceId, infoUniqueName, pipe[1]);
386fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        break;
387fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } catch (SendObjectInfoFailure exp) {
388fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        // This can be caused when we have an existing file with the same name.
389fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        continue;
390fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
391fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                }
392fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            } finally {
393fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[1].close();
394fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
395fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            if (objectHandle == -1) {
396fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new IllegalArgumentException(
397fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "The file name \"" + displayName + "\" is conflicted with existing files " +
398fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "and the provider failed to find unique name.");
3990f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            }
4009e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo infoWithHandle =
4019e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
4029e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final String documentId = mDatabase.putNewDocument(
40361ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    parentId.mDeviceId, parentDocumentId, record.operationsSupported,
40464111e08d905525c7f4fe27e69953eb71bd62511Daichi Hirono                    infoWithHandle, 0l);
4054c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            getDocumentLoader(parentId).clearTask(parentId);
40687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            notifyChildDocumentsChange(parentDocumentId);
40787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            return documentId;
408fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        } catch (FileNotFoundException | RuntimeException error) {
409fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            Log.e(TAG, "createDocument", error);
410fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw error;
41187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        } catch (IOException error) {
4126a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(TAG, "createDocument", error);
413fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw new IllegalStateException(error);
41487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        }
41587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    }
41687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski
4172efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
418e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
419fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            if (mDeviceToolkits.containsKey(deviceId)) {
420fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                return;
421fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
42219aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            if (DEBUG) {
42319aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono                Log.d(TAG, "Open device " + deviceId);
42419aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            }
4250f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = mMtpManager.openDevice(deviceId);
4264e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            final DeviceToolkit toolkit =
42761ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    new DeviceToolkit(mMtpManager, mResolver, mDatabase, device);
4284e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            mDeviceToolkits.put(deviceId, toolkit);
429fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            mIntentSender.sendUpdateNotificationIntent();
430fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            try {
431fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                mRootScanner.resume().await();
432fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            } catch (InterruptedException error) {
433fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                Log.e(TAG, "openDevice", error);
434fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
4354e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // Resume document loader to remap disconnected document ID. Must be invoked after the
4364e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // root scanner resumes.
4374e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            toolkit.mDocumentLoader.resume();
438e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
439d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
440d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
441e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
442e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
443e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            closeDeviceInternal(deviceId);
444e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
44520754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono        mRootScanner.resume();
446fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender.sendUpdateNotificationIntent();
447d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
448d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
4490f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono    MtpDeviceRecord[] getOpenedDeviceRecordsCache() {
450e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
4510f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord[] records = new MtpDeviceRecord[mDeviceToolkits.size()];
4520f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            int i = 0;
4530f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
4540f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                records[i] = toolkit.mDeviceRecord;
4550f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                i++;
45620754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            }
4570f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            return records;
458e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
45950d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
46050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
461e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
4621e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * Obtains document ID for the given device ID.
4631e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @param deviceId
4641e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @return document ID
4651e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @throws FileNotFoundException device ID has not been build.
4661e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     */
4671e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    public String getDeviceDocumentId(int deviceId) throws FileNotFoundException {
4681e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono        return mDatabase.getDeviceDocumentId(deviceId);
4691e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    }
4701e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono
4711e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    /**
472fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     * Resumes root scanner to handle the update of device list.
473fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     */
474fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    void resumeRootScanner() {
475ebd24051599280443435606cab220de33b9356adDaichi Hirono        if (DEBUG) {
476ebd24051599280443435606cab220de33b9356adDaichi Hirono            Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner");
477ebd24051599280443435606cab220de33b9356adDaichi Hirono        }
478fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mRootScanner.resume();
479fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    }
480fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono
481fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    /**
482e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
483e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
484e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
485e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
486e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
487e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            try {
4880f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                // Copy the opened key set because it will be modified when closing devices.
4890f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                final Integer[] keySet =
4900f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                        mDeviceToolkits.keySet().toArray(new Integer[mDeviceToolkits.size()]);
4910f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                for (final int id : keySet) {
492e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    closeDeviceInternal(id);
493e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                }
4942e9a57b0d4839b322787dad36bfcb64f211f7ac4Daichi Hirono                mRootScanner.pause();
495acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hirono            } catch (InterruptedException | IOException | TimeoutException e) {
496e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                // It should fail unit tests by throwing runtime exception.
497e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                throw new RuntimeException(e);
498e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } finally {
499e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                mDatabase.close();
500b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono                mAppFuse.close();
501e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                super.shutdown();
502e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            }
503e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
504e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
505e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
5065fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
5075fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
5085fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
5095fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
5105fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
5115fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
5124c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
513e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    /**
514be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono     * Clears MTP identifier in the database.
515e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono     */
516e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void resume() {
517e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
518e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDatabase.getMapper().clearMapping();
519e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
520e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
521e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
522e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
523e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        // TODO: Flush the device before closing (if not closed externally).
524fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        if (!mDeviceToolkits.containsKey(deviceId)) {
525fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            return;
526fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        }
52719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
52819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "Close device " + deviceId);
52919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
53024ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        getDeviceToolkit(deviceId).close();
531e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mDeviceToolkits.remove(deviceId);
532e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mMtpManager.closeDevice(deviceId);
533e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
534e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
5354c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
536e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
537e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
538e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
539e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
540e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
541e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
5424c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
5434c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
5444c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
5454c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
5464c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
5474c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
5484c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
5494c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
5504c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
5514c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
5524c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
553f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private long getFileSize(String documentId) throws FileNotFoundException {
554f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(
555f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                documentId,
556f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME));
557f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        try {
558f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            if (cursor.moveToNext()) {
55977a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                if (cursor.isNull(0)) {
56077a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                    throw new UnsupportedOperationException();
56177a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                }
562f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                return cursor.getLong(0);
563f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            } else {
564f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                throw new FileNotFoundException();
565f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            }
566f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        } finally {
567f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            cursor.close();
568f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
569f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
570f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
5712965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    /**
5722965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * Creates empty cursor with specific error message.
5732965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     *
5742965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param projection Column names.
5752965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param stringResId String resource ID of error message.
5762965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @return Empty cursor with error message.
5772965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     */
5782965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    private Cursor createErrorCursor(String[] projection, int stringResId) {
5792965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Bundle bundle = new Bundle();
5802965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId));
5812965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Cursor cursor = new MatrixCursor(projection);
5822965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        cursor.setExtras(bundle);
5832965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        return cursor;
5842965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    }
5852965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
58624ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono    private static class DeviceToolkit implements AutoCloseable {
5874c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
5884c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
5890f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono        public final MtpDeviceRecord mDeviceRecord;
5904c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
59161ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono        public DeviceToolkit(MtpManager manager,
59261ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             ContentResolver resolver,
59361ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDatabase database,
59461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDeviceRecord record) {
595f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono            mPipeManager = new PipeManager(database);
59661ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono            mDocumentLoader = new DocumentLoader(record, manager, resolver, database);
5970f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            mDeviceRecord = record;
5984c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
59924ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono
60024ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        @Override
60124ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        public void close() throws InterruptedException {
60224ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mPipeManager.close();
60324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mDocumentLoader.close();
60424ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        }
6054c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
606f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
607f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private class AppFuseCallback implements AppFuse.Callback {
608f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        private final Map<Long, MtpFileWriter> mWriters = new HashMap<>();
609f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
610f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        @Override
611f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        public long getFileSize(int inode) throws FileNotFoundException {
612f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
613f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        }
614f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
615f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
6162f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono        public long readObjectBytes(
6172f310f6d5d352817f42384394b50a660ad6e0bf8Daichi Hirono                int inode, long offset, long size, byte[] buffer) throws IOException {
618f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode));
6190f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
62077a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
62177a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono            if (MtpDeviceRecord.isSupported(
62277a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                    record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) {
62377a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                return mMtpManager.getPartialObject64(
62477a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                        identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
62577a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono            }
62677a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
62777a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono            if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported(
62877a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                    record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) {
6290f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                return mMtpManager.getPartialObject(
6300f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                        identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
6310f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            }
63277a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
63377a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono            throw new UnsupportedOperationException();
634f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
635f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
636f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
637f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        public int writeObjectBytes(
638f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                long fileHandle, int inode, long offset, int size, byte[] bytes)
639f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                throws IOException, ErrnoException {
640f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final MtpFileWriter writer;
641f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            if (mWriters.containsKey(fileHandle)) {
642f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                writer = mWriters.get(fileHandle);
643f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else {
644f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                writer = new MtpFileWriter(mContext, String.valueOf(inode));
645f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                mWriters.put(fileHandle, writer);
646f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
647f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            return writer.write(offset, size, bytes);
648f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
64909ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono
65009ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono        @Override
651f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        public void flushFileHandle(long fileHandle) throws IOException, ErrnoException {
652f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final MtpFileWriter writer = mWriters.get(fileHandle);
653f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            if (writer == null) {
654f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // File handle for reading.
655f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                return;
656f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
657f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final MtpDeviceRecord device = getDeviceToolkit(
658f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    mDatabase.createIdentifier(writer.getDocumentId()).mDeviceId).mDeviceRecord;
659f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            writer.flush(mMtpManager, mDatabase, device.operationsSupported);
660f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        }
661f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
662f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        @Override
663f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        public void closeFileHandle(long fileHandle) throws IOException, ErrnoException {
664f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final MtpFileWriter writer = mWriters.get(fileHandle);
665f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            if (writer == null) {
666f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // File handle for reading.
667f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                return;
668f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
669f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            try {
670f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                writer.close();
671f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } finally {
672f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                mWriters.remove(fileHandle);
673f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
67409ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono        }
675f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
676c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
677