Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -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.documentsui; |
| 18 | |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 19 | import static com.android.documentsui.Shared.DEBUG; |
Steve McKay | 7a3b88c | 2015-09-23 17:21:40 -0700 | [diff] [blame] | 20 | import static com.android.documentsui.Shared.TAG; |
Steve McKay | 008e948 | 2016-02-18 15:32:16 -0800 | [diff] [blame] | 21 | import static com.android.internal.util.Preconditions.checkState; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 22 | |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 23 | import android.content.ContentProviderClient; |
| 24 | import android.content.ContentResolver; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 25 | import android.content.Context; |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 26 | import android.content.Intent; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 27 | import android.content.pm.ApplicationInfo; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 28 | import android.content.pm.PackageManager; |
| 29 | import android.content.pm.ProviderInfo; |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 30 | import android.content.pm.ResolveInfo; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 31 | import android.database.ContentObserver; |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 32 | import android.database.Cursor; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 33 | import android.net.Uri; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 34 | import android.os.AsyncTask; |
| 35 | import android.os.Handler; |
| 36 | import android.os.SystemClock; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 37 | import android.provider.DocumentsContract; |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 38 | import android.provider.DocumentsContract.Root; |
Steve McKay | 7a3b88c | 2015-09-23 17:21:40 -0700 | [diff] [blame] | 39 | import android.support.annotation.VisibleForTesting; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 40 | import android.util.Log; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 41 | |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 42 | import com.android.documentsui.model.RootInfo; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 43 | import com.android.internal.annotations.GuardedBy; |
Steve McKay | 008e948 | 2016-02-18 15:32:16 -0800 | [diff] [blame] | 44 | import com.android.internal.util.Preconditions; |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 45 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 46 | import com.google.common.collect.ArrayListMultimap; |
| 47 | import com.google.common.collect.Multimap; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 48 | |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 49 | import libcore.io.IoUtils; |
| 50 | |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 51 | import java.util.ArrayList; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 52 | import java.util.Collection; |
Tomasz Mikolajewski | a6120da | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 53 | import java.util.Collections; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 54 | import java.util.HashSet; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 55 | import java.util.List; |
Kenny Root | e6585b3 | 2013-12-13 12:00:26 -0800 | [diff] [blame] | 56 | import java.util.Objects; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 57 | import java.util.concurrent.CountDownLatch; |
| 58 | import java.util.concurrent.TimeUnit; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 59 | |
| 60 | /** |
| 61 | * Cache of known storage backends and their roots. |
| 62 | */ |
| 63 | public class RootsCache { |
Jeff Sharkey | 46de7b5 | 2013-10-23 09:59:06 -0700 | [diff] [blame] | 64 | public static final Uri sNotificationUri = Uri.parse( |
| 65 | "content://com.android.documentsui.roots/"); |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 66 | |
Jeff Sharkey | 4eb407a | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 67 | private final Context mContext; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 68 | private final ContentObserver mObserver; |
Daichi Hirono | 60e9a07 | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 69 | private OnCacheUpdateListener mCacheUpdateListener; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 70 | |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 71 | private final RootInfo mRecentsRoot; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 72 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 73 | private final Object mLock = new Object(); |
| 74 | private final CountDownLatch mFirstLoad = new CountDownLatch(1); |
| 75 | |
| 76 | @GuardedBy("mLock") |
| 77 | private Multimap<String, RootInfo> mRoots = ArrayListMultimap.create(); |
| 78 | @GuardedBy("mLock") |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 79 | private HashSet<String> mStoppedAuthorities = new HashSet<>(); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 80 | |
| 81 | @GuardedBy("mObservedAuthorities") |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 82 | private final HashSet<String> mObservedAuthorities = new HashSet<>(); |
Jeff Sharkey | 4eb407a | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 83 | |
| 84 | public RootsCache(Context context) { |
| 85 | mContext = context; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 86 | mObserver = new RootsChangedObserver(); |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 87 | |
| 88 | // Create a new anonymous "Recents" RootInfo. It's a faker. |
| 89 | mRecentsRoot = new RootInfo() {{ |
Steve McKay | 008e948 | 2016-02-18 15:32:16 -0800 | [diff] [blame] | 90 | // Special root for recents |
| 91 | derivedIcon = R.drawable.ic_root_recent; |
| 92 | derivedType = RootInfo.TYPE_RECENTS; |
| 93 | flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD; |
| 94 | title = mContext.getString(R.string.root_recent); |
| 95 | availableBytes = -1; |
| 96 | }}; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 97 | } |
| 98 | |
| 99 | private class RootsChangedObserver extends ContentObserver { |
| 100 | public RootsChangedObserver() { |
| 101 | super(new Handler()); |
| 102 | } |
| 103 | |
| 104 | @Override |
| 105 | public void onChange(boolean selfChange, Uri uri) { |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 106 | if (DEBUG) Log.d(TAG, "Updating roots due to change at " + uri); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 107 | updateAuthorityAsync(uri.getAuthority()); |
| 108 | } |
Jeff Sharkey | 4eb407a | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 109 | } |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 110 | |
Daichi Hirono | 60e9a07 | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 111 | static interface OnCacheUpdateListener { |
| 112 | void onCacheUpdate(); |
| 113 | } |
| 114 | |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 115 | /** |
| 116 | * Gather roots from all known storage providers. |
| 117 | */ |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 118 | public void updateAsync() { |
Steve McKay | 008e948 | 2016-02-18 15:32:16 -0800 | [diff] [blame] | 119 | // Verifying an assumption about the recents root being immutable. |
| 120 | if (DEBUG) { |
| 121 | checkState(mRecentsRoot.authority == null); |
| 122 | checkState(mRecentsRoot.rootId == null); |
| 123 | checkState(mRecentsRoot.derivedIcon == R.drawable.ic_root_recent); |
| 124 | checkState(mRecentsRoot.derivedType == RootInfo.TYPE_RECENTS); |
| 125 | checkState(mRecentsRoot.flags == (Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD)); |
| 126 | checkState(mRecentsRoot.title == mContext.getString(R.string.root_recent)); |
| 127 | checkState(mRecentsRoot.availableBytes == -1); |
| 128 | } |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 129 | new UpdateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| 130 | } |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 131 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 132 | /** |
| 133 | * Gather roots from storage providers belonging to given package name. |
| 134 | */ |
| 135 | public void updatePackageAsync(String packageName) { |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 136 | new UpdateTask(packageName).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| 137 | } |
| 138 | |
| 139 | /** |
| 140 | * Gather roots from storage providers belonging to given authority. |
| 141 | */ |
| 142 | public void updateAuthorityAsync(String authority) { |
| 143 | final ProviderInfo info = mContext.getPackageManager().resolveContentProvider(authority, 0); |
| 144 | if (info != null) { |
| 145 | updatePackageAsync(info.packageName); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | private void waitForFirstLoad() { |
| 150 | boolean success = false; |
| 151 | try { |
| 152 | success = mFirstLoad.await(15, TimeUnit.SECONDS); |
| 153 | } catch (InterruptedException e) { |
| 154 | } |
| 155 | if (!success) { |
| 156 | Log.w(TAG, "Timeout waiting for first update"); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | /** |
| 161 | * Load roots from authorities that are in stopped state. Normal |
| 162 | * {@link UpdateTask} passes ignore stopped applications. |
| 163 | */ |
| 164 | private void loadStoppedAuthorities() { |
| 165 | final ContentResolver resolver = mContext.getContentResolver(); |
| 166 | synchronized (mLock) { |
| 167 | for (String authority : mStoppedAuthorities) { |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 168 | if (DEBUG) Log.d(TAG, "Loading stopped authority " + authority); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 169 | mRoots.putAll(authority, loadRootsForAuthority(resolver, authority)); |
| 170 | } |
| 171 | mStoppedAuthorities.clear(); |
| 172 | } |
| 173 | } |
| 174 | |
Tomasz Mikolajewski | a6120da | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 175 | /** |
| 176 | * Load roots from a stopped authority. Normal {@link UpdateTask} passes |
| 177 | * ignore stopped applications. |
| 178 | */ |
| 179 | private void loadStoppedAuthority(String authority) { |
| 180 | final ContentResolver resolver = mContext.getContentResolver(); |
| 181 | synchronized (mLock) { |
Tomasz Mikolajewski | df676dc | 2016-02-03 15:18:22 +0900 | [diff] [blame] | 182 | if (!mStoppedAuthorities.contains(authority)) { |
| 183 | return; |
| 184 | } |
Tomasz Mikolajewski | a6120da | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 185 | if (DEBUG) { |
| 186 | Log.d(TAG, "Loading stopped authority " + authority); |
| 187 | } |
| 188 | mRoots.putAll(authority, loadRootsForAuthority(resolver, authority)); |
| 189 | mStoppedAuthorities.remove(authority); |
| 190 | } |
| 191 | } |
| 192 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 193 | private class UpdateTask extends AsyncTask<Void, Void, Void> { |
| 194 | private final String mFilterPackage; |
| 195 | |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 196 | private final Multimap<String, RootInfo> mTaskRoots = ArrayListMultimap.create(); |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 197 | private final HashSet<String> mTaskStoppedAuthorities = new HashSet<>(); |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 198 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 199 | /** |
| 200 | * Update all roots. |
| 201 | */ |
| 202 | public UpdateTask() { |
| 203 | this(null); |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 204 | } |
| 205 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 206 | /** |
| 207 | * Only update roots belonging to given package name. Other roots will |
| 208 | * be copied from cached {@link #mRoots} values. |
| 209 | */ |
| 210 | public UpdateTask(String filterPackage) { |
| 211 | mFilterPackage = filterPackage; |
| 212 | } |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 213 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 214 | @Override |
| 215 | protected Void doInBackground(Void... params) { |
| 216 | final long start = SystemClock.elapsedRealtime(); |
| 217 | |
Jeff Sharkey | c9d7150 | 2014-09-10 11:23:15 -0700 | [diff] [blame] | 218 | if (mFilterPackage != null) { |
| 219 | // Need at least first load, since we're going to be using |
| 220 | // previously cached values for non-matching packages. |
| 221 | waitForFirstLoad(); |
| 222 | } |
| 223 | |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 224 | mTaskRoots.put(mRecentsRoot.authority, mRecentsRoot); |
Jeff Sharkey | 5545f56 | 2013-09-21 13:57:33 -0700 | [diff] [blame] | 225 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 226 | final ContentResolver resolver = mContext.getContentResolver(); |
| 227 | final PackageManager pm = mContext.getPackageManager(); |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 228 | |
| 229 | // Pick up provider with action string |
| 230 | final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); |
| 231 | final List<ResolveInfo> providers = pm.queryIntentContentProviders(intent, 0); |
| 232 | for (ResolveInfo info : providers) { |
| 233 | handleDocumentsProvider(info.providerInfo); |
| 234 | } |
| 235 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 236 | final long delta = SystemClock.elapsedRealtime() - start; |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 237 | if (DEBUG) |
| 238 | Log.d(TAG, "Update found " + mTaskRoots.size() + " roots in " + delta + "ms"); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 239 | synchronized (mLock) { |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 240 | mRoots = mTaskRoots; |
| 241 | mStoppedAuthorities = mTaskStoppedAuthorities; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 242 | } |
| 243 | mFirstLoad.countDown(); |
Jeff Sharkey | 46de7b5 | 2013-10-23 09:59:06 -0700 | [diff] [blame] | 244 | resolver.notifyChange(sNotificationUri, null, false); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 245 | return null; |
| 246 | } |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 247 | |
Daichi Hirono | 60e9a07 | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 248 | @Override |
| 249 | protected void onPostExecute(Void result) { |
| 250 | if (mCacheUpdateListener != null) { |
| 251 | mCacheUpdateListener.onCacheUpdate(); |
| 252 | } |
| 253 | } |
| 254 | |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 255 | private void handleDocumentsProvider(ProviderInfo info) { |
| 256 | // Ignore stopped packages for now; we might query them |
| 257 | // later during UI interaction. |
| 258 | if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) { |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 259 | if (DEBUG) Log.d(TAG, "Ignoring stopped authority " + info.authority); |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 260 | mTaskStoppedAuthorities.add(info.authority); |
| 261 | return; |
| 262 | } |
| 263 | |
| 264 | // Try using cached roots if filtering |
| 265 | boolean cacheHit = false; |
| 266 | if (mFilterPackage != null && !mFilterPackage.equals(info.packageName)) { |
| 267 | synchronized (mLock) { |
| 268 | if (mTaskRoots.putAll(info.authority, mRoots.get(info.authority))) { |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 269 | if (DEBUG) Log.d(TAG, "Used cached roots for " + info.authority); |
Jeff Sharkey | 85f5f81 | 2013-10-07 10:16:12 -0700 | [diff] [blame] | 270 | cacheHit = true; |
| 271 | } |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | // Cache miss, or loading everything |
| 276 | if (!cacheHit) { |
| 277 | mTaskRoots.putAll(info.authority, |
| 278 | loadRootsForAuthority(mContext.getContentResolver(), info.authority)); |
| 279 | } |
| 280 | } |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 281 | } |
| 282 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 283 | /** |
| 284 | * Bring up requested provider and query for all active roots. |
| 285 | */ |
| 286 | private Collection<RootInfo> loadRootsForAuthority(ContentResolver resolver, String authority) { |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 287 | if (DEBUG) Log.d(TAG, "Loading roots for " + authority); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 288 | |
| 289 | synchronized (mObservedAuthorities) { |
| 290 | if (mObservedAuthorities.add(authority)) { |
| 291 | // Watch for any future updates |
| 292 | final Uri rootsUri = DocumentsContract.buildRootsUri(authority); |
| 293 | mContext.getContentResolver().registerContentObserver(rootsUri, true, mObserver); |
| 294 | } |
| 295 | } |
| 296 | |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 297 | final List<RootInfo> roots = new ArrayList<>(); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 298 | final Uri rootsUri = DocumentsContract.buildRootsUri(authority); |
Jeff Sharkey | 7aa7601 | 2013-09-30 14:26:27 -0700 | [diff] [blame] | 299 | |
| 300 | ContentProviderClient client = null; |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 301 | Cursor cursor = null; |
| 302 | try { |
Jeff Sharkey | 7aa7601 | 2013-09-30 14:26:27 -0700 | [diff] [blame] | 303 | client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 304 | cursor = client.query(rootsUri, null, null, null, null); |
| 305 | while (cursor.moveToNext()) { |
| 306 | final RootInfo root = RootInfo.fromRootsCursor(authority, cursor); |
| 307 | roots.add(root); |
| 308 | } |
| 309 | } catch (Exception e) { |
| 310 | Log.w(TAG, "Failed to load some roots from " + authority + ": " + e); |
| 311 | } finally { |
| 312 | IoUtils.closeQuietly(cursor); |
Jeff Sharkey | 7aa7601 | 2013-09-30 14:26:27 -0700 | [diff] [blame] | 313 | ContentProviderClient.releaseQuietly(client); |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 314 | } |
| 315 | return roots; |
| 316 | } |
| 317 | |
| 318 | /** |
| 319 | * Return the requested {@link RootInfo}, but only loading the roots for the |
| 320 | * requested authority. This is useful when we want to load fast without |
| 321 | * waiting for all the other roots to come back. |
| 322 | */ |
| 323 | public RootInfo getRootOneshot(String authority, String rootId) { |
| 324 | synchronized (mLock) { |
| 325 | RootInfo root = getRootLocked(authority, rootId); |
| 326 | if (root == null) { |
| 327 | mRoots.putAll( |
| 328 | authority, loadRootsForAuthority(mContext.getContentResolver(), authority)); |
| 329 | root = getRootLocked(authority, rootId); |
| 330 | } |
| 331 | return root; |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | public RootInfo getRootBlocking(String authority, String rootId) { |
| 336 | waitForFirstLoad(); |
| 337 | loadStoppedAuthorities(); |
| 338 | synchronized (mLock) { |
| 339 | return getRootLocked(authority, rootId); |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | private RootInfo getRootLocked(String authority, String rootId) { |
| 344 | for (RootInfo root : mRoots.get(authority)) { |
Kenny Root | e6585b3 | 2013-12-13 12:00:26 -0800 | [diff] [blame] | 345 | if (Objects.equals(root.rootId, rootId)) { |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 346 | return root; |
| 347 | } |
| 348 | } |
| 349 | return null; |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 350 | } |
| 351 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 352 | public boolean isIconUniqueBlocking(RootInfo root) { |
| 353 | waitForFirstLoad(); |
| 354 | loadStoppedAuthorities(); |
| 355 | synchronized (mLock) { |
| 356 | final int rootIcon = root.derivedIcon != 0 ? root.derivedIcon : root.icon; |
| 357 | for (RootInfo test : mRoots.get(root.authority)) { |
Kenny Root | e6585b3 | 2013-12-13 12:00:26 -0800 | [diff] [blame] | 358 | if (Objects.equals(test.rootId, root.rootId)) { |
Jeff Sharkey | 4ec9739 | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 359 | continue; |
| 360 | } |
Jeff Sharkey | f6db154 | 2013-09-13 13:42:19 -0700 | [diff] [blame] | 361 | final int testIcon = test.derivedIcon != 0 ? test.derivedIcon : test.icon; |
| 362 | if (testIcon == rootIcon) { |
Jeff Sharkey | 4ec9739 | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 363 | return false; |
| 364 | } |
| 365 | } |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 366 | return true; |
Jeff Sharkey | 4ec9739 | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 367 | } |
Jeff Sharkey | 4ec9739 | 2013-09-10 12:04:26 -0700 | [diff] [blame] | 368 | } |
| 369 | |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 370 | public RootInfo getRecentsRoot() { |
Jeff Sharkey | 4eb407a | 2013-08-18 17:38:20 -0700 | [diff] [blame] | 371 | return mRecentsRoot; |
Jeff Sharkey | b156f4b | 2013-08-06 16:26:14 -0700 | [diff] [blame] | 372 | } |
| 373 | |
Jeff Sharkey | ae9b51b | 2013-08-31 15:02:20 -0700 | [diff] [blame] | 374 | public boolean isRecentsRoot(RootInfo root) { |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 375 | return mRecentsRoot.equals(root); |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 376 | } |
| 377 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 378 | public Collection<RootInfo> getRootsBlocking() { |
| 379 | waitForFirstLoad(); |
| 380 | loadStoppedAuthorities(); |
| 381 | synchronized (mLock) { |
| 382 | return mRoots.values(); |
| 383 | } |
Jeff Sharkey | aeb16e2 | 2013-08-27 18:26:48 -0700 | [diff] [blame] | 384 | } |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 385 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 386 | public Collection<RootInfo> getMatchingRootsBlocking(State state) { |
| 387 | waitForFirstLoad(); |
| 388 | loadStoppedAuthorities(); |
| 389 | synchronized (mLock) { |
| 390 | return getMatchingRoots(mRoots.values(), state); |
| 391 | } |
Jeff Sharkey | 6d97d3c | 2013-09-06 10:43:45 -0700 | [diff] [blame] | 392 | } |
| 393 | |
Tomasz Mikolajewski | a6120da | 2016-01-27 17:36:51 +0900 | [diff] [blame] | 394 | /** |
| 395 | * Returns a list of roots for the specified authority. If not found, then |
| 396 | * an empty list is returned. |
| 397 | */ |
| 398 | public Collection<RootInfo> getRootsForAuthorityBlocking(String authority) { |
| 399 | waitForFirstLoad(); |
| 400 | loadStoppedAuthority(authority); |
| 401 | synchronized (mLock) { |
| 402 | final Collection<RootInfo> roots = mRoots.get(authority); |
| 403 | return roots != null ? roots : Collections.<RootInfo>emptyList(); |
| 404 | } |
| 405 | } |
| 406 | |
Daichi Hirono | 60e9a07 | 2015-12-25 11:08:42 +0900 | [diff] [blame] | 407 | public void setOnCacheUpdateListener(OnCacheUpdateListener cacheUpdateListener) { |
| 408 | mCacheUpdateListener = cacheUpdateListener; |
| 409 | } |
| 410 | |
Jeff Sharkey | a9ce049 | 2013-09-19 15:25:56 -0700 | [diff] [blame] | 411 | @VisibleForTesting |
| 412 | static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) { |
Steve McKay | 58efce3 | 2015-08-20 16:19:38 +0000 | [diff] [blame] | 413 | final List<RootInfo> matching = new ArrayList<>(); |
Jeff Sharkey | 6d97d3c | 2013-09-06 10:43:45 -0700 | [diff] [blame] | 414 | for (RootInfo root : roots) { |
Jeff Sharkey | 348ad68 | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 415 | // Exclude read-only devices when creating |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 416 | if (state.action == State.ACTION_CREATE && !root.supportsCreate()) continue; |
| 417 | if (state.action == State.ACTION_PICK_COPY_DESTINATION |
| 418 | && !root.supportsCreate()) continue; |
Jeff Sharkey | 21de56a | 2014-04-05 19:05:24 -0700 | [diff] [blame] | 419 | // Exclude roots that don't support directory picking |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 420 | if (state.action == State.ACTION_OPEN_TREE && !root.supportsChildren()) continue; |
Jeff Sharkey | 348ad68 | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 421 | // Exclude advanced devices when not requested |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 422 | if (!state.showAdvanced && root.isAdvanced()) continue; |
Jeff Sharkey | 348ad68 | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 423 | // Exclude non-local devices when local only |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 424 | if (state.localOnly && !root.isLocalOnly()) continue; |
Steve McKay | 008e948 | 2016-02-18 15:32:16 -0800 | [diff] [blame] | 425 | // Exclude downloads roots as it doesn't support directory creation (actually |
| 426 | // we just don't show them). |
| 427 | // TODO: Add flag to check the root supports directory creation. |
| 428 | if (state.directoryCopy && !root.isDownloads()) continue; |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 429 | |
| 430 | // Only show empty roots when creating, or in browse mode. |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 431 | if (root.isEmpty() && (state.action == State.ACTION_OPEN |
Steve McKay | 0200e34 | 2015-09-23 16:16:32 -0700 | [diff] [blame] | 432 | || state.action == State.ACTION_GET_CONTENT)) { |
Steve McKay | 83df8c0 | 2015-09-16 15:07:31 -0700 | [diff] [blame] | 433 | if (DEBUG) Log.i(TAG, "Skipping empty root: " + root); |
| 434 | continue; |
| 435 | } |
Jeff Sharkey | 348ad68 | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 436 | |
Jeff Sharkey | 923396b | 2013-09-05 13:55:35 -0700 | [diff] [blame] | 437 | // Only include roots that serve requested content |
Jeff Sharkey | d182bb6 | 2013-09-07 14:45:03 -0700 | [diff] [blame] | 438 | final boolean overlap = |
| 439 | MimePredicate.mimeMatches(root.derivedMimeTypes, state.acceptMimes) || |
| 440 | MimePredicate.mimeMatches(state.acceptMimes, root.derivedMimeTypes); |
Jeff Sharkey | 6d97d3c | 2013-09-06 10:43:45 -0700 | [diff] [blame] | 441 | if (!overlap) { |
Jeff Sharkey | 923396b | 2013-09-05 13:55:35 -0700 | [diff] [blame] | 442 | continue; |
Jeff Sharkey | 348ad68 | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 443 | } |
| 444 | |
Ben Kwa | 7779740 | 2015-05-29 15:40:31 -0700 | [diff] [blame] | 445 | // Exclude roots from the calling package. |
| 446 | if (state.excludedAuthorities.contains(root.authority)) { |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 447 | if (DEBUG) Log.d( |
| 448 | TAG, "Excluding root " + root.authority + " from calling package."); |
Ben Kwa | 7779740 | 2015-05-29 15:40:31 -0700 | [diff] [blame] | 449 | continue; |
| 450 | } |
| 451 | |
Steve McKay | 4a1ca86 | 2016-02-17 18:25:47 -0800 | [diff] [blame] | 452 | if (DEBUG) Log.d( |
| 453 | TAG, "Including root " + root + " in roots list."); |
Jeff Sharkey | 348ad68 | 2013-09-02 17:19:40 -0700 | [diff] [blame] | 454 | matching.add(root); |
| 455 | } |
| 456 | return matching; |
| 457 | } |
Jeff Sharkey | 6651669 | 2013-08-06 11:26:10 -0700 | [diff] [blame] | 458 | } |