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;
200f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hironoimport android.content.ContentValues;
21f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.content.Context;
223bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.content.UriPermission;
233faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.content.res.AssetFileDescriptor;
2417c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hironoimport android.content.res.Resources;
25c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.database.Cursor;
260f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hironoimport android.database.DatabaseUtils;
27c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.database.MatrixCursor;
285884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hironoimport android.database.sqlite.SQLiteDiskIOException;
293faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hironoimport android.graphics.Point;
309e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.media.MediaFile;
319e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hironoimport android.mtp.MtpConstants;
32bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.mtp.MtpObjectInfo;
333bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.net.Uri;
34c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hironoimport android.os.Bundle;
35c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.CancellationSignal;
36fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hironoimport android.os.FileUtils;
37c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.os.ParcelFileDescriptor;
38e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport android.os.ProxyFileDescriptorCallback;
39f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hironoimport android.os.storage.StorageManager;
40c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Document;
41b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport android.provider.DocumentsContract.Path;
42c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsContract.Root;
43bb430fa930fa0d0700e46e7b4881de2a252223ddTomasz Mikolajewskiimport android.provider.DocumentsContract;
44c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport android.provider.DocumentsProvider;
453bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport android.provider.Settings;
46f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hironoimport android.system.ErrnoException;
47e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport android.system.OsConstants;
48d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport android.util.Log;
49d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
50e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hironoimport com.android.internal.annotations.GuardedBy;
51d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport com.android.internal.annotations.VisibleForTesting;
52d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
53c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironoimport java.io.FileNotFoundException;
54d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hironoimport java.io.IOException;
554c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.HashMap;
56b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hironoimport java.util.LinkedList;
573bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hironoimport java.util.List;
584c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewskiimport java.util.Map;
59acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hironoimport java.util.concurrent.TimeoutException;
60e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hironoimport libcore.io.IoUtils;
61e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
62d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono/**
63d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono * DocumentsProvider for MTP devices.
64d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono */
65c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hironopublic class MtpDocumentsProvider extends DocumentsProvider {
662efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String AUTHORITY = "com.android.mtp.documents";
672efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static final String TAG = "MtpDocumentsProvider";
686baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
69c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
70c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
71c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Root.COLUMN_AVAILABLE_BYTES,
72c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
736baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
74c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
75c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
76c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
77c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    };
78c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
79f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono    static final boolean DEBUG = false;
8019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono
81e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private final Object mDeviceListLock = new Object();
82e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
832efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private static MtpDocumentsProvider sSingleton;
842efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
852efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    private MtpManager mMtpManager;
86d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    private ContentResolver mResolver;
87e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    @GuardedBy("mDeviceListLock")
884c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private Map<Integer, DeviceToolkit> mDeviceToolkits;
898b9024f0c20b1b79df1f2d0bc2f1a82f726b1176Daichi Hirono    private RootScanner mRootScanner;
9017c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono    private Resources mResources;
91dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono    private MtpDatabase mDatabase;
92fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    private ServiceIntentSender mIntentSender;
93f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono    private Context mContext;
94e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono    private StorageManager mStorageManager;
95d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
962efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    /**
972efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     * Provides singleton instance to MtpDocumentsService.
982efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono     */
992efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    static MtpDocumentsProvider getInstance() {
1002efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        return sSingleton;
1012efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    }
1022efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono
103c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
104c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public boolean onCreate() {
1052efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        sSingleton = this;
106f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        mContext = getContext();
10717c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = getContext().getResources();
1082efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono        mMtpManager = new MtpManager(getContext());
109d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono        mResolver = getContext().getContentResolver();
1104c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
11147eb192b2704e27272ca94a95680cac40b6bff3fDaichi Hirono        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
112f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
113fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = new ServiceIntentSender(getContext());
114e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        mStorageManager = getContext().getSystemService(StorageManager.class);
1153bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
1163bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider
1173bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        // after booting.
1185884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        try {
1195884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1);
1205884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            final int lastBootCount = mDatabase.getLastBootCount();
1215884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            if (bootCount != -1 && bootCount != lastBootCount) {
1225884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.setLastBootCount(bootCount);
1235884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final List<UriPermission> permissions =
1245884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                        mResolver.getOutgoingPersistedUriPermissions();
1255884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                final Uri[] uris = new Uri[permissions.size()];
1265884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                for (int i = 0; i < permissions.size(); i++) {
1275884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                    uris[i] = permissions.get(i).getUri();
1285884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                }
1295884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono                mDatabase.cleanDatabase(uris);
1303bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono            }
1315884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono        } catch (SQLiteDiskIOException error) {
1325884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            // It can happen due to disk shortage.
1335884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            Log.e(TAG, "Failed to clean database.", error);
1345884e1fa87e5116cc7d4ba0935cff3f62ad8de4cDaichi Hirono            return false;
1353bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono        }
1363bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
137e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
138c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono        return true;
139c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
140c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
141d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    @VisibleForTesting
142b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono    boolean onCreateForTesting(
143f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            Context context,
144dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            Resources resources,
145dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            MtpManager mtpManager,
146dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono            ContentResolver resolver,
147b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono            MtpDatabase database,
148fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            StorageManager storageManager,
149fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            ServiceIntentSender intentSender) {
150f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        mContext = context;
15117c8d8bcdae4b31d295431fc089db81bf3721c54Daichi Hirono        mResources = resources;
1526baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mMtpManager = mtpManager;
1536baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono        mResolver = resolver;
1544c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
155dc47344660035b27a564ab6d9c9a9b58ec340347Daichi Hirono        mDatabase = database;
156f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
157fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mIntentSender = intentSender;
158e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        mStorageManager = storageManager;
1593bb37e7ff0a7b0a8086007633ad927fec59a6e94Daichi Hirono
160e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        resume();
161b36b15586a5d3d0de590773ce4392fa3a82af66aDaichi Hirono        return true;
162d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
163d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
164c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
165c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
16650d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        if (projection == null) {
16750d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
16850d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        }
169f83ccbd7edd32e728785fb7aad44f11886e79645Daichi Hirono        final Cursor cursor = mDatabase.queryRoots(mResources, projection);
17050d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        cursor.setNotificationUri(
17150d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
17250d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono        return cursor;
173c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
174c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
175c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
176c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    public Cursor queryDocument(String documentId, String[] projection)
177c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono            throws FileNotFoundException {
178e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        if (projection == null) {
179e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
180e5323b7493f2bc1537d7e6b2d4595d69fd01d72eDaichi Hirono        }
1810f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(documentId, projection);
1820f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        final int cursorCount = cursor.getCount();
1830f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        if (cursorCount == 0) {
1840f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            cursor.close();
1850f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            throw new FileNotFoundException();
1860f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        } else if (cursorCount != 1) {
1870f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            cursor.close();
1880f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            Log.wtf(TAG, "Unexpected cursor size: " + cursorCount);
1890f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            return null;
1900f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        }
1910f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono
1920f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
1930f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        if (identifier.mDocumentType != MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
1940f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            return cursor;
1950f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        }
1960f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        final String[] storageDocIds = mDatabase.getStorageDocumentIds(documentId);
1970f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        if (storageDocIds.length != 1) {
1980f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            return mDatabase.queryDocument(documentId, projection);
1990f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        }
2000f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono
2010f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        // If the documentId specifies a device having exact one storage, we repalce some device
2020f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        // attributes with the storage attributes.
2030f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        try {
2040f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            final String storageName;
2050f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            final int storageFlags;
2060f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            try (final Cursor storageCursor = mDatabase.queryDocument(
2070f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono                    storageDocIds[0],
2080f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono                    MtpDatabase.strings(Document.COLUMN_DISPLAY_NAME, Document.COLUMN_FLAGS))) {
2090f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono                if (!storageCursor.moveToNext()) {
2100f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono                    throw new FileNotFoundException();
2110f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono                }
2120f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono                storageName = storageCursor.getString(0);
2130f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono                storageFlags = storageCursor.getInt(1);
2140f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            }
2150f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono
2160f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            cursor.moveToNext();
2170f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            final ContentValues values = new ContentValues();
2180f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            DatabaseUtils.cursorRowToContentValues(cursor, values);
2190f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            if (values.containsKey(Document.COLUMN_DISPLAY_NAME)) {
2200f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono                values.put(Document.COLUMN_DISPLAY_NAME, mResources.getString(
2210f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono                        R.string.root_name,
2220f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono                        values.getAsString(Document.COLUMN_DISPLAY_NAME),
2230f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono                        storageName));
2240f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            }
2250f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            values.put(Document.COLUMN_FLAGS, storageFlags);
2260f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            final MatrixCursor output = new MatrixCursor(projection, 1);
2270f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            MtpDatabase.putValuesToCursor(values, output);
2280f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            return output;
2290f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        } finally {
2300f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono            cursor.close();
2310f6ab57eef3dcacfc099598dde5390dabc2e6a55Daichi Hirono        }
232c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
233c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
234c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
235124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono    public Cursor queryChildDocuments(String parentDocumentId,
236124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            String[] projection, String sortOrder) throws FileNotFoundException {
23719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
23819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "queryChildDocuments: " + parentDocumentId);
23919aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
240124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        if (projection == null) {
241124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
242124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
2436a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono        Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
244124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        try {
245fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentIdentifier.mDeviceId);
2466a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
2472965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                final String[] storageDocIds = mDatabase.getStorageDocumentIds(parentDocumentId);
2482965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                if (storageDocIds.length == 0) {
2492965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    // Remote device does not provide storages. Maybe it is locked.
2502965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                    return createErrorCursor(projection, R.string.error_locked_device);
2512965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                } else if (storageDocIds.length > 1) {
2526a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    // Returns storage list from database.
2536a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                    return mDatabase.queryChildDocuments(projection, parentDocumentId);
2546a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                }
2552965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2562965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // Exact one storage is found. Skip storage and returns object in the single
2572965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                // storage.
2582965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono                parentIdentifier = mDatabase.createIdentifier(storageDocIds[0]);
2596a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
2602965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
2616a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            // Returns object list from document loader.
2624c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski            return getDocumentLoader(parentIdentifier).queryChildDocuments(
2634c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    projection, parentIdentifier);
264c18f8076ebdb2cda8842cfda2583897aa2c388e1Daichi Hirono        } catch (BusyDeviceException exception) {
2652965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono            return createErrorCursor(projection, R.string.error_busy_device);
266124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        } catch (IOException exception) {
2676a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "queryChildDocuments", exception);
268124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono            throw new FileNotFoundException(exception.getMessage());
269124d060bc980c7555616ff9d07a4dc3b8f3cd341Daichi Hirono        }
270c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    }
271c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono
272c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono    @Override
2738ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono    public ParcelFileDescriptor openDocument(
2748ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono            String documentId, String mode, CancellationSignal signal)
2758ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono                    throws FileNotFoundException {
2766213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
2776213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "openDocument: " + documentId);
2786213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
2799e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
2808ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        try {
281fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
2820f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
283f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            // Turn off MODE_CREATE because openDocument does not allow to create new files.
284f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            final int modeFlag =
285f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE;
286f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) {
287f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                long fileSize;
288f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                try {
289f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    fileSize = getFileSize(documentId);
290f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } catch (UnsupportedOperationException exception) {
291f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    fileSize = -1;
292f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
293f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                if (MtpDeviceRecord.isPartialReadSupported(
294f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                        device.operationsSupported, fileSize)) {
295e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
296e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    return mStorageManager.openProxyFileDescriptor(
297e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            modeFlag,
298e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId)));
299f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } else {
300f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    // If getPartialObject{|64} are not supported for the device, returns
301f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    // non-seekable pipe FD instead.
302f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                    return getPipeManager(identifier).readDocument(mMtpManager, identifier);
303f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
304f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) {
305f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // TODO: Clear the parent document loader task (if exists) and call notify
306f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // when writing is completed.
307f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
308e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    return mStorageManager.openProxyFileDescriptor(
309e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            modeFlag,
310e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId)));
311f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                } else {
312b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski                    throw new UnsupportedOperationException(
313f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                            "The device does not support writing operation.");
314f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                }
315f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } else {
316f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                // TODO: Add support for "rw" mode.
317f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono                throw new UnsupportedOperationException("The provider does not support 'rw' mode.");
318b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2Tomasz Mikolajewski            }
319f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        } catch (FileNotFoundException | RuntimeException error) {
320f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
321f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            throw error;
3228ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        } catch (IOException error) {
3236a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
324f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            throw new IllegalStateException(error);
3258ba419119d50a031160cab54bef6899bd0051ea9Daichi Hirono        }
326d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
327d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
3283faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
3293faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public AssetFileDescriptor openDocumentThumbnail(
3303faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            String documentId,
3313faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            Point sizeHint,
3323faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            CancellationSignal signal) throws FileNotFoundException {
3339e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono        final Identifier identifier = mDatabase.createIdentifier(documentId);
3343faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
335fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
3363faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            return new AssetFileDescriptor(
3374c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski                    getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
338573c1fbc5f98f033681e378ec965136bce49c899Daichi Hirono                    0,  // Start offset.
3393faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono                    AssetFileDescriptor.UNKNOWN_LENGTH);
3403faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
3416a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "openDocumentThumbnail", error);
3423faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
3433faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
3443faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
3453faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
3463faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    @Override
3473faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    public void deleteDocument(String documentId) throws FileNotFoundException {
3483faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        try {
3499e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final Identifier identifier = mDatabase.createIdentifier(documentId);
350fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(identifier.mDeviceId);
3516a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
3523faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
3539e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            mDatabase.deleteDocument(documentId);
35476be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono            getDocumentLoader(parentIdentifier).cancelTask(parentIdentifier);
3559e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            notifyChildDocumentsChange(parentIdentifier.mDocumentId);
3566a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
3576a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // If the parent is storage, the object might be appeared as child of device because
3586a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                // we skip storage when the device has only one storage.
3596a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                final Identifier deviceIdentifier = mDatabase.getParentIdentifier(
3606a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                        parentIdentifier.mDocumentId);
3616a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono                notifyChildDocumentsChange(deviceIdentifier.mDocumentId);
3626a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            }
3633faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        } catch (IOException error) {
3646a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(MtpDocumentsProvider.TAG, "deleteDocument", error);
3653faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono            throw new FileNotFoundException(error.getMessage());
3663faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono        }
3673faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono    }
3683faa43a4a6f270e2e1e2ec55b77508084af16757Daichi Hirono
3696baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    @Override
3706baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    public void onTrimMemory(int level) {
371e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
372e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
373e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                toolkit.mDocumentLoader.clearCompletedTasks();
374e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
375e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
3766baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono    }
3776baa16e9109046661fef8dcc25b8754ac68bcdaeDaichi Hirono
37887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    @Override
37987763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    public String createDocument(String parentDocumentId, String mimeType, String displayName)
38087763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            throws FileNotFoundException {
3816213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        if (DEBUG) {
3826213cefbc06170f9463abf40c240322be11047bcDaichi Hirono            Log.d(TAG, "createDocument: " + displayName);
3836213cefbc06170f9463abf40c240322be11047bcDaichi Hirono        }
384fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final Identifier parentId;
385fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final MtpDeviceRecord record;
386fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        final ParcelFileDescriptor[] pipe;
38787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        try {
388fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            parentId = mDatabase.createIdentifier(parentDocumentId);
389fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            openDevice(parentId.mDeviceId);
390fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            record = getDeviceToolkit(parentId.mDeviceId).mDeviceRecord;
3910f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            if (!MtpDeviceRecord.isWritingSupported(record.operationsSupported)) {
392fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new UnsupportedOperationException(
393fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "Writing operation is not supported by the device.");
394fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
39535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono
39635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            final int parentObjectHandle;
39735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            final int storageId;
39835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            switch (parentId.mDocumentType) {
39935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE:
40035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    final String[] storageDocumentIds =
40135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                            mDatabase.getStorageDocumentIds(parentId.mDocumentId);
40235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    if (storageDocumentIds.length == 1) {
40335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        final String newDocumentId =
40435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                                createDocument(storageDocumentIds[0], mimeType, displayName);
40535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        notifyChildDocumentsChange(parentDocumentId);
40635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        return newDocumentId;
40735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    } else {
40835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        throw new UnsupportedOperationException(
40935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                                "Cannot create a file under the device.");
41035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    }
41135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE:
41235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    storageId = parentId.mStorageId;
41335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    parentObjectHandle = -1;
41435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    break;
41535b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT:
41635b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    storageId = parentId.mStorageId;
41735b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    parentObjectHandle = parentId.mObjectHandle;
41835b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    break;
41935b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                default:
42035b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                    throw new IllegalArgumentException("Unexpected document type.");
42135b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono            }
42235b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono
423fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            pipe = ParcelFileDescriptor.createReliablePipe();
424fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            int objectHandle = -1;
425fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            MtpObjectInfo info = null;
426fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            try {
427fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[0].close();  // 0 bytes for a new document.
428fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
429fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
430fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MtpConstants.FORMAT_ASSOCIATION :
431fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        MediaFile.getFormatCode(displayName, mimeType);
432fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                info = new MtpObjectInfo.Builder()
43335b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        .setStorageId(storageId)
43435b2ec551f670562a779925fe152307f20ad67cdDaichi Hirono                        .setParent(parentObjectHandle)
435fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setFormat(formatCode)
436fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .setName(displayName)
437fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        .build();
438fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono
439fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String[] parts = FileUtils.splitFileName(mimeType, displayName);
440fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String baseName = parts[0];
441fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                final String extension = parts[1];
442fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                for (int i = 0; i <= 32; i++) {
443fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    final MtpObjectInfo infoUniqueName;
444fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    if (i == 0) {
445fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        infoUniqueName = info;
446fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } else {
4474f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        String suffixedName = baseName + " (" + i + " )";
4484f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        if (!extension.isEmpty()) {
4494f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                            suffixedName += "." + extension;
4504f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        }
4514f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                        infoUniqueName =
4524f04fd358d84f7ea8a9a26bf4c2ef4bef553c732Daichi Hirono                                new MtpObjectInfo.Builder(info).setName(suffixedName).build();
453fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
454fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    try {
455fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        objectHandle = mMtpManager.createDocument(
456fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                                parentId.mDeviceId, infoUniqueName, pipe[1]);
457fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        break;
458fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    } catch (SendObjectInfoFailure exp) {
459fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        // This can be caused when we have an existing file with the same name.
460fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        continue;
461fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                    }
462fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                }
463fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            } finally {
464fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                pipe[1].close();
465fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            }
466fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            if (objectHandle == -1) {
467fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                throw new IllegalArgumentException(
468fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "The file name \"" + displayName + "\" is conflicted with existing files " +
469fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono                        "and the provider failed to find unique name.");
4700f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            }
4719e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final MtpObjectInfo infoWithHandle =
4729e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono                    new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
4739e8a4fa78f5b9e3964dca84ad4047210d35c4013Daichi Hirono            final String documentId = mDatabase.putNewDocument(
47461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    parentId.mDeviceId, parentDocumentId, record.operationsSupported,
47564111e08d905525c7f4fe27e69953eb71bd62511Daichi Hirono                    infoWithHandle, 0l);
47676be46f4d9314fd7daca0985a0a7e02126d85975Daichi Hirono            getDocumentLoader(parentId).cancelTask(parentId);
47787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            notifyChildDocumentsChange(parentDocumentId);
47887763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski            return documentId;
479fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono        } catch (FileNotFoundException | RuntimeException error) {
480fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            Log.e(TAG, "createDocument", error);
481fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw error;
48287763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        } catch (IOException error) {
4836a5ea7eae8a70bced97ceef051c965c27cb642caDaichi Hirono            Log.e(TAG, "createDocument", error);
484fc7fb7533f46b53247d1e6e6edca6e6c9ac676feDaichi Hirono            throw new IllegalStateException(error);
48587763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski        }
48687763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski    }
48787763e6a91a54e7995cfda9b7e80162f02ac4cbcTomasz Mikolajewski
488b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono    @Override
489b690b4de06385a821aed3442e10058986c03badcGarfield Tan    public Path findDocumentPath(String parentDocumentId, String childDocumentId)
490b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            throws FileNotFoundException {
491b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        final LinkedList<String> ids = new LinkedList<>();
492b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        final Identifier childIdentifier = mDatabase.createIdentifier(childDocumentId);
493b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
494b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        Identifier i = childIdentifier;
495b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        outer: while (true) {
496b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            if (i.mDocumentId.equals(parentDocumentId)) {
497b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                ids.addFirst(i.mDocumentId);
498b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                break;
499b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            }
500b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            switch (i.mDocumentType) {
501b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT:
502b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    ids.addFirst(i.mDocumentId);
503b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    i = mDatabase.getParentIdentifier(i.mDocumentId);
504b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break;
505b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE: {
506b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // Check if there is the multiple storage.
507b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    final Identifier deviceIdentifier =
508b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                            mDatabase.getParentIdentifier(i.mDocumentId);
509b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    final String[] storageIds =
510b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                            mDatabase.getStorageDocumentIds(deviceIdentifier.mDocumentId);
511b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // Add storage's document ID to the path only when the device has multiple
512b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    // storages.
513b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    if (storageIds.length > 1) {
514b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                        ids.addFirst(i.mDocumentId);
515b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                        break outer;
516b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    }
517b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    i = deviceIdentifier;
518b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break;
519b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                }
520b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE:
521b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    ids.addFirst(i.mDocumentId);
522b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono                    break outer;
523b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            }
524b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        }
525b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
526b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        if (parentDocumentId != null) {
527b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            return new Path(null, ids);
528b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        } else {
529b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono            return new Path(/* Should be same with root ID */ i.mDocumentId, ids);
530b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono        }
531b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono    }
532b9ffa2a1d2cfd0b182629c849a22f2efa832892eDaichi Hirono
53329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    @Override
53429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    public boolean isChildDocument(String parentDocumentId, String documentId) {
53529de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        try {
53629de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            Identifier identifier = mDatabase.createIdentifier(documentId);
53729de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            while (true) {
53829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                if (parentDocumentId.equals(identifier.mDocumentId)) {
53929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                    return true;
54029de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                }
54129de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                if (identifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
54229de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                    return false;
54329de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                }
54429de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono                identifier = mDatabase.getParentIdentifier(identifier.mDocumentId);
54529de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            }
54629de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        } catch (FileNotFoundException error) {
54729de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono            return false;
54829de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono        }
54929de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono    }
55029de7693f4fab188e695b9ecda1de0345496bd10Daichi Hirono
5512efe4cac49efd87c5f14f48051a5556f437cedc9Daichi Hirono    void openDevice(int deviceId) throws IOException {
552e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
553fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            if (mDeviceToolkits.containsKey(deviceId)) {
554fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                return;
555fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
55619aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            if (DEBUG) {
55719aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono                Log.d(TAG, "Open device " + deviceId);
55819aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            }
5590f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord device = mMtpManager.openDevice(deviceId);
5604e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            final DeviceToolkit toolkit =
56161ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                    new DeviceToolkit(mMtpManager, mResolver, mDatabase, device);
5624e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            mDeviceToolkits.put(deviceId, toolkit);
56398f48479e95b04479b5512c587da6bc9fbd73b7eDaichi Hirono            mIntentSender.sendUpdateNotificationIntent(getOpenedDeviceRecordsCache());
564fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            try {
565fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                mRootScanner.resume().await();
566fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            } catch (InterruptedException error) {
567fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono                Log.e(TAG, "openDevice", error);
568fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            }
5694e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // Resume document loader to remap disconnected document ID. Must be invoked after the
5704e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            // root scanner resumes.
5714e94b8deaa646f176bad9b80d5924ce64142743eDaichi Hirono            toolkit.mDocumentLoader.resume();
572e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
573d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
574d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
575e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    void closeDevice(int deviceId) throws IOException, InterruptedException {
576e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
577e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            closeDeviceInternal(deviceId);
57898f48479e95b04479b5512c587da6bc9fbd73b7eDaichi Hirono            mIntentSender.sendUpdateNotificationIntent(getOpenedDeviceRecordsCache());
579e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
58020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono        mRootScanner.resume();
581d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono    }
582d5152429e11d3a831a7993dc67ddf69f69bd3912Daichi Hirono
5830f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono    MtpDeviceRecord[] getOpenedDeviceRecordsCache() {
584e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
5850f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            final MtpDeviceRecord[] records = new MtpDeviceRecord[mDeviceToolkits.size()];
5860f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            int i = 0;
5870f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
5880f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                records[i] = toolkit.mDeviceRecord;
5890f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                i++;
59020754c5a112e418c11cc88176283db2c4bf2efd6Daichi Hirono            }
5910f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            return records;
592e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
59350d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono    }
59450d17aa871d9ca645a8e7af64df8866b85aee245Daichi Hirono
595e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    /**
5961e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * Obtains document ID for the given device ID.
5971e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @param deviceId
5981e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @return document ID
5991e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     * @throws FileNotFoundException device ID has not been build.
6001e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono     */
6011e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    public String getDeviceDocumentId(int deviceId) throws FileNotFoundException {
6021e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono        return mDatabase.getDeviceDocumentId(deviceId);
6031e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    }
6041e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono
6051e3744441a1e4ead0a3c752644bee930be020698Daichi Hirono    /**
606fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     * Resumes root scanner to handle the update of device list.
607fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono     */
608fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    void resumeRootScanner() {
609ebd24051599280443435606cab220de33b9356adDaichi Hirono        if (DEBUG) {
610ebd24051599280443435606cab220de33b9356adDaichi Hirono            Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner");
611ebd24051599280443435606cab220de33b9356adDaichi Hirono        }
612fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        mRootScanner.resume();
613fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    }
614fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono
615fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono    /**
616e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     * Finalize the content provider for unit tests.
617e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono     */
618e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    @Override
619e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    public void shutdown() {
620e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
621e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            try {
6220f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                // Copy the opened key set because it will be modified when closing devices.
6230f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                final Integer[] keySet =
6240f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                        mDeviceToolkits.keySet().toArray(new Integer[mDeviceToolkits.size()]);
6250f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono                for (final int id : keySet) {
626e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                    closeDeviceInternal(id);
627e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                }
6282e9a57b0d4839b322787dad36bfcb64f211f7ac4Daichi Hirono                mRootScanner.pause();
629acb0e27bb33e373f1c42d6e2ef9344169cae96f0Daichi Hirono            } catch (InterruptedException | IOException | TimeoutException e) {
630e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                // It should fail unit tests by throwing runtime exception.
631e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                throw new RuntimeException(e);
632e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            } finally {
633e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                mDatabase.close();
634e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono                super.shutdown();
635e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            }
636e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono        }
637e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono    }
638e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono
6395fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    private void notifyChildDocumentsChange(String parentDocumentId) {
6405fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono        mResolver.notifyChange(
6415fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
6425fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                null,
6435fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono                false);
6445fecc6cf032bbbc2616dd2342a50656bf2857832Daichi Hirono    }
6454c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
646e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    /**
647be38848969c91ba9bc3ec8eee31017a34905acfcDaichi Hirono     * Clears MTP identifier in the database.
648e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono     */
649e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void resume() {
650e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
651e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono            mDatabase.getMapper().clearMapping();
652e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        }
653e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
654e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
655e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
656e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        // TODO: Flush the device before closing (if not closed externally).
657fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        if (!mDeviceToolkits.containsKey(deviceId)) {
658fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono            return;
659fda7474c5faae1e36a9274d8a5fe83e42ec6503bDaichi Hirono        }
66019aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        if (DEBUG) {
66119aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono            Log.d(TAG, "Close device " + deviceId);
66219aa93249edc5dac01025456ce3bb1881f1b11d1Daichi Hirono        }
66324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        getDeviceToolkit(deviceId).close();
664e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mDeviceToolkits.remove(deviceId);
665e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        mMtpManager.closeDevice(deviceId);
666e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono    }
667e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono
6684c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
669e0282dd7d409dce7738173162e766ce9317ef804Daichi Hirono        synchronized (mDeviceListLock) {
670e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
671e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            if (toolkit == null) {
672e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono                throw new FileNotFoundException();
673e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            }
674e1d57710fb38dae2747aadca0e5b6f98a84a0514Daichi Hirono            return toolkit;
6754c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
6764c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
6774c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
6784c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
6794c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mPipeManager;
6804c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
6814c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
6824c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException {
6834c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader;
6844c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
6854c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
686f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    private long getFileSize(String documentId) throws FileNotFoundException {
687f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        final Cursor cursor = mDatabase.queryDocument(
688f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                documentId,
689f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME));
690f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        try {
691f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            if (cursor.moveToNext()) {
69277a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                if (cursor.isNull(0)) {
69377a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                    throw new UnsupportedOperationException();
69477a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono                }
695f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                return cursor.getLong(0);
696f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            } else {
697f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono                throw new FileNotFoundException();
698f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            }
699f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        } finally {
700f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono            cursor.close();
701f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
702f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
703f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
7042965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    /**
7052965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * Creates empty cursor with specific error message.
7062965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     *
7072965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param projection Column names.
7082965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @param stringResId String resource ID of error message.
7092965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     * @return Empty cursor with error message.
7102965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono     */
7112965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    private Cursor createErrorCursor(String[] projection, int stringResId) {
7122965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Bundle bundle = new Bundle();
7132965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        bundle.putString(DocumentsContract.EXTRA_ERROR, mResources.getString(stringResId));
7142965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        final Cursor cursor = new MatrixCursor(projection);
7152965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        cursor.setExtras(bundle);
7162965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono        return cursor;
7172965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono    }
7182965776ba403ef1f6d9a0e870ebf3c44ee5d8373Daichi Hirono
71924ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono    private static class DeviceToolkit implements AutoCloseable {
7204c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final PipeManager mPipeManager;
7214c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        public final DocumentLoader mDocumentLoader;
7220f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono        public final MtpDeviceRecord mDeviceRecord;
7234c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski
72461ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono        public DeviceToolkit(MtpManager manager,
72561ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             ContentResolver resolver,
72661ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDatabase database,
72761ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono                             MtpDeviceRecord record) {
728f578fa275a535016f5322c88ad7a92e517d04a12Daichi Hirono            mPipeManager = new PipeManager(database);
72961ba923ca0cb5c928a16729d0aa67b6bf4b2f027Daichi Hirono            mDocumentLoader = new DocumentLoader(record, manager, resolver, database);
7300f32537e40ee2662d4f0b7b625ee280ca9c02066Daichi Hirono            mDeviceRecord = record;
7314c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski        }
73224ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono
73324ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        @Override
73424ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        public void close() throws InterruptedException {
73524ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mPipeManager.close();
73624ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono            mDocumentLoader.close();
73724ab92a5f7492f116ae82f354f406de60a0d912cDaichi Hirono        }
7384c1d3dde05308cb10187269dd9824c9bfdbb27deTomasz Mikolajewski    }
739f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
740e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono    private class MtpProxyFileDescriptorCallback extends ProxyFileDescriptorCallback {
741e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        private final int mInode;
742e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        private MtpFileWriter mWriter;
743f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
744e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        MtpProxyFileDescriptorCallback(int inode) {
745e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            mInode = inode;
746f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        }
747f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
748f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
749e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public long onGetSize() throws ErrnoException {
750e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
751e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                return getFileSize(String.valueOf(mInode));
752e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (FileNotFoundException e) {
753e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
754e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onGetSize", OsConstants.ENOENT);
75577a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono            }
756e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        }
75777a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
758e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        @Override
759e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public int onRead(long offset, int size, byte[] data) throws ErrnoException {
760e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
761e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                final Identifier identifier = mDatabase.createIdentifier(Integer.toString(mInode));
762e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
763e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (MtpDeviceRecord.isSupported(
764e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                        record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) {
765e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
766e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                        return (int) mMtpManager.getPartialObject64(
767e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                                identifier.mDeviceId, identifier.mObjectHandle, offset, size, data);
76877a1c65610618891ba28d7a10e4f107ea27e392eDaichi Hirono
769e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
770e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported(
771e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                        record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) {
772e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    return (int) mMtpManager.getPartialObject(
773e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            identifier.mDeviceId, identifier.mObjectHandle, offset, size, data);
774e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
775e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onRead", OsConstants.ENOTSUP);
776e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (IOException e) {
777e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
778e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onRead", OsConstants.EIO);
779e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            }
780f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
781f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono
782f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        @Override
783e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
784e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
785e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (mWriter == null) {
786e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    mWriter = new MtpFileWriter(mContext, String.valueOf(mInode));
787e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
788e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                return mWriter.write(offset, size, data);
789e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (IOException e) {
790e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
791e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onWrite", OsConstants.EIO);
792f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
793f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono        }
79409ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono
79509ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono        @Override
796e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public void onFsync() throws ErrnoException {
797e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            tryFsync();
798f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        }
799f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono
800f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono        @Override
801e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        public void onRelease() {
802f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            try {
803e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                tryFsync();
804e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (ErrnoException error) {
805e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                // Cannot recover from the error at onRelease. Client app should use fsync to
806e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                // ensure the provider writes data correctly.
807e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, "Cannot recover from the error at onRelease.", error);
808f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            } finally {
809e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (mWriter != null) {
810e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    IoUtils.closeQuietly(mWriter);
811e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
812e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            }
813e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        }
814e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono
815e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono        private void tryFsync() throws ErrnoException {
816e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            try {
817e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                if (mWriter != null) {
818e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    final MtpDeviceRecord device =
819e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                            getDeviceToolkit(mDatabase.createIdentifier(
820e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                                    mWriter.getDocumentId()).mDeviceId).mDeviceRecord;
821e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                    mWriter.flush(mMtpManager, mDatabase, device.operationsSupported);
822e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                }
823e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono            } catch (IOException e) {
824e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                Log.e(TAG, e.getMessage(), e);
825e80ea38489a7fca0e5a8cf2f663704058c782d63Daichi Hirono                throw new ErrnoException("onWrite", OsConstants.EIO);
826f4e7fa80384ac72d0228ca5de6e949a9162cefbfDaichi Hirono            }
82709ece6c68bdaf3a04b517f04dff6a3272b54b2b2Daichi Hirono        }
828f52ef008c76566f7118a80bf28f599ba48d7c578Daichi Hirono    }
829c00d5d4d82620beba271e63875b93ad9cc39523fDaichi Hirono}
830