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
195a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKayimport android.annotation.Nullable;
20d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.content.ContentResolver;
2166fcb4beae9605cd034c9437e510b21260d6f519Daichi Hironoimport android.content.ContentValues;
22f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.content.Context;
233bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.content.UriPermission;
243faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.content.res.AssetFileDescriptor;
2517c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hironoimport android.content.res.Resources;
26c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor;
2766fcb4beae9605cd034c9437e510b21260d6f519Daichi Hironoimport android.database.DatabaseUtils;
28c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.database.MatrixCursor;
295884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hironoimport android.database.sqlite.SQLiteDiskIOException;
303faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point;
319e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile;
329e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants;
33bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo;
343bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.net.Uri;
35c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.os.Bundle;
36c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal;
37fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hironoimport android.os.FileUtils;
38c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor;
39e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport android.os.ProxyFileDescriptorCallback;
40f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager;
415a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKayimport android.provider.DocumentsContract;
42c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document;
43b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport android.provider.DocumentsContract.Path;
44c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root;
45c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider;
465a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKayimport android.provider.MetadataReader;
473bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.provider.Settings;
48f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.system.ErrnoException;
49e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport android.system.OsConstants;
50d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log;
51d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
52e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy;
53d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting;
54d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
555a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKayimport libcore.io.IoUtils;
565a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay
57c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException;
58d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException;
595a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKayimport java.io.InputStream;
604c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap;
61b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport java.util.LinkedList;
623bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport java.util.List;
634c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map;
64acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hironoimport java.util.concurrent.TimeoutException;
65e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
66d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/**
67d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices.
68d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */
69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider {
702efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String AUTHORITY = "com.android.mtp.documents";
712efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String TAG = "MtpDocumentsProvider";
726baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
73c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
74c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
75c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_AVAILABLE_BYTES,
76c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
776baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
78c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
79c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
80c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
81c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
82c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
83f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono    static final boolean DEBUG = false;
8419aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono
85e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private final Object mDeviceListLock = new Object();
86e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
872efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
882efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
892efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
90d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
91e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    @GuardedBy("mDeviceListLock")
924c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private Map<Integer, DeviceToolkit> mDeviceToolkits;
938b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono    private RootScanner mRootScanner;
9417c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono    private Resources mResources;
95dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    private MtpDatabase mDatabase;
96fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    private ServiceIntentSender mIntentSender;
97f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono    private Context mContext;
98e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono    private StorageManager mStorageManager;
99d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
1002efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
1012efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
1022efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
1032efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
1042efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
1052efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
1062efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
107c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
108c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
1092efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
110f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        mContext = getContext();
11117c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
1122efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
113d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
1145a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay        mDeviceToolkits = new HashMap<>();
11547eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
116f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
117fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = new ServiceIntentSender(getContext());
118e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        mStorageManager = getContext().getSystemService(StorageManager.class);
1193bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
1203bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider
1213bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // after booting.
1225884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        try {
1235884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1);
1245884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int lastBootCount = mDatabase.getLastBootCount();
1255884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            if (bootCount != -1 && bootCount != lastBootCount) {
1265884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.setLastBootCount(bootCount);
1275884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final List<UriPermission> permissions =
1285884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                        mResolver.getOutgoingPersistedUriPermissions();
1295884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final Uri[] uris = new Uri[permissions.size()];
1305884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                for (int i = 0; i < permissions.size(); i++) {
1315884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                    uris[i] = permissions.get(i).getUri();
1325884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                }
1335884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.cleanDatabase(uris);
1343bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono            }
1355884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        } catch (SQLiteDiskIOException error) {
1365884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            // It can happen due to disk shortage.
1375884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            Log.e(TAG, "Failed to clean database.", error);
1385884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            return false;
1393bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        }
1403bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
141e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
142c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
143c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
144c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
145d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
146b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono    boolean onCreateForTesting(
147f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            Context context,
148dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
149dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
150dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
151b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            MtpDatabase database,
152fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            StorageManager storageManager,
153fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            ServiceIntentSender intentSender) {
154f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        mContext = context;
15517c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
1566baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
1576baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
1585a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay        mDeviceToolkits = new HashMap<>();
159dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
160f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
161fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = intentSender;
162e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        mStorageManager = storageManager;
1633bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
164e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
165b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        return true;
166d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
167d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
168c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
169c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
17050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
17150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
17250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
173f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(mResources, projection);
17450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
17550d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
17650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
177c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
178c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
179c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
180c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
181c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
182e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
183e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
184e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
18566fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(documentId, projection);
18666fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        final int cursorCount = cursor.getCount();
18766fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        if (cursorCount == 0) {
18866fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            cursor.close();
18966fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            throw new FileNotFoundException();
19066fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        } else if (cursorCount != 1) {
19166fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            cursor.close();
19266fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            Log.wtf(TAG, "Unexpected cursor size: " + cursorCount);
19366fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            return null;
19466fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        }
19566fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono
19666fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
19766fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        if (identifier.mDocumentType != MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
19866fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            return cursor;
19966fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        }
20066fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        final String[] storageDocIds = mDatabase.getStorageDocumentIds(documentId);
20166fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        if (storageDocIds.length != 1) {
20266fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            return mDatabase.queryDocument(documentId, projection);
20366fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        }
20466fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono
20566fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        // If the documentId specifies a device having exact one storage, we repalce some device
20666fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        // attributes with the storage attributes.
20766fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        try {
20866fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            final String storageName;
20966fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            final int storageFlags;
21066fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            try (final Cursor storageCursor = mDatabase.queryDocument(
21166fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono                    storageDocIds[0],
21266fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono                    MtpDatabase.strings(Document.COLUMN_DISPLAY_NAME, Document.COLUMN_FLAGS))) {
21366fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono                if (!storageCursor.moveToNext()) {
21466fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono                    throw new FileNotFoundException();
21566fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono                }
21666fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono                storageName = storageCursor.getString(0);
21766fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono                storageFlags = storageCursor.getInt(1);
21866fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            }
21966fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono
22066fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            cursor.moveToNext();
22166fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            final ContentValues values = new ContentValues();
22266fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            DatabaseUtils.cursorRowToContentValues(cursor, values);
22366fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            if (values.containsKey(Document.COLUMN_DISPLAY_NAME)) {
22466fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono                values.put(Document.COLUMN_DISPLAY_NAME, mResources.getString(
22566fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono                        R.string.root_name,
22666fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono                        values.getAsString(Document.COLUMN_DISPLAY_NAME),
22766fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono                        storageName));
22866fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            }
22966fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            values.put(Document.COLUMN_FLAGS, storageFlags);
23066fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            final MatrixCursor output = new MatrixCursor(projection, 1);
23166fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            MtpDatabase.putValuesToCursor(values, output);
23266fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            return output;
23366fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        } finally {
23466fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono            cursor.close();
23566fcb4beae9605cd034c9437e510b21260d6f519Daichi Hirono        }
236c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
237c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
238c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
239124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
240124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
24119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
24219aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "queryChildDocuments: " + parentDocumentId);
24319aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
244124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
245124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
246124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
2476a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono        Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
248124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
249fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentIdentifier.mDeviceId);
2506a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
2512965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId);
2522965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                if (storageDocIds.length == 0) {
2532965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    // Remote device does not provide storages. Maybe it is locked.
2542965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    return createErrorCursor(projection, R.string.error_locked_device);
2552965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                } else if (storageDocIds.length > 1) {
2566a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    // Returns storage list from database.
2576a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    return mDatabase.queryChildDocuments(projection, parentDocumentId);
2586a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                }
2592965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2602965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // Exact one storage is found. Skip storage and returns object in the single
2612965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // storage.
2622965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]);
2636a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
2642965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2656a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            // Returns object list from document loader.
2664c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
2674c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
268c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono        } catch (BusyDeviceException exception) {
2692965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono            return createErrorCursor(projection, R.string.error_busy_device);
270124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
2716a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception);
272124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
273124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
274c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
275c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
276c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
2778ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
2788ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
2798ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
2806213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
2816213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "openDocument: " + documentId);
2826213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
2839e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2848ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
285fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2860f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
287f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            // Turn off MODE_CREATE because openDocument does not allow to create new files.
288f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final int modeFlag =
289f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE;
290f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) {
291f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                long fileSize;
292f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                try {
293f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    fileSize = getFileSize(documentId);
294f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } catch (UnsupportedOperationException exception) {
295f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    fileSize = -1;
296f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
297f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                if (MtpDeviceRecord.isPartialReadSupported(
298f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                        device.operationsSupported, fileSize)) {
299e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
300e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    return mStorageManager.openProxyFileDescriptor(
301e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            modeFlag,
302e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId)));
303f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } else {
304f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    // If getPartialObject{|64} are not supported for the device, returns
305f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    // non-seekable pipe FD instead.
306f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    return getPipeManager(identifier).readDocument(mMtpManager, identifier);
307f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
308f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) {
309f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // TODO: Clear the parent document loader task (if exists) and call notify
310f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // when writing is completed.
311f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
312e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    return mStorageManager.openProxyFileDescriptor(
313e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            modeFlag,
314e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId)));
315f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } else {
316b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
317f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                            "The device does not support writing operation.");
318f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
319f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else {
320f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // TODO: Add support for "rw" mode.
321f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                throw new UnsupportedOperationException("The provider does not support 'rw' mode.");
322b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
323f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        } catch (FileNotFoundException | RuntimeException error) {
324f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
325f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            throw error;
3268ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
3276a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
328f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            throw new IllegalStateException(error);
3298ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
330d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
331d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
3323faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
3333faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
3343faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
3353faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
3363faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
3379e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
3383faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
339fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
3403faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
3414c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
342573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
3433faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
3443faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
3456a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error);
3463faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
3473faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
3483faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
3493faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
3503faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
3513faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
3523faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
3539e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
354fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
3556a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
3563faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
3579e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
35876be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono            getDocumentLoader(parentIdentifier).cancelTask(parentIdentifier);
3599e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
3606a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
3616a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // If the parent is storage, the object might be appeared as child of device because
3626a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // we skip storage when the device has only one storage.
3636a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                final Identifier deviceIdentifier = mDatabase.getParentIdentifier(
3646a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                        parentIdentifier.mDocumentId);
3656a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                notifyChildDocumentsChange(deviceIdentifier.mDocumentId);
3666a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
3673faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
3686a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error);
3693faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
3703faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
3713faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
3723faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
3736baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
3746baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
375e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
376e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
377e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                toolkit.mDocumentLoader.clearCompletedTasks();
378e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
379e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
3806baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    }
3816baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono
38287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    @Override
38387763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    public String createDocument(String parentDocumentId, String mimeType, String displayName)
38487763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throws FileNotFoundException {
3856213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
3866213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "createDocument: " + displayName);
3876213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
388fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final Identifier parentId;
389fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final MtpDeviceRecord record;
390fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final ParcelFileDescriptor[] pipe;
39187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        try {
392fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            parentId = mDatabase.createIdentifier(parentDocumentId);
393fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentId.mDeviceId);
394fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            record = getDeviceToolkit(parentId.mDeviceId).mDeviceRecord;
3950f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            if (!MtpDeviceRecord.isWritingSupported(record.operationsSupported)) {
396fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new UnsupportedOperationException(
397fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "Writing operation is not supported by the device.");
398fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
39935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono
40035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            final int parentObjectHandle;
40135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            final int storageId;
40235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            switch (parentId.mDocumentType) {
40335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE:
40435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    final String[] storageDocumentIds =
40535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                            mDatabase.getStorageDocumentIds(parentId.mDocumentId);
40635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    if (storageDocumentIds.length == 1) {
40735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        final String newDocumentId =
40835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                                createDocument(storageDocumentIds[0], mimeType, displayName);
40935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        notifyChildDocumentsChange(parentDocumentId);
41035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        return newDocumentId;
41135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    } else {
41235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        throw new UnsupportedOperationException(
41335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                                "Cannot create a file under the device.");
41435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    }
41535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE:
41635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    storageId = parentId.mStorageId;
41735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    parentObjectHandle = -1;
41835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    break;
41935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT:
42035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    storageId = parentId.mStorageId;
42135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    parentObjectHandle = parentId.mObjectHandle;
42235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    break;
42335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                default:
42435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    throw new IllegalArgumentException("Unexpected document type.");
42535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            }
42635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono
427fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            pipe = ParcelFileDescriptor.createReliablePipe();
428fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            int objectHandle = -1;
429fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            MtpObjectInfo info = null;
430fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            try {
431fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[0].close();  // 0 bytes for a new document.
432fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
433fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
434fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MtpConstants.FORMAT_ASSOCIATION :
435fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MediaFile.getFormatCode(displayName, mimeType);
436fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                info = new MtpObjectInfo.Builder()
43735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        .setStorageId(storageId)
43835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        .setParent(parentObjectHandle)
439fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setFormat(formatCode)
440fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setName(displayName)
441fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .build();
442fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
443fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String[] parts = FileUtils.splitFileName(mimeType, displayName);
444fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String baseName = parts[0];
445fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String extension = parts[1];
446fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                for (int i = 0; i <= 32; i++) {
447fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    final MtpObjectInfo infoUniqueName;
448fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    if (i == 0) {
449fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        infoUniqueName = info;
450fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } else {
4514f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        String suffixedName = baseName + " (" + i + " )";
4524f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        if (!extension.isEmpty()) {
4534f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                            suffixedName += "." + extension;
4544f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        }
4554f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        infoUniqueName =
4564f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                                new MtpObjectInfo.Builder(info).setName(suffixedName).build();
457fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
458fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    try {
459fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        objectHandle = mMtpManager.createDocument(
460fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                                parentId.mDeviceId, infoUniqueName, pipe[1]);
461fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        break;
462fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } catch (SendObjectInfoFailure exp) {
463fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        // This can be caused when we have an existing file with the same name.
464fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        continue;
465fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
466fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                }
467fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            } finally {
468fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[1].close();
469fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
470fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            if (objectHandle == -1) {
471fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new IllegalArgumentException(
472fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "The file name \"" + displayName + "\" is conflicted with existing files " +
473fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "and the provider failed to find unique name.");
4740f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            }
4759e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo infoWithHandle =
4769e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
4779e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final String documentId = mDatabase.putNewDocument(
47861ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    parentId.mDeviceId, parentDocumentId, record.operationsSupported,
47964111e08d905525c7f4fe27e69953eb71bd62511Daichi Hirono                    infoWithHandle, 0l);
48076be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono            getDocumentLoader(parentId).cancelTask(parentId);
48187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            notifyChildDocumentsChange(parentDocumentId);
48287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            return documentId;
483fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        } catch (FileNotFoundException | RuntimeException error) {
484fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            Log.e(TAG, "createDocument", error);
485fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw error;
48687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        } catch (IOException error) {
4876a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(TAG, "createDocument", error);
488fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw new IllegalStateException(error);
48987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        }
49087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    }
49187763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski
492b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono    @Override
493b690b4de06385a821aed3442e10058986c03badcGarfield Tan    public Path findDocumentPath(String parentDocumentId, String childDocumentId)
494b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            throws FileNotFoundException {
495b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        final LinkedList<String> ids = new LinkedList<>();
496b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        final Identifier childIdentifier = mDatabase.createIdentifier(childDocumentId);
497b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
498b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        Identifier i = childIdentifier;
499b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        outer: while (true) {
500b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            if (i.mDocumentId.equals(parentDocumentId)) {
501b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                ids.addFirst(i.mDocumentId);
502b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                break;
503b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            }
504b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            switch (i.mDocumentType) {
505b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT:
506b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    ids.addFirst(i.mDocumentId);
507b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    i = mDatabase.getParentIdentifier(i.mDocumentId);
508b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break;
509b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE: {
510b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // Check if there is the multiple storage.
511b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    final Identifier deviceIdentifier =
512b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                            mDatabase.getParentIdentifier(i.mDocumentId);
513b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    final String[] storageIds =
514b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                            mDatabase.getStorageDocumentIds(deviceIdentifier.mDocumentId);
515b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // Add storage's document ID to the path only when the device has multiple
516b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // storages.
517b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    if (storageIds.length > 1) {
518b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                        ids.addFirst(i.mDocumentId);
519b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                        break outer;
520b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    }
521b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    i = deviceIdentifier;
522b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break;
523b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                }
524b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE:
525b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    ids.addFirst(i.mDocumentId);
526b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break outer;
527b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            }
528b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        }
529b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
530b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        if (parentDocumentId != null) {
531b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            return new Path(null, ids);
532b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        } else {
533b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            return new Path(/* Should be same with root ID */ i.mDocumentId, ids);
534b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        }
535b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono    }
536b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
53729de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    @Override
53829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    public boolean isChildDocument(String parentDocumentId, String documentId) {
53929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        try {
54029de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            Identifier identifier = mDatabase.createIdentifier(documentId);
54129de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            while (true) {
54229de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                if (parentDocumentId.equals(identifier.mDocumentId)) {
54329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                    return true;
54429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                }
54529de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                if (identifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
54629de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                    return false;
54729de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                }
54829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                identifier = mDatabase.getParentIdentifier(identifier.mDocumentId);
54929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            }
55029de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        } catch (FileNotFoundException error) {
55129de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            return false;
55229de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        }
55329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    }
55429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono
5555a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay    @Override
5565a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay    public @Nullable Bundle getDocumentMetadata(String docId) throws FileNotFoundException {
5575a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay        String mimeType = getDocumentType(docId);
5585a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay
5595a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay        if (!MetadataReader.isSupportedMimeType(mimeType)) {
5605a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay            return null;
5615a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay        }
5625a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay
5635a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay        InputStream stream = null;
5645a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay        try {
5655a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay            stream = new ParcelFileDescriptor.AutoCloseInputStream(
5665a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay                    openDocument(docId, "r", null));
5675a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay            Bundle metadata = new Bundle();
5685a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay            MetadataReader.getMetadata(metadata, stream, mimeType, null);
5695a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay            return metadata;
5705a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay        } catch (IOException e) {
5715a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay            Log.e(TAG, "An error occurred retrieving the metadata", e);
5725a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay            return null;
5735a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay        } finally {
5745a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay            IoUtils.closeQuietly(stream);
5755a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay        }
5765a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay    }
5775a10ff1828eabae2aabcc4981ece7b9aff772d3bSteve McKay
5782efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
579e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
580fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            if (mDeviceToolkits.containsKey(deviceId)) {
581fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                return;
582fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
58319aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            if (DEBUG) {
58419aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono                Log.d(TAG, "Open device " + deviceId);
58519aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            }
5860f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = mMtpManager.openDevice(deviceId);
5874e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            final DeviceToolkit toolkit =
58861ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    new DeviceToolkit(mMtpManager, mResolver, mDatabase, device);
5894e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            mDeviceToolkits.put(deviceId, toolkit);
590203be491ef479deea1f39f8b63f3e1916501a37aDaichi Hirono            mIntentSender.sendUpdateNotificationIntent(getOpenedDeviceRecordsCache());
591fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            try {
592fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                mRootScanner.resume().await();
593fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            } catch (InterruptedException error) {
594fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                Log.e(TAG, "openDevice", error);
595fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
5964e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // Resume document loader to remap disconnected document ID. Must be invoked after the
5974e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // root scanner resumes.
5984e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            toolkit.mDocumentLoader.resume();
599e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
600d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
601d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
602e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
603e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
604e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            closeDeviceInternal(deviceId);
605203be491ef479deea1f39f8b63f3e1916501a37aDaichi Hirono            mIntentSender.sendUpdateNotificationIntent(getOpenedDeviceRecordsCache());
606e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
60720754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono        mRootScanner.resume();
608d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
609d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
6100f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono    MtpDeviceRecord[] getOpenedDeviceRecordsCache() {
611e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
6120f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord[] records = new MtpDeviceRecord[mDeviceToolkits.size()];
6130f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            int i = 0;
6140f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
6150f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                records[i] = toolkit.mDeviceRecord;
6160f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                i++;
61720754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            }
6180f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            return records;
619e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
62050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
62150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
622e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
6231e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * Obtains document ID for the given device ID.
6241e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @param deviceId
6251e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @return document ID
6261e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @throws FileNotFoundException device ID has not been build.
6271e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     */
6281e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    public String getDeviceDocumentId(int deviceId) throws FileNotFoundException {
6291e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono        return mDatabase.getDeviceDocumentId(deviceId);
6301e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    }
6311e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono
6321e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    /**
633fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     * Resumes root scanner to handle the update of device list.
634fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     */
635fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    void resumeRootScanner() {
636ebd24051599280443435606cab220de33b9356adDaichi Hirono        if (DEBUG) {
637ebd24051599280443435606cab220de33b9356adDaichi Hirono            Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner");
638ebd24051599280443435606cab220de33b9356adDaichi Hirono        }
639fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mRootScanner.resume();
640fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    }
641fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono
642fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    /**
643e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
644e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
645e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
646e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
647e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
648e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            try {
6490f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                // Copy the opened key set because it will be modified when closing devices.
6500f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                final Integer[] keySet =
6510f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                        mDeviceToolkits.keySet().toArray(new Integer[mDeviceToolkits.size()]);
6520f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                for (final int id : keySet) {
653e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    closeDeviceInternal(id);
654e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                }
6552e9a57b0d4839b322787dad36bfcb64f211f7ac4Daichi Hirono                mRootScanner.pause();
656acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hirono            } catch (InterruptedException | IOException | TimeoutException e) {
657e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                // It should fail unit tests by throwing runtime exception.
658e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                throw new RuntimeException(e);
659e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } finally {
660e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                mDatabase.close();
661e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                super.shutdown();
662e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            }
663e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
664e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
665e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
6665fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
6675fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
6685fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
6695fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
6705fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
6715fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
6724c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
673e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    /**
674be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono     * Clears MTP identifier in the database.
675e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono     */
676e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void resume() {
677e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
678e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDatabase.getMapper().clearMapping();
679e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
680e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
681e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
682e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
683e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        // TODO: Flush the device before closing (if not closed externally).
684fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        if (!mDeviceToolkits.containsKey(deviceId)) {
685fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            return;
686fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        }
68719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
68819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "Close device " + deviceId);
68919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
69024ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        getDeviceToolkit(deviceId).close();
691e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mDeviceToolkits.remove(deviceId);
692e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mMtpManager.closeDevice(deviceId);
693e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
694e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
6954c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
696e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
697e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
698e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
699e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
700e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
701e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
7024c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
7034c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
7044c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
7054c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
7064c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
7074c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
7084c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
7094c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
7104c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
7114c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
7124c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
713f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private long getFileSize(String documentId) throws FileNotFoundException {
714f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(
715f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                documentId,
716f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME));
717f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        try {
718f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            if (cursor.moveToNext()) {
71977a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                if (cursor.isNull(0)) {
72077a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                    throw new UnsupportedOperationException();
72177a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                }
722f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                return cursor.getLong(0);
723f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            } else {
724f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                throw new FileNotFoundException();
725f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            }
726f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        } finally {
727f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            cursor.close();
728f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
729f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
730f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
7312965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    /**
7322965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * Creates empty cursor with specific error message.
7332965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     *
7342965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param projection Column names.
7352965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param stringResId String resource ID of error message.
7362965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @return Empty cursor with error message.
7372965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     */
7382965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    private Cursor createErrorCursor(String[] projection, int stringResId) {
7392965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Bundle bundle = new Bundle();
7402965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId));
7412965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Cursor cursor = new MatrixCursor(projection);
7422965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        cursor.setExtras(bundle);
7432965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        return cursor;
7442965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    }
7452965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
74624ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono    private static class DeviceToolkit implements AutoCloseable {
7474c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
7484c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
7490f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono        public final MtpDeviceRecord mDeviceRecord;
7504c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
75161ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono        public DeviceToolkit(MtpManager manager,
75261ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             ContentResolver resolver,
75361ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDatabase database,
75461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDeviceRecord record) {
755f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono            mPipeManager = new PipeManager(database);
75661ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono            mDocumentLoader = new DocumentLoader(record, manager, resolver, database);
7570f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            mDeviceRecord = record;
7584c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
75924ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono
76024ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        @Override
76124ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        public void close() throws InterruptedException {
76224ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mPipeManager.close();
76324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mDocumentLoader.close();
76424ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        }
7654c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
766f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
767e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono    private class MtpProxyFileDescriptorCallback extends ProxyFileDescriptorCallback {
768e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        private final int mInode;
769e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        private MtpFileWriter mWriter;
770f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
771e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        MtpProxyFileDescriptorCallback(int inode) {
772e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            mInode = inode;
773f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        }
774f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
775f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
776e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public long onGetSize() throws ErrnoException {
777e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
778e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                return getFileSize(String.valueOf(mInode));
779e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (FileNotFoundException e) {
780e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
781e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onGetSize", OsConstants.ENOENT);
78277a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono            }
783e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        }
78477a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
785e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        @Override
786e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public int onRead(long offset, int size, byte[] data) throws ErrnoException {
787e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
788e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                final Identifier identifier = mDatabase.createIdentifier(Integer.toString(mInode));
789e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
790e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (MtpDeviceRecord.isSupported(
791e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                        record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) {
792e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
793e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                        return (int) mMtpManager.getPartialObject64(
794e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                                identifier.mDeviceId, identifier.mObjectHandle, offset, size, data);
79577a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
796e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
797e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported(
798e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                        record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) {
799e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    return (int) mMtpManager.getPartialObject(
800e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            identifier.mDeviceId, identifier.mObjectHandle, offset, size, data);
801e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
802e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onRead", OsConstants.ENOTSUP);
803e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (IOException e) {
804e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
805e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onRead", OsConstants.EIO);
806e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            }
807f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
808f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
809f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
810e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
811e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
812e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (mWriter == null) {
813e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    mWriter = new MtpFileWriter(mContext, String.valueOf(mInode));
814e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
815e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                return mWriter.write(offset, size, data);
816e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (IOException e) {
817e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
818e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onWrite", OsConstants.EIO);
819f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
820f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
82109ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono
82209ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono        @Override
823e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public void onFsync() throws ErrnoException {
824e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            tryFsync();
825f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        }
826f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
827f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        @Override
828e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public void onRelease() {
829f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            try {
830e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                tryFsync();
831e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (ErrnoException error) {
832e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                // Cannot recover from the error at onRelease. Client app should use fsync to
833e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                // ensure the provider writes data correctly.
834e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, "Cannot recover from the error at onRelease.", error);
835f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } finally {
836e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (mWriter != null) {
837e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    IoUtils.closeQuietly(mWriter);
838e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
839e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            }
840e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        }
841e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
842e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        private void tryFsync() throws ErrnoException {
843e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
844e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (mWriter != null) {
845e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    final MtpDeviceRecord device =
846e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            getDeviceToolkit(mDatabase.createIdentifier(
847e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                                    mWriter.getDocumentId()).mDeviceId).mDeviceRecord;
848e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    mWriter.flush(mMtpManager, mDatabase, device.operationsSupported);
849e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
850e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (IOException e) {
851e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
852e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onWrite", OsConstants.EIO);
853f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
85409ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono        }
855f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
856c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
857