Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2013 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.externalstorage; |
| 18 | |
Ivan Chiang | 730b3a3 | 2019-08-21 16:12:54 +0800 | [diff] [blame] | 19 | import android.annotation.NonNull; |
Garfield Tan | 06940e1 | 2016-10-07 16:03:17 -0700 | [diff] [blame] | 20 | import android.annotation.Nullable; |
Jeff Sharkey | 06823d4 | 2017-05-09 16:55:29 -0600 | [diff] [blame] | 21 | import android.app.usage.StorageStatsManager; |
Jeff Sharkey | db5ef12 | 2013-10-25 17:12:49 -0700 | [diff] [blame] | 22 | import android.content.ContentResolver; |
Garfield Tan | 92b96ba | 2016-11-01 14:33:48 -0700 | [diff] [blame] | 23 | import android.content.UriPermission; |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 24 | import android.database.Cursor; |
| 25 | import android.database.MatrixCursor; |
Jeff Sharkey | 9d0843d | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 26 | import android.database.MatrixCursor.RowBuilder; |
Jeff Sharkey | db5ef12 | 2013-10-25 17:12:49 -0700 | [diff] [blame] | 27 | import android.net.Uri; |
Ben Lin | e7822fb | 2016-06-24 15:21:08 -0700 | [diff] [blame] | 28 | import android.os.Binder; |
Felipe Leme | b012f91 | 2016-01-22 16:49:55 -0800 | [diff] [blame] | 29 | import android.os.Bundle; |
Steve McKay | 5c462a0 | 2016-01-29 16:13:21 -0800 | [diff] [blame] | 30 | import android.os.Environment; |
Jeff Sharkey | b78b754d | 2018-01-04 15:07:38 -0700 | [diff] [blame] | 31 | import android.os.IBinder; |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 32 | import android.os.UserHandle; |
Jeff Sharkey | b78b754d | 2018-01-04 15:07:38 -0700 | [diff] [blame] | 33 | import android.os.UserManager; |
Steve McKay | ba23e54 | 2016-03-02 15:15:00 -0800 | [diff] [blame] | 34 | import android.os.storage.DiskInfo; |
Zim | 5631b6e | 2020-05-01 15:54:55 +0100 | [diff] [blame] | 35 | import android.os.storage.StorageEventListener; |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 36 | import android.os.storage.StorageManager; |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 37 | import android.os.storage.VolumeInfo; |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 38 | import android.provider.DocumentsContract; |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 39 | import android.provider.DocumentsContract.Document; |
Garfield Tan | aba97f3 | 2016-10-06 17:34:19 +0000 | [diff] [blame] | 40 | import android.provider.DocumentsContract.Path; |
Garfield Tan | 06940e1 | 2016-10-07 16:03:17 -0700 | [diff] [blame] | 41 | import android.provider.DocumentsContract.Root; |
Steve McKay | ecec7cb | 2016-03-02 11:35:39 -0800 | [diff] [blame] | 42 | import android.provider.Settings; |
Jeff Sharkey | b00d5ea | 2018-05-01 10:01:52 -0600 | [diff] [blame] | 43 | import android.system.ErrnoException; |
| 44 | import android.system.Os; |
| 45 | import android.system.OsConstants; |
Jeff Sharkey | b7e1255 | 2014-05-21 22:22:03 -0700 | [diff] [blame] | 46 | import android.text.TextUtils; |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 47 | import android.util.ArrayMap; |
| 48 | import android.util.DebugUtils; |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 49 | import android.util.Log; |
Garfield Tan | aba97f3 | 2016-10-06 17:34:19 +0000 | [diff] [blame] | 50 | import android.util.Pair; |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 51 | |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 52 | import com.android.internal.annotations.GuardedBy; |
Ivan Chiang | 698340f | 2019-11-15 19:02:46 +0800 | [diff] [blame] | 53 | import com.android.internal.annotations.VisibleForTesting; |
Garfield Tan | 75379db | 2017-02-08 15:32:56 -0800 | [diff] [blame] | 54 | import com.android.internal.content.FileSystemProvider; |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 55 | import com.android.internal.util.IndentingPrintWriter; |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 56 | |
| 57 | import java.io.File; |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 58 | import java.io.FileDescriptor; |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 59 | import java.io.FileNotFoundException; |
Jeff Sharkey | 06823d4 | 2017-05-09 16:55:29 -0600 | [diff] [blame] | 60 | import java.io.IOException; |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 61 | import java.io.PrintWriter; |
Garfield Tan | 75379db | 2017-02-08 15:32:56 -0800 | [diff] [blame] | 62 | import java.util.Collections; |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 63 | import java.util.List; |
Garfield Tan | 92b96ba | 2016-11-01 14:33:48 -0700 | [diff] [blame] | 64 | import java.util.Objects; |
Jeff Sharkey | 06823d4 | 2017-05-09 16:55:29 -0600 | [diff] [blame] | 65 | import java.util.UUID; |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 66 | |
Garfield Tan | 75379db | 2017-02-08 15:32:56 -0800 | [diff] [blame] | 67 | public class ExternalStorageProvider extends FileSystemProvider { |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 68 | private static final String TAG = "ExternalStorage"; |
| 69 | |
Steve McKay | ecec7cb | 2016-03-02 11:35:39 -0800 | [diff] [blame] | 70 | private static final boolean DEBUG = false; |
Jeff Sharkey | db5ef12 | 2013-10-25 17:12:49 -0700 | [diff] [blame] | 71 | |
Amin Shaikh | 305e87e | 2018-11-19 12:33:27 -0500 | [diff] [blame] | 72 | public static final String AUTHORITY = DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY; |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 73 | |
Makoto Onuki | 14a6df7 | 2015-07-01 14:55:14 -0700 | [diff] [blame] | 74 | private static final Uri BASE_URI = |
| 75 | new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); |
| 76 | |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 77 | // docId format: root:path/to/file |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 78 | |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 79 | private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { |
Jeff Sharkey | 6efba22 | 2013-09-27 16:44:11 -0700 | [diff] [blame] | 80 | Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, |
Ivan Chiang | c26d3c2 | 2019-01-10 19:29:01 +0800 | [diff] [blame] | 81 | Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, Root.COLUMN_QUERY_ARGS |
Jeff Sharkey | 9d0843d | 2013-05-07 12:41:33 -0700 | [diff] [blame] | 82 | }; |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 83 | |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 84 | private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { |
| 85 | Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, |
| 86 | Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE, |
| 87 | }; |
| 88 | |
| 89 | private static class RootInfo { |
| 90 | public String rootId; |
Ben Lin | e7822fb | 2016-06-24 15:21:08 -0700 | [diff] [blame] | 91 | public String volumeId; |
Jeff Sharkey | 06823d4 | 2017-05-09 16:55:29 -0600 | [diff] [blame] | 92 | public UUID storageUuid; |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 93 | public int flags; |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 94 | public String title; |
| 95 | public String docId; |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 96 | public File visiblePath; |
| 97 | public File path; |
Steve McKay | c6a4cd8 | 2015-11-18 14:56:50 -0800 | [diff] [blame] | 98 | public boolean reportAvailableBytes = true; |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 99 | } |
| 100 | |
Amin Shaikh | 305e87e | 2018-11-19 12:33:27 -0500 | [diff] [blame] | 101 | private static final String ROOT_ID_PRIMARY_EMULATED = |
| 102 | DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID; |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 103 | |
Jeff Sharkey | 4018283 | 2019-12-18 14:06:48 -0700 | [diff] [blame] | 104 | private static final String GET_DOCUMENT_URI_CALL = "get_document_uri"; |
| 105 | private static final String GET_MEDIA_URI_CALL = "get_media_uri"; |
| 106 | |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 107 | private StorageManager mStorageManager; |
Jeff Sharkey | b78b754d | 2018-01-04 15:07:38 -0700 | [diff] [blame] | 108 | private UserManager mUserManager; |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 109 | |
| 110 | private final Object mRootsLock = new Object(); |
| 111 | |
| 112 | @GuardedBy("mRootsLock") |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 113 | private ArrayMap<String, RootInfo> mRoots = new ArrayMap<>(); |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 114 | |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 115 | @Override |
| 116 | public boolean onCreate() { |
Garfield Tan | 75379db | 2017-02-08 15:32:56 -0800 | [diff] [blame] | 117 | super.onCreate(DEFAULT_DOCUMENT_PROJECTION); |
| 118 | |
Jeff Sharkey | b78b754d | 2018-01-04 15:07:38 -0700 | [diff] [blame] | 119 | mStorageManager = getContext().getSystemService(StorageManager.class); |
| 120 | mUserManager = getContext().getSystemService(UserManager.class); |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 121 | |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 122 | updateVolumes(); |
Zim | 5631b6e | 2020-05-01 15:54:55 +0100 | [diff] [blame] | 123 | |
| 124 | mStorageManager.registerListener(new StorageEventListener() { |
| 125 | @Override |
| 126 | public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { |
| 127 | updateVolumes(); |
| 128 | } |
| 129 | }); |
| 130 | |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 131 | return true; |
| 132 | } |
| 133 | |
Jeff Sharkey | b78b754d | 2018-01-04 15:07:38 -0700 | [diff] [blame] | 134 | private void enforceShellRestrictions() { |
| 135 | if (UserHandle.getCallingAppId() == android.os.Process.SHELL_UID |
| 136 | && mUserManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) { |
| 137 | throw new SecurityException( |
| 138 | "Shell user cannot access files for user " + UserHandle.myUserId()); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | @Override |
Philip P. Moltmann | 128b703 | 2019-09-27 08:44:12 -0700 | [diff] [blame] | 143 | protected int enforceReadPermissionInner(Uri uri, String callingPkg, |
| 144 | @Nullable String featureId, IBinder callerToken) throws SecurityException { |
Jeff Sharkey | b78b754d | 2018-01-04 15:07:38 -0700 | [diff] [blame] | 145 | enforceShellRestrictions(); |
Philip P. Moltmann | 128b703 | 2019-09-27 08:44:12 -0700 | [diff] [blame] | 146 | return super.enforceReadPermissionInner(uri, callingPkg, featureId, callerToken); |
Jeff Sharkey | b78b754d | 2018-01-04 15:07:38 -0700 | [diff] [blame] | 147 | } |
| 148 | |
| 149 | @Override |
Philip P. Moltmann | 128b703 | 2019-09-27 08:44:12 -0700 | [diff] [blame] | 150 | protected int enforceWritePermissionInner(Uri uri, String callingPkg, |
| 151 | @Nullable String featureId, IBinder callerToken) throws SecurityException { |
Jeff Sharkey | b78b754d | 2018-01-04 15:07:38 -0700 | [diff] [blame] | 152 | enforceShellRestrictions(); |
Philip P. Moltmann | 128b703 | 2019-09-27 08:44:12 -0700 | [diff] [blame] | 153 | return super.enforceWritePermissionInner(uri, callingPkg, featureId, callerToken); |
Jeff Sharkey | b78b754d | 2018-01-04 15:07:38 -0700 | [diff] [blame] | 154 | } |
| 155 | |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 156 | public void updateVolumes() { |
| 157 | synchronized (mRootsLock) { |
| 158 | updateVolumesLocked(); |
| 159 | } |
| 160 | } |
| 161 | |
Andreas Gampe | b5889307 | 2018-09-05 16:52:31 -0700 | [diff] [blame] | 162 | @GuardedBy("mRootsLock") |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 163 | private void updateVolumesLocked() { |
| 164 | mRoots.clear(); |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 165 | |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 166 | final int userId = UserHandle.myUserId(); |
| 167 | final List<VolumeInfo> volumes = mStorageManager.getVolumes(); |
| 168 | for (VolumeInfo volume : volumes) { |
Zim | 2dca320 | 2019-11-26 11:36:04 +0000 | [diff] [blame] | 169 | if (!volume.isMountedReadable() || volume.getMountUserId() != userId) continue; |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 170 | |
| 171 | final String rootId; |
Jeff Sharkey | b521fea | 2015-06-15 21:09:10 -0700 | [diff] [blame] | 172 | final String title; |
Jeff Sharkey | 06823d4 | 2017-05-09 16:55:29 -0600 | [diff] [blame] | 173 | final UUID storageUuid; |
Jeff Sharkey | b521fea | 2015-06-15 21:09:10 -0700 | [diff] [blame] | 174 | if (volume.getType() == VolumeInfo.TYPE_EMULATED) { |
Zim | 17be6f9 | 2019-09-25 14:37:55 +0100 | [diff] [blame] | 175 | // We currently only support a single emulated volume per user mounted at |
Jeff Sharkey | b521fea | 2015-06-15 21:09:10 -0700 | [diff] [blame] | 176 | // a time, and it's always considered the primary |
Steve McKay | ecec7cb | 2016-03-02 11:35:39 -0800 | [diff] [blame] | 177 | if (DEBUG) Log.d(TAG, "Found primary volume: " + volume); |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 178 | rootId = ROOT_ID_PRIMARY_EMULATED; |
Steve McKay | ecec7cb | 2016-03-02 11:35:39 -0800 | [diff] [blame] | 179 | |
Zim | 17be6f9 | 2019-09-25 14:37:55 +0100 | [diff] [blame] | 180 | if (volume.isPrimaryEmulatedForUser(userId)) { |
Steve McKay | ecec7cb | 2016-03-02 11:35:39 -0800 | [diff] [blame] | 181 | // This is basically the user's primary device storage. |
| 182 | // Use device name for the volume since this is likely same thing |
| 183 | // the user sees when they mount their phone on another device. |
| 184 | String deviceName = Settings.Global.getString( |
| 185 | getContext().getContentResolver(), Settings.Global.DEVICE_NAME); |
| 186 | |
| 187 | // Device name should always be set. In case it isn't, though, |
| 188 | // fall back to a localized "Internal Storage" string. |
| 189 | title = !TextUtils.isEmpty(deviceName) |
| 190 | ? deviceName |
| 191 | : getContext().getString(R.string.root_internal_storage); |
Jeff Sharkey | 06823d4 | 2017-05-09 16:55:29 -0600 | [diff] [blame] | 192 | storageUuid = StorageManager.UUID_DEFAULT; |
Jeff Sharkey | b521fea | 2015-06-15 21:09:10 -0700 | [diff] [blame] | 193 | } else { |
Steve McKay | ecec7cb | 2016-03-02 11:35:39 -0800 | [diff] [blame] | 194 | // This should cover all other storage devices, like an SD card |
| 195 | // or USB OTG drive plugged in. Using getBestVolumeDescription() |
| 196 | // will give us a nice string like "Samsung SD card" or "SanDisk USB drive" |
Jeff Sharkey | b521fea | 2015-06-15 21:09:10 -0700 | [diff] [blame] | 197 | final VolumeInfo privateVol = mStorageManager.findPrivateForEmulated(volume); |
| 198 | title = mStorageManager.getBestVolumeDescription(privateVol); |
Jeff Sharkey | 06823d4 | 2017-05-09 16:55:29 -0600 | [diff] [blame] | 199 | storageUuid = StorageManager.convert(privateVol.fsUuid); |
Jeff Sharkey | b521fea | 2015-06-15 21:09:10 -0700 | [diff] [blame] | 200 | } |
Zim | 2dca320 | 2019-11-26 11:36:04 +0000 | [diff] [blame] | 201 | } else if (volume.getType() == VolumeInfo.TYPE_PUBLIC |
| 202 | || volume.getType() == VolumeInfo.TYPE_STUB) { |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 203 | rootId = volume.getFsUuid(); |
Jeff Sharkey | b521fea | 2015-06-15 21:09:10 -0700 | [diff] [blame] | 204 | title = mStorageManager.getBestVolumeDescription(volume); |
Jeff Sharkey | 06823d4 | 2017-05-09 16:55:29 -0600 | [diff] [blame] | 205 | storageUuid = null; |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 206 | } else { |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 207 | // Unsupported volume; ignore |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 208 | continue; |
| 209 | } |
| 210 | |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 211 | if (TextUtils.isEmpty(rootId)) { |
| 212 | Log.d(TAG, "Missing UUID for " + volume.getId() + "; skipping"); |
| 213 | continue; |
| 214 | } |
| 215 | if (mRoots.containsKey(rootId)) { |
| 216 | Log.w(TAG, "Duplicate UUID " + rootId + " for " + volume.getId() + "; skipping"); |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 217 | continue; |
| 218 | } |
| 219 | |
Steve McKay | c6a4cd8 | 2015-11-18 14:56:50 -0800 | [diff] [blame] | 220 | final RootInfo root = new RootInfo(); |
| 221 | mRoots.put(rootId, root); |
| 222 | |
| 223 | root.rootId = rootId; |
Ben Lin | e7822fb | 2016-06-24 15:21:08 -0700 | [diff] [blame] | 224 | root.volumeId = volume.id; |
Jeff Sharkey | 06823d4 | 2017-05-09 16:55:29 -0600 | [diff] [blame] | 225 | root.storageUuid = storageUuid; |
Steve McKay | efa1761 | 2016-01-29 18:15:39 -0800 | [diff] [blame] | 226 | root.flags = Root.FLAG_LOCAL_ONLY |
Garfield Tan | aba97f3 | 2016-10-06 17:34:19 +0000 | [diff] [blame] | 227 | | Root.FLAG_SUPPORTS_SEARCH |
| 228 | | Root.FLAG_SUPPORTS_IS_CHILD; |
Steve McKay | c6a4cd8 | 2015-11-18 14:56:50 -0800 | [diff] [blame] | 229 | |
Steve McKay | ba23e54 | 2016-03-02 15:15:00 -0800 | [diff] [blame] | 230 | final DiskInfo disk = volume.getDisk(); |
| 231 | if (DEBUG) Log.d(TAG, "Disk for root " + rootId + " is " + disk); |
| 232 | if (disk != null && disk.isSd()) { |
| 233 | root.flags |= Root.FLAG_REMOVABLE_SD; |
| 234 | } else if (disk != null && disk.isUsb()) { |
| 235 | root.flags |= Root.FLAG_REMOVABLE_USB; |
| 236 | } |
| 237 | |
Takamasa Kuramitsu | f5f03fc | 2018-10-04 17:56:37 +0900 | [diff] [blame] | 238 | if (volume.getType() != VolumeInfo.TYPE_EMULATED) { |
Ben Lin | e7822fb | 2016-06-24 15:21:08 -0700 | [diff] [blame] | 239 | root.flags |= Root.FLAG_SUPPORTS_EJECT; |
| 240 | } |
| 241 | |
Steve McKay | efa1761 | 2016-01-29 18:15:39 -0800 | [diff] [blame] | 242 | if (volume.isPrimary()) { |
Aga Wronska | 1719b35 | 2016-03-21 11:28:03 -0700 | [diff] [blame] | 243 | root.flags |= Root.FLAG_ADVANCED; |
Steve McKay | efa1761 | 2016-01-29 18:15:39 -0800 | [diff] [blame] | 244 | } |
Steve McKay | c6a4cd8 | 2015-11-18 14:56:50 -0800 | [diff] [blame] | 245 | // Dunno when this would NOT be the case, but never hurts to be correct. |
| 246 | if (volume.isMountedWritable()) { |
| 247 | root.flags |= Root.FLAG_SUPPORTS_CREATE; |
| 248 | } |
| 249 | root.title = title; |
| 250 | if (volume.getType() == VolumeInfo.TYPE_PUBLIC) { |
| 251 | root.flags |= Root.FLAG_HAS_SETTINGS; |
| 252 | } |
| 253 | if (volume.isVisibleForRead(userId)) { |
| 254 | root.visiblePath = volume.getPathForUser(userId); |
| 255 | } else { |
| 256 | root.visiblePath = null; |
| 257 | } |
| 258 | root.path = volume.getInternalPathForUser(userId); |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 259 | try { |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 260 | root.docId = getDocIdForFile(root.path); |
Steve McKay | c6a4cd8 | 2015-11-18 14:56:50 -0800 | [diff] [blame] | 261 | } catch (FileNotFoundException e) { |
| 262 | throw new IllegalStateException(e); |
| 263 | } |
| 264 | } |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 265 | |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 266 | Log.d(TAG, "After updating volumes, found " + mRoots.size() + " active roots"); |
| 267 | |
Makoto Onuki | 14a6df7 | 2015-07-01 14:55:14 -0700 | [diff] [blame] | 268 | // Note this affects content://com.android.externalstorage.documents/root/39BD-07C5 |
| 269 | // as well as content://com.android.externalstorage.documents/document/*/children, |
| 270 | // so just notify on content://com.android.externalstorage.documents/. |
| 271 | getContext().getContentResolver().notifyChange(BASE_URI, null, false); |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 272 | } |
| 273 | |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 274 | private static String[] resolveRootProjection(String[] projection) { |
| 275 | return projection != null ? projection : DEFAULT_ROOT_PROJECTION; |
| 276 | } |
| 277 | |
Abhijeet Kaur | 553625d | 2020-05-07 13:11:55 +0100 | [diff] [blame] | 278 | @Override |
| 279 | public Cursor queryChildDocumentsForManage( |
| 280 | String parentDocId, String[] projection, String sortOrder) |
| 281 | throws FileNotFoundException { |
| 282 | return queryChildDocumentsShowAll(parentDocId, projection, sortOrder); |
| 283 | } |
| 284 | |
Ivan Chiang | 730b3a3 | 2019-08-21 16:12:54 +0800 | [diff] [blame] | 285 | /** |
| 286 | * Check that the directory is the root of storage or blocked file from tree. |
| 287 | * |
| 288 | * @param docId the docId of the directory to be checked |
| 289 | * @return true, should be blocked from tree. Otherwise, false. |
| 290 | */ |
| 291 | @Override |
| 292 | protected boolean shouldBlockFromTree(@NonNull String docId) { |
| 293 | try { |
Ivan Chiang | 698340f | 2019-11-15 19:02:46 +0800 | [diff] [blame] | 294 | final File dir = getFileForDocId(docId, false /* visible */); |
| 295 | |
| 296 | // the file is null or it is not a directory |
| 297 | if (dir == null || !dir.isDirectory()) { |
Ivan Chiang | 730b3a3 | 2019-08-21 16:12:54 +0800 | [diff] [blame] | 298 | return false; |
| 299 | } |
| 300 | |
Stephen Hughes | 4827cdf | 2020-01-15 13:03:56 -0800 | [diff] [blame] | 301 | // Allow all directories on USB, including the root. |
| 302 | try { |
| 303 | RootInfo rootInfo = getRootFromDocId(docId); |
| 304 | if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) { |
| 305 | return false; |
| 306 | } |
| 307 | } catch (FileNotFoundException e) { |
| 308 | Log.e(TAG, "Failed to determine rootInfo for docId"); |
| 309 | } |
| 310 | |
Ivan Chiang | 698340f | 2019-11-15 19:02:46 +0800 | [diff] [blame] | 311 | final String path = getPathFromDocId(docId); |
Ivan Chiang | 730b3a3 | 2019-08-21 16:12:54 +0800 | [diff] [blame] | 312 | |
Ivan Chiang | 698340f | 2019-11-15 19:02:46 +0800 | [diff] [blame] | 313 | // Block the root of the storage |
| 314 | if (path.isEmpty()) { |
Ivan Chiang | 730b3a3 | 2019-08-21 16:12:54 +0800 | [diff] [blame] | 315 | return true; |
| 316 | } |
| 317 | |
Ivan Chiang | 698340f | 2019-11-15 19:02:46 +0800 | [diff] [blame] | 318 | // Block Download folder from tree |
| 319 | if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(), |
| 320 | path.toLowerCase())) { |
| 321 | return true; |
Ivan Chiang | 730b3a3 | 2019-08-21 16:12:54 +0800 | [diff] [blame] | 322 | } |
| 323 | |
Ivan Chiang | 730b3a3 | 2019-08-21 16:12:54 +0800 | [diff] [blame] | 324 | return false; |
| 325 | } catch (IOException e) { |
| 326 | throw new IllegalArgumentException( |
| 327 | "Failed to determine if " + docId + " should block from tree " + ": " + e); |
| 328 | } |
| 329 | } |
| 330 | |
Garfield Tan | 75379db | 2017-02-08 15:32:56 -0800 | [diff] [blame] | 331 | @Override |
| 332 | protected String getDocIdForFile(File file) throws FileNotFoundException { |
Felipe Leme | b012f91 | 2016-01-22 16:49:55 -0800 | [diff] [blame] | 333 | return getDocIdForFileMaybeCreate(file, false); |
| 334 | } |
| 335 | |
| 336 | private String getDocIdForFileMaybeCreate(File file, boolean createNewDir) |
| 337 | throws FileNotFoundException { |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 338 | String path = file.getAbsolutePath(); |
Jeff Sharkey | 92d7e69 | 2013-08-02 10:33:21 -0700 | [diff] [blame] | 339 | |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 340 | // Find the most-specific root path |
Garfield Tan | dc9593e | 2017-01-09 18:04:43 -0800 | [diff] [blame] | 341 | boolean visiblePath = false; |
| 342 | RootInfo mostSpecificRoot = getMostSpecificRootForPath(path, false); |
| 343 | |
| 344 | if (mostSpecificRoot == null) { |
| 345 | // Try visible path if no internal path matches. MediaStore uses visible paths. |
| 346 | visiblePath = true; |
| 347 | mostSpecificRoot = getMostSpecificRootForPath(path, true); |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 348 | } |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 349 | |
Garfield Tan | dc9593e | 2017-01-09 18:04:43 -0800 | [diff] [blame] | 350 | if (mostSpecificRoot == null) { |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 351 | throw new FileNotFoundException("Failed to find root that contains " + path); |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 352 | } |
Jeff Sharkey | 20d96d8 | 2013-07-30 17:08:39 -0700 | [diff] [blame] | 353 | |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 354 | // Start at first char of path under root |
Garfield Tan | dc9593e | 2017-01-09 18:04:43 -0800 | [diff] [blame] | 355 | final String rootPath = visiblePath |
| 356 | ? mostSpecificRoot.visiblePath.getAbsolutePath() |
| 357 | : mostSpecificRoot.path.getAbsolutePath(); |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 358 | if (rootPath.equals(path)) { |
| 359 | path = ""; |
| 360 | } else if (rootPath.endsWith("/")) { |
| 361 | path = path.substring(rootPath.length()); |
Jeff Sharkey | 92d7e69 | 2013-08-02 10:33:21 -0700 | [diff] [blame] | 362 | } else { |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 363 | path = path.substring(rootPath.length() + 1); |
Jeff Sharkey | 92d7e69 | 2013-08-02 10:33:21 -0700 | [diff] [blame] | 364 | } |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 365 | |
Felipe Leme | b012f91 | 2016-01-22 16:49:55 -0800 | [diff] [blame] | 366 | if (!file.exists() && createNewDir) { |
| 367 | Log.i(TAG, "Creating new directory " + file); |
| 368 | if (!file.mkdir()) { |
| 369 | Log.e(TAG, "Could not create directory " + file); |
| 370 | } |
| 371 | } |
| 372 | |
Garfield Tan | dc9593e | 2017-01-09 18:04:43 -0800 | [diff] [blame] | 373 | return mostSpecificRoot.rootId + ':' + path; |
| 374 | } |
| 375 | |
| 376 | private RootInfo getMostSpecificRootForPath(String path, boolean visible) { |
| 377 | // Find the most-specific root path |
| 378 | RootInfo mostSpecificRoot = null; |
| 379 | String mostSpecificPath = null; |
| 380 | synchronized (mRootsLock) { |
| 381 | for (int i = 0; i < mRoots.size(); i++) { |
| 382 | final RootInfo root = mRoots.valueAt(i); |
| 383 | final File rootFile = visible ? root.visiblePath : root.path; |
| 384 | if (rootFile != null) { |
| 385 | final String rootPath = rootFile.getAbsolutePath(); |
| 386 | if (path.startsWith(rootPath) && (mostSpecificPath == null |
| 387 | || rootPath.length() > mostSpecificPath.length())) { |
| 388 | mostSpecificRoot = root; |
| 389 | mostSpecificPath = rootPath; |
| 390 | } |
| 391 | } |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | return mostSpecificRoot; |
Jeff Sharkey | 20d96d8 | 2013-07-30 17:08:39 -0700 | [diff] [blame] | 396 | } |
| 397 | |
Garfield Tan | 75379db | 2017-02-08 15:32:56 -0800 | [diff] [blame] | 398 | @Override |
| 399 | protected File getFileForDocId(String docId, boolean visible) throws FileNotFoundException { |
Jeff Sharkey | b00d5ea | 2018-05-01 10:01:52 -0600 | [diff] [blame] | 400 | return getFileForDocId(docId, visible, true); |
| 401 | } |
| 402 | |
| 403 | private File getFileForDocId(String docId, boolean visible, boolean mustExist) |
| 404 | throws FileNotFoundException { |
Garfield Tan | 06940e1 | 2016-10-07 16:03:17 -0700 | [diff] [blame] | 405 | RootInfo root = getRootFromDocId(docId); |
Jeff Sharkey | b00d5ea | 2018-05-01 10:01:52 -0600 | [diff] [blame] | 406 | return buildFile(root, docId, visible, mustExist); |
Garfield Tan | aba97f3 | 2016-10-06 17:34:19 +0000 | [diff] [blame] | 407 | } |
| 408 | |
| 409 | private Pair<RootInfo, File> resolveDocId(String docId, boolean visible) |
| 410 | throws FileNotFoundException { |
Garfield Tan | 06940e1 | 2016-10-07 16:03:17 -0700 | [diff] [blame] | 411 | RootInfo root = getRootFromDocId(docId); |
Jeff Sharkey | b00d5ea | 2018-05-01 10:01:52 -0600 | [diff] [blame] | 412 | return Pair.create(root, buildFile(root, docId, visible, true)); |
Garfield Tan | 06940e1 | 2016-10-07 16:03:17 -0700 | [diff] [blame] | 413 | } |
| 414 | |
Ivan Chiang | 698340f | 2019-11-15 19:02:46 +0800 | [diff] [blame] | 415 | @VisibleForTesting |
| 416 | static String getPathFromDocId(String docId) { |
| 417 | final int splitIndex = docId.indexOf(':', 1); |
| 418 | final String path = docId.substring(splitIndex + 1); |
| 419 | |
| 420 | if (path.isEmpty()) { |
| 421 | return path; |
| 422 | } |
| 423 | |
| 424 | // remove trailing "/" |
| 425 | if (path.charAt(path.length() - 1) == '/') { |
| 426 | return path.substring(0, path.length() - 1); |
| 427 | } else { |
| 428 | return path; |
| 429 | } |
| 430 | } |
| 431 | |
Garfield Tan | 06940e1 | 2016-10-07 16:03:17 -0700 | [diff] [blame] | 432 | private RootInfo getRootFromDocId(String docId) throws FileNotFoundException { |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 433 | final int splitIndex = docId.indexOf(':', 1); |
| 434 | final String tag = docId.substring(0, splitIndex); |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 435 | |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 436 | RootInfo root; |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 437 | synchronized (mRootsLock) { |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 438 | root = mRoots.get(tag); |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 439 | } |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 440 | if (root == null) { |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 441 | throw new FileNotFoundException("No root for " + tag); |
| 442 | } |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 443 | |
Garfield Tan | 06940e1 | 2016-10-07 16:03:17 -0700 | [diff] [blame] | 444 | return root; |
| 445 | } |
| 446 | |
Jeff Sharkey | b00d5ea | 2018-05-01 10:01:52 -0600 | [diff] [blame] | 447 | private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist) |
Garfield Tan | 06940e1 | 2016-10-07 16:03:17 -0700 | [diff] [blame] | 448 | throws FileNotFoundException { |
| 449 | final int splitIndex = docId.indexOf(':', 1); |
| 450 | final String path = docId.substring(splitIndex + 1); |
| 451 | |
Diksha Gohlyan | 0d1313a | 2020-04-23 08:02:13 -0700 | [diff] [blame] | 452 | File target = root.visiblePath != null ? root.visiblePath : root.path; |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 453 | if (target == null) { |
| 454 | return null; |
| 455 | } |
Jeff Sharkey | 3e1189b | 2013-09-12 21:59:06 -0700 | [diff] [blame] | 456 | if (!target.exists()) { |
| 457 | target.mkdirs(); |
| 458 | } |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 459 | target = new File(target, path); |
Jeff Sharkey | b00d5ea | 2018-05-01 10:01:52 -0600 | [diff] [blame] | 460 | if (mustExist && !target.exists()) { |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 461 | throw new FileNotFoundException("Missing file for " + docId + " at " + target); |
| 462 | } |
Garfield Tan | 06940e1 | 2016-10-07 16:03:17 -0700 | [diff] [blame] | 463 | return target; |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 464 | } |
| 465 | |
Garfield Tan | 75379db | 2017-02-08 15:32:56 -0800 | [diff] [blame] | 466 | @Override |
| 467 | protected Uri buildNotificationUri(String docId) { |
| 468 | return DocumentsContract.buildChildDocumentsUri(AUTHORITY, docId); |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 469 | } |
| 470 | |
| 471 | @Override |
Jeff Sharkey | b00d5ea | 2018-05-01 10:01:52 -0600 | [diff] [blame] | 472 | protected void onDocIdChanged(String docId) { |
| 473 | try { |
| 474 | // Touch the visible path to ensure that any sdcardfs caches have |
| 475 | // been updated to reflect underlying changes on disk. |
| 476 | final File visiblePath = getFileForDocId(docId, true, false); |
| 477 | if (visiblePath != null) { |
| 478 | Os.access(visiblePath.getAbsolutePath(), OsConstants.F_OK); |
| 479 | } |
| 480 | } catch (FileNotFoundException | ErrnoException ignored) { |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | @Override |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 485 | public Cursor queryRoots(String[] projection) throws FileNotFoundException { |
| 486 | final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 487 | synchronized (mRootsLock) { |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 488 | for (RootInfo root : mRoots.values()) { |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 489 | final RowBuilder row = result.newRow(); |
| 490 | row.add(Root.COLUMN_ROOT_ID, root.rootId); |
| 491 | row.add(Root.COLUMN_FLAGS, root.flags); |
| 492 | row.add(Root.COLUMN_TITLE, root.title); |
| 493 | row.add(Root.COLUMN_DOCUMENT_ID, root.docId); |
Ivan Chiang | c26d3c2 | 2019-01-10 19:29:01 +0800 | [diff] [blame] | 494 | row.add(Root.COLUMN_QUERY_ARGS, SUPPORTED_QUERY_ARGS); |
Jeff Sharkey | 06823d4 | 2017-05-09 16:55:29 -0600 | [diff] [blame] | 495 | |
| 496 | long availableBytes = -1; |
| 497 | if (root.reportAvailableBytes) { |
| 498 | if (root.storageUuid != null) { |
| 499 | try { |
| 500 | availableBytes = getContext() |
| 501 | .getSystemService(StorageStatsManager.class) |
| 502 | .getFreeBytes(root.storageUuid); |
| 503 | } catch (IOException e) { |
| 504 | Log.w(TAG, e); |
| 505 | } |
| 506 | } else { |
| 507 | availableBytes = root.path.getUsableSpace(); |
| 508 | } |
| 509 | } |
| 510 | row.add(Root.COLUMN_AVAILABLE_BYTES, availableBytes); |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 511 | } |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 512 | } |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 513 | return result; |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 514 | } |
| 515 | |
| 516 | @Override |
Garfield Tan | b690b4d | 2017-03-01 16:05:23 -0800 | [diff] [blame] | 517 | public Path findDocumentPath(@Nullable String parentDocId, String childDocId) |
Garfield Tan | aba97f3 | 2016-10-06 17:34:19 +0000 | [diff] [blame] | 518 | throws FileNotFoundException { |
Garfield Tan | 06940e1 | 2016-10-07 16:03:17 -0700 | [diff] [blame] | 519 | final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false); |
| 520 | final RootInfo root = resolvedDocId.first; |
| 521 | File child = resolvedDocId.second; |
Garfield Tan | aba97f3 | 2016-10-06 17:34:19 +0000 | [diff] [blame] | 522 | |
Garfield Tan | 06940e1 | 2016-10-07 16:03:17 -0700 | [diff] [blame] | 523 | final File parent = TextUtils.isEmpty(parentDocId) |
| 524 | ? root.path |
| 525 | : getFileForDocId(parentDocId); |
| 526 | |
Garfield Tan | 75379db | 2017-02-08 15:32:56 -0800 | [diff] [blame] | 527 | return new Path(parentDocId == null ? root.rootId : null, findDocumentPath(parent, child)); |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 528 | } |
| 529 | |
Garfield Tan | 92b96ba | 2016-11-01 14:33:48 -0700 | [diff] [blame] | 530 | private Uri getDocumentUri(String path, List<UriPermission> accessUriPermissions) |
| 531 | throws FileNotFoundException { |
| 532 | File doc = new File(path); |
| 533 | |
| 534 | final String docId = getDocIdForFile(doc); |
| 535 | |
| 536 | UriPermission docUriPermission = null; |
| 537 | UriPermission treeUriPermission = null; |
| 538 | for (UriPermission uriPermission : accessUriPermissions) { |
| 539 | final Uri uri = uriPermission.getUri(); |
| 540 | if (AUTHORITY.equals(uri.getAuthority())) { |
| 541 | boolean matchesRequestedDoc = false; |
| 542 | if (DocumentsContract.isTreeUri(uri)) { |
| 543 | final String parentDocId = DocumentsContract.getTreeDocumentId(uri); |
Garfield Tan | dc9593e | 2017-01-09 18:04:43 -0800 | [diff] [blame] | 544 | if (isChildDocument(parentDocId, docId)) { |
Garfield Tan | 92b96ba | 2016-11-01 14:33:48 -0700 | [diff] [blame] | 545 | treeUriPermission = uriPermission; |
| 546 | matchesRequestedDoc = true; |
| 547 | } |
| 548 | } else { |
| 549 | final String candidateDocId = DocumentsContract.getDocumentId(uri); |
Garfield Tan | dc9593e | 2017-01-09 18:04:43 -0800 | [diff] [blame] | 550 | if (Objects.equals(docId, candidateDocId)) { |
Garfield Tan | 92b96ba | 2016-11-01 14:33:48 -0700 | [diff] [blame] | 551 | docUriPermission = uriPermission; |
| 552 | matchesRequestedDoc = true; |
| 553 | } |
| 554 | } |
| 555 | |
| 556 | if (matchesRequestedDoc && allowsBothReadAndWrite(uriPermission)) { |
| 557 | // This URI permission provides everything an app can get, no need to |
| 558 | // further check any other granted URI. |
| 559 | break; |
| 560 | } |
| 561 | } |
| 562 | } |
| 563 | |
| 564 | // Full permission URI first. |
| 565 | if (allowsBothReadAndWrite(treeUriPermission)) { |
| 566 | return DocumentsContract.buildDocumentUriUsingTree(treeUriPermission.getUri(), docId); |
| 567 | } |
| 568 | |
| 569 | if (allowsBothReadAndWrite(docUriPermission)) { |
| 570 | return docUriPermission.getUri(); |
| 571 | } |
| 572 | |
| 573 | // Then partial permission URI. |
| 574 | if (treeUriPermission != null) { |
| 575 | return DocumentsContract.buildDocumentUriUsingTree(treeUriPermission.getUri(), docId); |
| 576 | } |
| 577 | |
| 578 | if (docUriPermission != null) { |
| 579 | return docUriPermission.getUri(); |
| 580 | } |
| 581 | |
| 582 | throw new SecurityException("The app is not given any access to the document under path " + |
| 583 | path + " with permissions granted in " + accessUriPermissions); |
| 584 | } |
| 585 | |
| 586 | private static boolean allowsBothReadAndWrite(UriPermission permission) { |
| 587 | return permission != null |
| 588 | && permission.isReadPermission() |
| 589 | && permission.isWritePermission(); |
| 590 | } |
| 591 | |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 592 | @Override |
Ivan Chiang | a972d04 | 2018-10-15 15:23:02 +0800 | [diff] [blame] | 593 | public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs) |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 594 | throws FileNotFoundException { |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 595 | final File parent; |
Diksha Gohlyan | f555b7b | 2020-04-29 10:06:03 -0700 | [diff] [blame] | 596 | |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 597 | synchronized (mRootsLock) { |
Diksha Gohlyan | f555b7b | 2020-04-29 10:06:03 -0700 | [diff] [blame] | 598 | RootInfo root = mRoots.get(rootId); |
| 599 | parent = root.visiblePath != null ? root.visiblePath |
| 600 | : root.path; |
Jeff Sharkey | 1f706c6 | 2013-10-17 10:52:17 -0700 | [diff] [blame] | 601 | } |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 602 | |
Ivan Chiang | a972d04 | 2018-10-15 15:23:02 +0800 | [diff] [blame] | 603 | return querySearchDocuments(parent, projection, Collections.emptySet(), queryArgs); |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 604 | } |
| 605 | |
| 606 | @Override |
Garfield Tan | 8787703 | 2017-03-22 12:01:14 -0700 | [diff] [blame] | 607 | public void ejectRoot(String rootId) { |
Ben Lin | e7822fb | 2016-06-24 15:21:08 -0700 | [diff] [blame] | 608 | final long token = Binder.clearCallingIdentity(); |
Ben Lin | e7822fb | 2016-06-24 15:21:08 -0700 | [diff] [blame] | 609 | RootInfo root = mRoots.get(rootId); |
| 610 | if (root != null) { |
| 611 | try { |
| 612 | mStorageManager.unmount(root.volumeId); |
Ben Lin | e7822fb | 2016-06-24 15:21:08 -0700 | [diff] [blame] | 613 | } catch (RuntimeException e) { |
Garfield Tan | 8787703 | 2017-03-22 12:01:14 -0700 | [diff] [blame] | 614 | throw new IllegalStateException(e); |
Ben Lin | e7822fb | 2016-06-24 15:21:08 -0700 | [diff] [blame] | 615 | } finally { |
| 616 | Binder.restoreCallingIdentity(token); |
| 617 | } |
| 618 | } |
Ben Lin | e7822fb | 2016-06-24 15:21:08 -0700 | [diff] [blame] | 619 | } |
| 620 | |
| 621 | @Override |
Jeff Sharkey | 27de30d | 2015-04-18 16:20:27 -0700 | [diff] [blame] | 622 | public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { |
| 623 | final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); |
| 624 | synchronized (mRootsLock) { |
| 625 | for (int i = 0; i < mRoots.size(); i++) { |
| 626 | final RootInfo root = mRoots.valueAt(i); |
| 627 | pw.println("Root{" + root.rootId + "}:"); |
| 628 | pw.increaseIndent(); |
| 629 | pw.printPair("flags", DebugUtils.flagsToString(Root.class, "FLAG_", root.flags)); |
| 630 | pw.println(); |
| 631 | pw.printPair("title", root.title); |
| 632 | pw.printPair("docId", root.docId); |
| 633 | pw.println(); |
| 634 | pw.printPair("path", root.path); |
| 635 | pw.printPair("visiblePath", root.visiblePath); |
| 636 | pw.decreaseIndent(); |
| 637 | pw.println(); |
| 638 | } |
| 639 | } |
| 640 | } |
| 641 | |
Felipe Leme | b012f91 | 2016-01-22 16:49:55 -0800 | [diff] [blame] | 642 | @Override |
| 643 | public Bundle call(String method, String arg, Bundle extras) { |
| 644 | Bundle bundle = super.call(method, arg, extras); |
| 645 | if (bundle == null && !TextUtils.isEmpty(method)) { |
| 646 | switch (method) { |
| 647 | case "getDocIdForFileCreateNewDir": { |
| 648 | getContext().enforceCallingPermission( |
| 649 | android.Manifest.permission.MANAGE_DOCUMENTS, null); |
| 650 | if (TextUtils.isEmpty(arg)) { |
| 651 | return null; |
| 652 | } |
| 653 | try { |
| 654 | final String docId = getDocIdForFileMaybeCreate(new File(arg), true); |
| 655 | bundle = new Bundle(); |
| 656 | bundle.putString("DOC_ID", docId); |
| 657 | } catch (FileNotFoundException e) { |
| 658 | Log.w(TAG, "file '" + arg + "' not found"); |
| 659 | return null; |
| 660 | } |
| 661 | break; |
| 662 | } |
Jeff Sharkey | 4018283 | 2019-12-18 14:06:48 -0700 | [diff] [blame] | 663 | case GET_DOCUMENT_URI_CALL: { |
Jeff Sharkey | 643e99e | 2018-10-22 18:01:27 -0600 | [diff] [blame] | 664 | // All callers must go through MediaProvider |
| 665 | getContext().enforceCallingPermission( |
| 666 | android.Manifest.permission.WRITE_MEDIA_STORAGE, TAG); |
Garfield Tan | 92b96ba | 2016-11-01 14:33:48 -0700 | [diff] [blame] | 667 | |
Jeff Sharkey | 643e99e | 2018-10-22 18:01:27 -0600 | [diff] [blame] | 668 | final Uri fileUri = extras.getParcelable(DocumentsContract.EXTRA_URI); |
| 669 | final List<UriPermission> accessUriPermissions = extras |
| 670 | .getParcelableArrayList(DocumentsContract.EXTRA_URI_PERMISSIONS); |
| 671 | |
| 672 | final String path = fileUri.getPath(); |
Garfield Tan | 92b96ba | 2016-11-01 14:33:48 -0700 | [diff] [blame] | 673 | try { |
| 674 | final Bundle out = new Bundle(); |
| 675 | final Uri uri = getDocumentUri(path, accessUriPermissions); |
| 676 | out.putParcelable(DocumentsContract.EXTRA_URI, uri); |
| 677 | return out; |
| 678 | } catch (FileNotFoundException e) { |
| 679 | throw new IllegalStateException("File in " + path + " is not found.", e); |
| 680 | } |
Jeff Sharkey | 643e99e | 2018-10-22 18:01:27 -0600 | [diff] [blame] | 681 | } |
Jeff Sharkey | 4018283 | 2019-12-18 14:06:48 -0700 | [diff] [blame] | 682 | case GET_MEDIA_URI_CALL: { |
Jeff Sharkey | 643e99e | 2018-10-22 18:01:27 -0600 | [diff] [blame] | 683 | // All callers must go through MediaProvider |
| 684 | getContext().enforceCallingPermission( |
| 685 | android.Manifest.permission.WRITE_MEDIA_STORAGE, TAG); |
Garfield Tan | 92b96ba | 2016-11-01 14:33:48 -0700 | [diff] [blame] | 686 | |
Jeff Sharkey | 643e99e | 2018-10-22 18:01:27 -0600 | [diff] [blame] | 687 | final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI); |
| 688 | final String docId = DocumentsContract.getDocumentId(documentUri); |
| 689 | try { |
| 690 | final Bundle out = new Bundle(); |
Jeff Sharkey | 601c3c6 | 2019-05-29 12:55:05 -0600 | [diff] [blame] | 691 | final Uri uri = Uri.fromFile(getFileForDocId(docId, true)); |
Jeff Sharkey | 643e99e | 2018-10-22 18:01:27 -0600 | [diff] [blame] | 692 | out.putParcelable(DocumentsContract.EXTRA_URI, uri); |
| 693 | return out; |
| 694 | } catch (FileNotFoundException e) { |
| 695 | throw new IllegalStateException(e); |
| 696 | } |
Garfield Tan | 92b96ba | 2016-11-01 14:33:48 -0700 | [diff] [blame] | 697 | } |
Felipe Leme | b012f91 | 2016-01-22 16:49:55 -0800 | [diff] [blame] | 698 | default: |
| 699 | Log.w(TAG, "unknown method passed to call(): " + method); |
| 700 | } |
| 701 | } |
| 702 | return bundle; |
| 703 | } |
Jeff Sharkey | 9e0036e | 2013-04-26 16:54:55 -0700 | [diff] [blame] | 704 | } |