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