[go: nahoru, domu]

19e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey/*
29e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey * Copyright (C) 2013 The Android Open Source Project
39e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey *
49e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
59e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey * you may not use this file except in compliance with the License.
69e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey * You may obtain a copy of the License at
79e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey *
89e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
99e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey *
109e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey * Unless required by applicable law or agreed to in writing, software
119e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
129e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey * See the License for the specific language governing permissions and
149e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey * limitations under the License.
159e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey */
169e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
179e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkeypackage com.android.externalstorage;
189e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
19db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkeyimport android.content.ContentResolver;
201f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkeyimport android.content.Context;
21ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkeyimport android.content.Intent;
226398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkeyimport android.content.res.AssetFileDescriptor;
239e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkeyimport android.database.Cursor;
249e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkeyimport android.database.MatrixCursor;
259d0843df7e3984293dc4ab6ee2f9502e898b63aaJeff Sharkeyimport android.database.MatrixCursor.RowBuilder;
26aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.graphics.Point;
27db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkeyimport android.net.Uri;
28b012f913cbb3e91572817914537ec16e6138a6dbFelipe Lemeimport android.os.Bundle;
29aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.os.CancellationSignal;
305c462a0e31dc75527d55a26f9cabf45d2ab874b3Steve McKayimport android.os.Environment;
31db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkeyimport android.os.FileObserver;
3221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkeyimport android.os.FileUtils;
33ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkeyimport android.os.Handler;
349e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkeyimport android.os.ParcelFileDescriptor;
35ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkeyimport android.os.ParcelFileDescriptor.OnCloseListener;
3627de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkeyimport android.os.UserHandle;
37ba23e54d263fefaac96b3ce6b068e70ec6f06128Steve McKayimport android.os.storage.DiskInfo;
381f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkeyimport android.os.storage.StorageManager;
3927de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkeyimport android.os.storage.VolumeInfo;
401f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkeyimport android.provider.DocumentsContract;
41ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkeyimport android.provider.DocumentsContract.Document;
42ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkeyimport android.provider.DocumentsContract.Root;
43aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.provider.DocumentsProvider;
44307d424467daf82542715c66735daebd87af6dceJeff Sharkeyimport android.provider.MediaStore;
45ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKayimport android.provider.Settings;
4668dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewskiimport android.support.provider.DocumentArchiveHelper;
47b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkeyimport android.text.TextUtils;
4827de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkeyimport android.util.ArrayMap;
4927de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkeyimport android.util.DebugUtils;
501f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkeyimport android.util.Log;
519e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkeyimport android.webkit.MimeTypeMap;
529e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
531f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkeyimport com.android.internal.annotations.GuardedBy;
5427de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkeyimport com.android.internal.util.IndentingPrintWriter;
559e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
569e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkeyimport java.io.File;
5727de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkeyimport java.io.FileDescriptor;
589e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkeyimport java.io.FileNotFoundException;
5920d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkeyimport java.io.IOException;
6027de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkeyimport java.io.PrintWriter;
6120d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkeyimport java.util.LinkedList;
6227de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkeyimport java.util.List;
639e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
64aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeypublic class ExternalStorageProvider extends DocumentsProvider {
659e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    private static final String TAG = "ExternalStorage";
669e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
67ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay    private static final boolean DEBUG = false;
68db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey    private static final boolean LOG_INOTIFY = false;
69db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
701f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey    public static final String AUTHORITY = "com.android.externalstorage.documents";
711f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
7214a6df7d7626b5e3c739e4dafd11073a471c91a8Makoto Onuki    private static final Uri BASE_URI =
7314a6df7d7626b5e3c739e4dafd11073a471c91a8Makoto Onuki            new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build();
7414a6df7d7626b5e3c739e4dafd11073a471c91a8Makoto Onuki
75aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    // docId format: root:path/to/file
769e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
77ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
786efba22ce510352bb84910d6efc42fecafd31ed7Jeff Sharkey            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
796efba22ce510352bb84910d6efc42fecafd31ed7Jeff Sharkey            Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
809d0843df7e3984293dc4ab6ee2f9502e898b63aaJeff Sharkey    };
819e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
82ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
83ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
84ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
85ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    };
86ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey
87ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    private static class RootInfo {
88ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        public String rootId;
89ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        public int flags;
90ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        public String title;
91ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        public String docId;
9227de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        public File visiblePath;
9327de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        public File path;
94c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay        public boolean reportAvailableBytes = true;
95ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    }
96ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey
971f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey    private static final String ROOT_ID_PRIMARY_EMULATED = "primary";
98c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay    private static final String ROOT_ID_HOME = "home";
991f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
1001f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey    private StorageManager mStorageManager;
101ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey    private Handler mHandler;
10268dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski    private DocumentArchiveHelper mArchiveHelper;
1031f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
1041f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey    private final Object mRootsLock = new Object();
1051f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
1061f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey    @GuardedBy("mRootsLock")
10727de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey    private ArrayMap<String, RootInfo> mRoots = new ArrayMap<>();
108aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
109db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey    @GuardedBy("mObservers")
11027de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey    private ArrayMap<File, DirectoryObserver> mObservers = new ArrayMap<>();
111db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
1129e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    @Override
1139e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    public boolean onCreate() {
1141f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        mStorageManager = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
115ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey        mHandler = new Handler();
11668dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        mArchiveHelper = new DocumentArchiveHelper(this, (char) 0);
1171f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
1181f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        updateVolumes();
1199e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey        return true;
1209e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    }
1219e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
1221f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey    public void updateVolumes() {
1231f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        synchronized (mRootsLock) {
1241f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey            updateVolumesLocked();
1251f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        }
1261f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey    }
1271f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
1281f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey    private void updateVolumesLocked() {
1291f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        mRoots.clear();
1301f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
131c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay        VolumeInfo primaryVolume = null;
13227de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        final int userId = UserHandle.myUserId();
13327de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        final List<VolumeInfo> volumes = mStorageManager.getVolumes();
13427de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        for (VolumeInfo volume : volumes) {
13527de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            if (!volume.isMountedReadable()) continue;
1361f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
1371f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey            final String rootId;
138b521feaed410b6862baca9b42d5fd7c398e07b2fJeff Sharkey            final String title;
139b521feaed410b6862baca9b42d5fd7c398e07b2fJeff Sharkey            if (volume.getType() == VolumeInfo.TYPE_EMULATED) {
140b521feaed410b6862baca9b42d5fd7c398e07b2fJeff Sharkey                // We currently only support a single emulated volume mounted at
141b521feaed410b6862baca9b42d5fd7c398e07b2fJeff Sharkey                // a time, and it's always considered the primary
142ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                if (DEBUG) Log.d(TAG, "Found primary volume: " + volume);
1431f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey                rootId = ROOT_ID_PRIMARY_EMULATED;
144ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay
145b521feaed410b6862baca9b42d5fd7c398e07b2fJeff Sharkey                if (VolumeInfo.ID_EMULATED_INTERNAL.equals(volume.getId())) {
146ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                    // This is basically the user's primary device storage.
147ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                    // Use device name for the volume since this is likely same thing
148ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                    // the user sees when they mount their phone on another device.
149ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                    String deviceName = Settings.Global.getString(
150ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                            getContext().getContentResolver(), Settings.Global.DEVICE_NAME);
151ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay
152ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                    // Device name should always be set. In case it isn't, though,
153ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                    // fall back to a localized "Internal Storage" string.
154ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                    title = !TextUtils.isEmpty(deviceName)
155ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                            ? deviceName
156ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                            : getContext().getString(R.string.root_internal_storage);
157b521feaed410b6862baca9b42d5fd7c398e07b2fJeff Sharkey                } else {
158ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                    // This should cover all other storage devices, like an SD card
159ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                    // or USB OTG drive plugged in. Using getBestVolumeDescription()
160ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay                    // will give us a nice string like "Samsung SD card" or "SanDisk USB drive"
161b521feaed410b6862baca9b42d5fd7c398e07b2fJeff Sharkey                    final VolumeInfo privateVol = mStorageManager.findPrivateForEmulated(volume);
162b521feaed410b6862baca9b42d5fd7c398e07b2fJeff Sharkey                    title = mStorageManager.getBestVolumeDescription(privateVol);
163b521feaed410b6862baca9b42d5fd7c398e07b2fJeff Sharkey                }
16427de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            } else if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
16527de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                rootId = volume.getFsUuid();
166b521feaed410b6862baca9b42d5fd7c398e07b2fJeff Sharkey                title = mStorageManager.getBestVolumeDescription(volume);
1671f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey            } else {
16827de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                // Unsupported volume; ignore
1691f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey                continue;
1701f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey            }
1711f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
17227de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            if (TextUtils.isEmpty(rootId)) {
17327de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                Log.d(TAG, "Missing UUID for " + volume.getId() + "; skipping");
17427de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                continue;
17527de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            }
17627de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            if (mRoots.containsKey(rootId)) {
17727de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                Log.w(TAG, "Duplicate UUID " + rootId + " for " + volume.getId() + "; skipping");
1781f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey                continue;
1791f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey            }
1801f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
181c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            final RootInfo root = new RootInfo();
182c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            mRoots.put(rootId, root);
183c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay
184c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            root.rootId = rootId;
185efa1761776160376278fa467ea31d8e3f621a286Steve McKay            root.flags = Root.FLAG_LOCAL_ONLY
186c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay                    | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
187c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay
188ba23e54d263fefaac96b3ce6b068e70ec6f06128Steve McKay            final DiskInfo disk = volume.getDisk();
189ba23e54d263fefaac96b3ce6b068e70ec6f06128Steve McKay            if (DEBUG) Log.d(TAG, "Disk for root " + rootId + " is " + disk);
190ba23e54d263fefaac96b3ce6b068e70ec6f06128Steve McKay            if (disk != null && disk.isSd()) {
191ba23e54d263fefaac96b3ce6b068e70ec6f06128Steve McKay                root.flags |= Root.FLAG_REMOVABLE_SD;
192ba23e54d263fefaac96b3ce6b068e70ec6f06128Steve McKay            } else if (disk != null && disk.isUsb()) {
193ba23e54d263fefaac96b3ce6b068e70ec6f06128Steve McKay                root.flags |= Root.FLAG_REMOVABLE_USB;
194ba23e54d263fefaac96b3ce6b068e70ec6f06128Steve McKay            }
195ba23e54d263fefaac96b3ce6b068e70ec6f06128Steve McKay
196efa1761776160376278fa467ea31d8e3f621a286Steve McKay            if (volume.isPrimary()) {
197efa1761776160376278fa467ea31d8e3f621a286Steve McKay                // save off the primary volume for subsequent "Home" dir initialization.
198efa1761776160376278fa467ea31d8e3f621a286Steve McKay                primaryVolume = volume;
1991719b3555dc9bff5394045585051e7d5684bceb1Aga Wronska                root.flags |= Root.FLAG_ADVANCED;
200efa1761776160376278fa467ea31d8e3f621a286Steve McKay            }
201c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            // Dunno when this would NOT be the case, but never hurts to be correct.
202c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            if (volume.isMountedWritable()) {
203c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay                root.flags |= Root.FLAG_SUPPORTS_CREATE;
204c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            }
205c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            root.title = title;
206c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
207c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay                root.flags |= Root.FLAG_HAS_SETTINGS;
208c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            }
209c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            if (volume.isVisibleForRead(userId)) {
210c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay                root.visiblePath = volume.getPathForUser(userId);
211c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            } else {
212c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay                root.visiblePath = null;
213c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            }
214c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            root.path = volume.getInternalPathForUser(userId);
2151f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey            try {
21627de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                root.docId = getDocIdForFile(root.path);
217c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            } catch (FileNotFoundException e) {
218c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay                throw new IllegalStateException(e);
219c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            }
220c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay        }
22127de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey
222ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay        // Finally, if primary storage is available we add the "Documents" directory.
223ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay        // If I recall correctly the actual directory is created on demand
224ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay        // by calling either getPathForUser, or getInternalPathForUser.
225c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay        if (primaryVolume != null && primaryVolume.isVisible()) {
226c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            final RootInfo root = new RootInfo();
227c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            root.rootId = ROOT_ID_HOME;
228c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            mRoots.put(root.rootId, root);
229ab3b8936dc1f4a24cffea422baf9ef51f0de05e7Steve McKay            root.title = getContext().getString(R.string.root_documents);
230c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay
231c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            // Only report bytes on *volumes*...as a matter of policy.
232c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            root.reportAvailableBytes = false;
233c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH
234c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay                    | Root.FLAG_SUPPORTS_IS_CHILD;
235c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay
236c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            // Dunno when this would NOT be the case, but never hurts to be correct.
237c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            if (primaryVolume.isMountedWritable()) {
238c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay                root.flags |= Root.FLAG_SUPPORTS_CREATE;
239c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            }
240c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay
241ecec7cb0623100526ef9ff68cc2d841ca8f71729Steve McKay            // Create the "Documents" directory on disk (don't use the localized title).
242c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            root.visiblePath = new File(
243ab3b8936dc1f4a24cffea422baf9ef51f0de05e7Steve McKay                    primaryVolume.getPathForUser(userId), Environment.DIRECTORY_DOCUMENTS);
244c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            root.path = new File(
245ab3b8936dc1f4a24cffea422baf9ef51f0de05e7Steve McKay                    primaryVolume.getInternalPathForUser(userId), Environment.DIRECTORY_DOCUMENTS);
246c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay            try {
247c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay                root.docId = getDocIdForFile(root.path);
2481f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey            } catch (FileNotFoundException e) {
2491f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey                throw new IllegalStateException(e);
2501f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey            }
2511f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        }
2521f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
2531f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        Log.d(TAG, "After updating volumes, found " + mRoots.size() + " active roots");
2541f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
25514a6df7d7626b5e3c739e4dafd11073a471c91a8Makoto Onuki        // Note this affects content://com.android.externalstorage.documents/root/39BD-07C5
25614a6df7d7626b5e3c739e4dafd11073a471c91a8Makoto Onuki        // as well as content://com.android.externalstorage.documents/document/*/children,
25714a6df7d7626b5e3c739e4dafd11073a471c91a8Makoto Onuki        // so just notify on content://com.android.externalstorage.documents/.
25814a6df7d7626b5e3c739e4dafd11073a471c91a8Makoto Onuki        getContext().getContentResolver().notifyChange(BASE_URI, null, false);
2591f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey    }
2601f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
261ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    private static String[] resolveRootProjection(String[] projection) {
262ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
263ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    }
264ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey
265ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    private static String[] resolveDocumentProjection(String[] projection) {
266ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
267ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    }
268ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey
269b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme
270aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    private String getDocIdForFile(File file) throws FileNotFoundException {
271b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme        return getDocIdForFileMaybeCreate(file, false);
272b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme    }
273b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme
274b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme    private String getDocIdForFileMaybeCreate(File file, boolean createNewDir)
275b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme            throws FileNotFoundException {
276aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        String path = file.getAbsolutePath();
2779e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
278aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        // Find the most-specific root path
27927de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        String mostSpecificId = null;
28027de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        String mostSpecificPath = null;
2811f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        synchronized (mRootsLock) {
28227de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            for (int i = 0; i < mRoots.size(); i++) {
28327de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                final String rootId = mRoots.keyAt(i);
28427de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                final String rootPath = mRoots.valueAt(i).path.getAbsolutePath();
28527de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                if (path.startsWith(rootPath) && (mostSpecificPath == null
28627de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                        || rootPath.length() > mostSpecificPath.length())) {
28727de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                    mostSpecificId = rootId;
28827de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                    mostSpecificPath = rootPath;
2891f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey                }
29092d7e697a864a3e18bef4ef256bb3eb339a66b4eJeff Sharkey            }
291aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
292dc2963aecaf38bf53d6de82957412a486049c207Jeff Sharkey
29327de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        if (mostSpecificPath == null) {
294aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throw new FileNotFoundException("Failed to find root that contains " + path);
295aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
296dc2963aecaf38bf53d6de82957412a486049c207Jeff Sharkey
297aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        // Start at first char of path under root
29827de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        final String rootPath = mostSpecificPath;
299aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (rootPath.equals(path)) {
300aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            path = "";
301aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        } else if (rootPath.endsWith("/")) {
302aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            path = path.substring(rootPath.length());
303aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        } else {
304aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            path = path.substring(rootPath.length() + 1);
3059e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey        }
306aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
307b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme        if (!file.exists() && createNewDir) {
308b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme            Log.i(TAG, "Creating new directory " + file);
309b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme            if (!file.mkdir()) {
310b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                Log.e(TAG, "Could not create directory " + file);
311b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme            }
312b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme        }
313b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme
31427de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        return mostSpecificId + ':' + path;
3159e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    }
3169e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
317aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    private File getFileForDocId(String docId) throws FileNotFoundException {
31827de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        return getFileForDocId(docId, false);
31927de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey    }
32027de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey
32127de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey    private File getFileForDocId(String docId, boolean visible) throws FileNotFoundException {
322aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        final int splitIndex = docId.indexOf(':', 1);
323aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        final String tag = docId.substring(0, splitIndex);
324aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        final String path = docId.substring(splitIndex + 1);
32520d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey
32627de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        RootInfo root;
3271f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        synchronized (mRootsLock) {
32827de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            root = mRoots.get(tag);
3291f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        }
33027de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        if (root == null) {
331aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throw new FileNotFoundException("No root for " + tag);
33292d7e697a864a3e18bef4ef256bb3eb339a66b4eJeff Sharkey        }
33327de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey
33427de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        File target = visible ? root.visiblePath : root.path;
33527de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        if (target == null) {
33627de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            return null;
33727de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        }
3383e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey        if (!target.exists()) {
3393e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey            target.mkdirs();
3403e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey        }
341aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        target = new File(target, path);
342aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (!target.exists()) {
343aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throw new FileNotFoundException("Missing file for " + docId + " at " + target);
34492d7e697a864a3e18bef4ef256bb3eb339a66b4eJeff Sharkey        }
345aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        return target;
34620d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey    }
34720d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey
348aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    private void includeFile(MatrixCursor result, String docId, File file)
349aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throws FileNotFoundException {
350aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (docId == null) {
351aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            docId = getDocIdForFile(file);
35292d7e697a864a3e18bef4ef256bb3eb339a66b4eJeff Sharkey        } else {
353aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            file = getFileForDocId(docId);
35420d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey        }
35520d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey
3569e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey        int flags = 0;
3579e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
3589e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey        if (file.canWrite()) {
3592a030b05a978281147df4d1cc4f12bc8d61c0729Jeff Sharkey            if (file.isDirectory()) {
3602a030b05a978281147df4d1cc4f12bc8d61c0729Jeff Sharkey                flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
361b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                flags |= Document.FLAG_SUPPORTS_DELETE;
362b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                flags |= Document.FLAG_SUPPORTS_RENAME;
3632273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski                flags |= Document.FLAG_SUPPORTS_MOVE;
3642a030b05a978281147df4d1cc4f12bc8d61c0729Jeff Sharkey            } else {
3652a030b05a978281147df4d1cc4f12bc8d61c0729Jeff Sharkey                flags |= Document.FLAG_SUPPORTS_WRITE;
36621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                flags |= Document.FLAG_SUPPORTS_DELETE;
367b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                flags |= Document.FLAG_SUPPORTS_RENAME;
3682273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski                flags |= Document.FLAG_SUPPORTS_MOVE;
3692a030b05a978281147df4d1cc4f12bc8d61c0729Jeff Sharkey            }
3709e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey        }
3719e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
37220d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey        final String mimeType = getTypeForFile(file);
37368dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        if (mArchiveHelper.isSupportedArchiveType(mimeType)) {
37468dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski            flags |= Document.FLAG_ARCHIVE;
37568dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        }
37668dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski
37768dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        final String displayName = file.getName();
3789e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey        if (mimeType.startsWith("image/")) {
379ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
3809e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey        }
3819e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
3829d0843df7e3984293dc4ab6ee2f9502e898b63aaJeff Sharkey        final RowBuilder row = result.newRow();
383b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, docId);
384b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME, displayName);
385b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Document.COLUMN_SIZE, file.length());
386b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Document.COLUMN_MIME_TYPE, mimeType);
387b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Document.COLUMN_FLAGS, flags);
38868dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        row.add(DocumentArchiveHelper.COLUMN_LOCAL_FILE_PATH, file.getPath());
389d5a4658cea8def24155849a8621608155dcba05cJeff Sharkey
390d5a4658cea8def24155849a8621608155dcba05cJeff Sharkey        // Only publish dates reasonably after epoch
391d5a4658cea8def24155849a8621608155dcba05cJeff Sharkey        long lastModified = file.lastModified();
392d5a4658cea8def24155849a8621608155dcba05cJeff Sharkey        if (lastModified > 31536000000L) {
393d5a4658cea8def24155849a8621608155dcba05cJeff Sharkey            row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
394d5a4658cea8def24155849a8621608155dcba05cJeff Sharkey        }
3959e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    }
3969e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
3979e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    @Override
398ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
399ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
4001f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        synchronized (mRootsLock) {
40127de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            for (RootInfo root : mRoots.values()) {
4021f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey                final RowBuilder row = result.newRow();
4031f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey                row.add(Root.COLUMN_ROOT_ID, root.rootId);
4041f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey                row.add(Root.COLUMN_FLAGS, root.flags);
4051f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey                row.add(Root.COLUMN_TITLE, root.title);
4061f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey                row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
407c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay                row.add(Root.COLUMN_AVAILABLE_BYTES,
408c6a4cd8c0f35a7e9d126ab09924f8f1f8422182aSteve McKay                        root.reportAvailableBytes ? root.path.getFreeSpace() : -1);
4091f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey            }
4109e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey        }
411ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        return result;
4129e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    }
4139e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
414aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
41521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    public boolean isChildDocument(String parentDocId, String docId) {
41621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        try {
41768dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski            if (mArchiveHelper.isArchivedDocument(docId)) {
41868dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski                return mArchiveHelper.isChildDocument(parentDocId, docId);
41968dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski            }
420e192cb71d6a8e2e85c32cfb3d61ea5770d1e4e00Tomasz Mikolajewski            // Archives do not contain regular files.
421e192cb71d6a8e2e85c32cfb3d61ea5770d1e4e00Tomasz Mikolajewski            if (mArchiveHelper.isArchivedDocument(parentDocId)) {
422e192cb71d6a8e2e85c32cfb3d61ea5770d1e4e00Tomasz Mikolajewski                return false;
423e192cb71d6a8e2e85c32cfb3d61ea5770d1e4e00Tomasz Mikolajewski            }
42468dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski
42521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            final File parent = getFileForDocId(parentDocId).getCanonicalFile();
42621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            final File doc = getFileForDocId(docId).getCanonicalFile();
42721de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            return FileUtils.contains(parent, doc);
42821de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        } catch (IOException e) {
42921de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            throw new IllegalArgumentException(
43021de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                    "Failed to determine if " + docId + " is child of " + parentDocId + ": " + e);
43121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        }
43221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    }
43321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey
43421de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    @Override
435aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public String createDocument(String docId, String mimeType, String displayName)
436aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throws FileNotFoundException {
4370cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        displayName = FileUtils.buildValidFatFilename(displayName);
4380cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
439aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        final File parent = getFileForDocId(docId);
44021de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        if (!parent.isDirectory()) {
44121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            throw new IllegalArgumentException("Parent document isn't a directory");
44221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        }
443aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
44462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa        final File file = FileUtils.buildUniqueFile(parent, mimeType, displayName);
445ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
446aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            if (!file.mkdir()) {
447aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                throw new IllegalStateException("Failed to mkdir " + file);
448aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            }
44920d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey        } else {
450aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            try {
451aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                if (!file.createNewFile()) {
452aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                    throw new IllegalStateException("Failed to touch " + file);
453aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                }
454aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            } catch (IOException e) {
455aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                throw new IllegalStateException("Failed to touch " + file + ": " + e);
456aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            }
4579e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey        }
4580cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
459aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        return getDocIdForFile(file);
46020d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey    }
4619e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
462aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
463b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey    public String renameDocument(String docId, String displayName) throws FileNotFoundException {
4640cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        // Since this provider treats renames as generating a completely new
4650cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        // docId, we're okay with letting the MIME type change.
4660cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey        displayName = FileUtils.buildValidFatFilename(displayName);
4670cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey
468b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        final File before = getFileForDocId(docId);
469b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        final File after = new File(before.getParentFile(), displayName);
470b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        if (after.exists()) {
471b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey            throw new IllegalStateException("Already exists " + after);
472b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        }
473b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        if (!before.renameTo(after)) {
474b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey            throw new IllegalStateException("Failed to rename to " + after);
475b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        }
476b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        final String afterDocId = getDocIdForFile(after);
477b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        if (!TextUtils.equals(docId, afterDocId)) {
478b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey            return afterDocId;
479b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        } else {
480b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey            return null;
481b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        }
482b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey    }
483b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey
484b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey    @Override
485aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public void deleteDocument(String docId) throws FileNotFoundException {
486aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        final File file = getFileForDocId(docId);
487307d424467daf82542715c66735daebd87af6dceJeff Sharkey        final boolean isDirectory = file.isDirectory();
488307d424467daf82542715c66735daebd87af6dceJeff Sharkey        if (isDirectory) {
489b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey            FileUtils.deleteContents(file);
490b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        }
491aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (!file.delete()) {
492aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throw new IllegalStateException("Failed to delete " + file);
4939e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey        }
494307d424467daf82542715c66735daebd87af6dceJeff Sharkey
495307d424467daf82542715c66735daebd87af6dceJeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
496307d424467daf82542715c66735daebd87af6dceJeff Sharkey        final Uri externalUri = MediaStore.Files.getContentUri("external");
497307d424467daf82542715c66735daebd87af6dceJeff Sharkey
498307d424467daf82542715c66735daebd87af6dceJeff Sharkey        // Remove media store entries for any files inside this directory, using
499307d424467daf82542715c66735daebd87af6dceJeff Sharkey        // path prefix match. Logic borrowed from MtpDatabase.
500307d424467daf82542715c66735daebd87af6dceJeff Sharkey        if (isDirectory) {
501307d424467daf82542715c66735daebd87af6dceJeff Sharkey            final String path = file.getAbsolutePath() + "/";
502307d424467daf82542715c66735daebd87af6dceJeff Sharkey            resolver.delete(externalUri,
503307d424467daf82542715c66735daebd87af6dceJeff Sharkey                    "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
504307d424467daf82542715c66735daebd87af6dceJeff Sharkey                    new String[] { path + "%", Integer.toString(path.length()), path });
505307d424467daf82542715c66735daebd87af6dceJeff Sharkey        }
506307d424467daf82542715c66735daebd87af6dceJeff Sharkey
507307d424467daf82542715c66735daebd87af6dceJeff Sharkey        // Remove media store entry for this exact file.
508307d424467daf82542715c66735daebd87af6dceJeff Sharkey        final String path = file.getAbsolutePath();
509307d424467daf82542715c66735daebd87af6dceJeff Sharkey        resolver.delete(externalUri,
510307d424467daf82542715c66735daebd87af6dceJeff Sharkey                "_data LIKE ?1 AND lower(_data)=lower(?2)",
511307d424467daf82542715c66735daebd87af6dceJeff Sharkey                new String[] { path, path });
5129e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    }
5139e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
5149e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    @Override
515d46ecbcc5322cf817e64591e985f1f2a6167e9a7Tomasz Mikolajewski    public String moveDocument(String sourceDocumentId, String sourceParentDocumentId,
516d46ecbcc5322cf817e64591e985f1f2a6167e9a7Tomasz Mikolajewski            String targetParentDocumentId)
5172273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski            throws FileNotFoundException {
5182273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski        final File before = getFileForDocId(sourceDocumentId);
5192273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski        final File after = new File(getFileForDocId(targetParentDocumentId), before.getName());
5202273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski
5212273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski        if (after.exists()) {
5222273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski            throw new IllegalStateException("Already exists " + after);
5232273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski        }
5242273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski        if (!before.renameTo(after)) {
5252273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski            throw new IllegalStateException("Failed to move to " + after);
5262273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski        }
5272273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski        return getDocIdForFile(after);
5282273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski    }
5292273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski
5302273e0f095e3cd459d2c72982c396e9941c876e2Tomasz Mikolajewski    @Override
531ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public Cursor queryDocument(String documentId, String[] projection)
532ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            throws FileNotFoundException {
53368dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        if (mArchiveHelper.isArchivedDocument(documentId)) {
53468dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski            return mArchiveHelper.queryDocument(documentId, projection);
53568dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        }
53668dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski
537ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
538ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        includeFile(result, documentId, null);
539aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        return result;
540aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
541aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
542aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
543ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public Cursor queryChildDocuments(
544ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            String parentDocumentId, String[] projection, String sortOrder)
545ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            throws FileNotFoundException {
54668dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        if (mArchiveHelper.isArchivedDocument(parentDocumentId) ||
54768dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski                mArchiveHelper.isSupportedArchiveType(getDocumentType(parentDocumentId))) {
54868dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski            return mArchiveHelper.queryChildDocuments(parentDocumentId, projection, sortOrder);
54968dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        }
55068dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski
551ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        final File parent = getFileForDocId(parentDocumentId);
552db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        final MatrixCursor result = new DirectoryCursor(
553db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey                resolveDocumentProjection(projection), parentDocumentId, parent);
554aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        for (File file : parent.listFiles()) {
555aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            includeFile(result, null, file);
5566398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey        }
557aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        return result;
558aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
5596398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey
560aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
5613e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey    public Cursor querySearchDocuments(String rootId, String query, String[] projection)
562ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            throws FileNotFoundException {
563ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
5641f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey
5651f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        final File parent;
5661f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        synchronized (mRootsLock) {
56727de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            parent = mRoots.get(rootId).path;
5681f706c6cd1cb841adadc2babc57a34e5728983ecJeff Sharkey        }
569aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
570aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        final LinkedList<File> pending = new LinkedList<File>();
571aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        pending.add(parent);
5724ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey        while (!pending.isEmpty() && result.getCount() < 24) {
573aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            final File file = pending.removeFirst();
574aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            if (file.isDirectory()) {
575aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                for (File child : file.listFiles()) {
576aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                    pending.add(child);
577aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                }
5784ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey            }
5794ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey            if (file.getName().toLowerCase().contains(query)) {
5804ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey                includeFile(result, null, file);
5816398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey            }
5826398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey        }
583aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        return result;
5846398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey    }
5856398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey
5866398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey    @Override
587ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public String getDocumentType(String documentId) throws FileNotFoundException {
58868dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        if (mArchiveHelper.isArchivedDocument(documentId)) {
58968dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski            return mArchiveHelper.getDocumentType(documentId);
59068dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        }
59168dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski
592ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        final File file = getFileForDocId(documentId);
593aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        return getTypeForFile(file);
594aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
59520d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey
596aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
597ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public ParcelFileDescriptor openDocument(
598ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            String documentId, String mode, CancellationSignal signal)
599aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throws FileNotFoundException {
60068dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        if (mArchiveHelper.isArchivedDocument(documentId)) {
60168dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski            return mArchiveHelper.openDocument(documentId, mode, signal);
60268dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski        }
60368dec40633db637b804b5cc292d4ca981e61c379Tomasz Mikolajewski
604ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        final File file = getFileForDocId(documentId);
60527de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        final File visibleFile = getFileForDocId(documentId, true);
60627de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey
607ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey        final int pfdMode = ParcelFileDescriptor.parseMode(mode);
60827de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY || visibleFile == null) {
609ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey            return ParcelFileDescriptor.open(file, pfdMode);
610ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey        } else {
611ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey            try {
612ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey                // When finished writing, kick off media scanner
613ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey                return ParcelFileDescriptor.open(file, pfdMode, mHandler, new OnCloseListener() {
614ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey                    @Override
615ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey                    public void onClose(IOException e) {
616ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey                        final Intent intent = new Intent(
617ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey                                Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
61827de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                        intent.setData(Uri.fromFile(visibleFile));
619ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey                        getContext().sendBroadcast(intent);
620ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey                    }
621ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey                });
622ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey            } catch (IOException e) {
623ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey                throw new FileNotFoundException("Failed to open for writing: " + e);
624ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey            }
625ab1e9bddd83466f15c2f61fbc0b942c6416eb87aJeff Sharkey        }
6269e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    }
6279e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
6289e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    @Override
629aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public AssetFileDescriptor openDocumentThumbnail(
630ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            String documentId, Point sizeHint, CancellationSignal signal)
631ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            throws FileNotFoundException {
6327e326a867c12222c7fb359d2ce9ab0b95b8ab6c4Tomasz Mikolajewski        if (mArchiveHelper.isArchivedDocument(documentId)) {
6337e326a867c12222c7fb359d2ce9ab0b95b8ab6c4Tomasz Mikolajewski            return mArchiveHelper.openDocumentThumbnail(documentId, sizeHint, signal);
6347e326a867c12222c7fb359d2ce9ab0b95b8ab6c4Tomasz Mikolajewski        }
6357e326a867c12222c7fb359d2ce9ab0b95b8ab6c4Tomasz Mikolajewski
636ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        final File file = getFileForDocId(documentId);
637c1c8f3f97d344a24bfddcb56a8be05e7e2fabe9eJeff Sharkey        return DocumentsContract.openImageThumbnail(file);
6389e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey    }
6399e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey
64027de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey    @Override
64127de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
64227de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
64327de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        synchronized (mRootsLock) {
64427de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            for (int i = 0; i < mRoots.size(); i++) {
64527de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                final RootInfo root = mRoots.valueAt(i);
64627de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                pw.println("Root{" + root.rootId + "}:");
64727de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                pw.increaseIndent();
64827de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                pw.printPair("flags", DebugUtils.flagsToString(Root.class, "FLAG_", root.flags));
64927de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                pw.println();
65027de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                pw.printPair("title", root.title);
65127de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                pw.printPair("docId", root.docId);
65227de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                pw.println();
65327de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                pw.printPair("path", root.path);
65427de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                pw.printPair("visiblePath", root.visiblePath);
65527de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                pw.decreaseIndent();
65627de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey                pw.println();
65727de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey            }
65827de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey        }
65927de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey    }
66027de30d31c3e79bc429cb71aed9681c55243f18dJeff Sharkey
661b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme    @Override
662b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme    public Bundle call(String method, String arg, Bundle extras) {
663b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme        Bundle bundle = super.call(method, arg, extras);
664b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme        if (bundle == null && !TextUtils.isEmpty(method)) {
665b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme            switch (method) {
666b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                case "getDocIdForFileCreateNewDir": {
667b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                    getContext().enforceCallingPermission(
668b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                            android.Manifest.permission.MANAGE_DOCUMENTS, null);
669b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                    if (TextUtils.isEmpty(arg)) {
670b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                        return null;
671b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                    }
672b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                    try {
673b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                        final String docId = getDocIdForFileMaybeCreate(new File(arg), true);
674b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                        bundle = new Bundle();
675b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                        bundle.putString("DOC_ID", docId);
676b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                    } catch (FileNotFoundException e) {
677b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                        Log.w(TAG, "file '" + arg + "' not found");
678b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                        return null;
679b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                    }
680b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                    break;
681b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                }
682b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                default:
683b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme                    Log.w(TAG, "unknown method passed to call(): " + method);
684b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme            }
685b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme        }
686b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme        return bundle;
687b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme    }
688b012f913cbb3e91572817914537ec16e6138a6dbFelipe Leme
689aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    private static String getTypeForFile(File file) {
690aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (file.isDirectory()) {
691ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            return Document.MIME_TYPE_DIR;
692aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        } else {
693aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            return getTypeForName(file.getName());
694aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
695aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
696aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
697aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    private static String getTypeForName(String name) {
698aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        final int lastDot = name.lastIndexOf('.');
699aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (lastDot >= 0) {
70096c620595bd0585f934b0971b4552c57845e9a78Jeff Sharkey            final String extension = name.substring(lastDot + 1).toLowerCase();
701aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
702aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            if (mime != null) {
703aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                return mime;
70420d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey            }
70520d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey        }
706aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
707aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        return "application/octet-stream";
70820d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey    }
70920d96d8aff2193d548977e23ce5158657cac94e0Jeff Sharkey
710db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey    private void startObserving(File file, Uri notifyUri) {
711db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        synchronized (mObservers) {
712db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            DirectoryObserver observer = mObservers.get(file);
713db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            if (observer == null) {
714db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey                observer = new DirectoryObserver(
715db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey                        file, getContext().getContentResolver(), notifyUri);
716db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey                observer.startWatching();
717db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey                mObservers.put(file, observer);
718db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            }
719db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            observer.mRefCount++;
720db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
721db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            if (LOG_INOTIFY) Log.d(TAG, "after start: " + observer);
722db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        }
723db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey    }
724db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
725db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey    private void stopObserving(File file) {
726db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        synchronized (mObservers) {
727db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            DirectoryObserver observer = mObservers.get(file);
728db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            if (observer == null) return;
729db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
730db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            observer.mRefCount--;
731db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            if (observer.mRefCount == 0) {
732db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey                mObservers.remove(file);
733db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey                observer.stopWatching();
734db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            }
735db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
736db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            if (LOG_INOTIFY) Log.d(TAG, "after stop: " + observer);
737db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        }
738db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey    }
739db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
740db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey    private static class DirectoryObserver extends FileObserver {
741db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        private static final int NOTIFY_EVENTS = ATTRIB | CLOSE_WRITE | MOVED_FROM | MOVED_TO
742db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey                | CREATE | DELETE | DELETE_SELF | MOVE_SELF;
743db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
744db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        private final File mFile;
745db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        private final ContentResolver mResolver;
746db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        private final Uri mNotifyUri;
747db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
748db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        private int mRefCount = 0;
749db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
750db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        public DirectoryObserver(File file, ContentResolver resolver, Uri notifyUri) {
751db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            super(file.getAbsolutePath(), NOTIFY_EVENTS);
752db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            mFile = file;
753db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            mResolver = resolver;
754db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            mNotifyUri = notifyUri;
755db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        }
756db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
757db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        @Override
758db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        public void onEvent(int event, String path) {
759db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            if ((event & NOTIFY_EVENTS) != 0) {
760db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey                if (LOG_INOTIFY) Log.d(TAG, "onEvent() " + event + " at " + path);
761db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey                mResolver.notifyChange(mNotifyUri, null, false);
762db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            }
763db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        }
764db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
765db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        @Override
766db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        public String toString() {
767db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            return "DirectoryObserver{file=" + mFile.getAbsolutePath() + ", ref=" + mRefCount + "}";
768db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        }
769db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey    }
770db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
771db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey    private class DirectoryCursor extends MatrixCursor {
772db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        private final File mFile;
773db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
774db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        public DirectoryCursor(String[] columnNames, String docId, File file) {
775db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            super(columnNames);
776db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
777db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            final Uri notifyUri = DocumentsContract.buildChildDocumentsUri(
778db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey                    AUTHORITY, docId);
779db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            setNotificationUri(getContext().getContentResolver(), notifyUri);
780db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
781db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            mFile = file;
782db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            startObserving(mFile, notifyUri);
783db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        }
784db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey
785db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        @Override
786db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        public void close() {
787db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            super.close();
788db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey            stopObserving(mFile);
789db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey        }
790db5ef125007644daa94aeaf1bd8637f4e0095e94Jeff Sharkey    }
7919e0036ed7d3260d79cc5f9ffd8e3bbe760699924Jeff Sharkey}
792