blob: 6efe9c8fae83f6caa5afb9c1388983aea4bff233 [file] [log] [blame]
Jeff Sharkey66516692013-08-06 11:26:10 -07001/*
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
17package com.android.documentsui;
18
Steve McKay83df8c02015-09-16 15:07:31 -070019import static com.android.documentsui.Shared.DEBUG;
Jeff Sharkey66516692013-08-06 11:26:10 -070020
Jeff Sharkey87314082016-03-11 17:25:11 -070021import android.content.BroadcastReceiver.PendingResult;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070022import android.content.ContentProviderClient;
23import android.content.ContentResolver;
Jeff Sharkey66516692013-08-06 11:26:10 -070024import android.content.Context;
Jeff Sharkey85f5f812013-10-07 10:16:12 -070025import android.content.Intent;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070026import android.content.pm.ApplicationInfo;
Jeff Sharkey66516692013-08-06 11:26:10 -070027import android.content.pm.PackageManager;
28import android.content.pm.ProviderInfo;
Jeff Sharkey85f5f812013-10-07 10:16:12 -070029import android.content.pm.ResolveInfo;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070030import android.database.ContentObserver;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070031import android.database.Cursor;
Jeff Sharkey66516692013-08-06 11:26:10 -070032import android.net.Uri;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070033import android.os.AsyncTask;
Jeff Sharkey87314082016-03-11 17:25:11 -070034import android.os.Bundle;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070035import android.os.Handler;
36import android.os.SystemClock;
Jeff Sharkey66516692013-08-06 11:26:10 -070037import android.provider.DocumentsContract;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070038import android.provider.DocumentsContract.Root;
Steve McKay7a3b88c2015-09-23 17:21:40 -070039import android.support.annotation.VisibleForTesting;
Jeff Sharkey66516692013-08-06 11:26:10 -070040import android.util.Log;
Jeff Sharkey66516692013-08-06 11:26:10 -070041
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070042import com.android.documentsui.model.RootInfo;
Jeff Sharkey66516692013-08-06 11:26:10 -070043import com.android.internal.annotations.GuardedBy;
Steve McKay58efce32015-08-20 16:19:38 +000044
Jeff Sharkey87314082016-03-11 17:25:11 -070045import libcore.io.IoUtils;
46
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070047import com.google.common.collect.ArrayListMultimap;
48import com.google.common.collect.Multimap;
Jeff Sharkey66516692013-08-06 11:26:10 -070049
Steve McKay58efce32015-08-20 16:19:38 +000050import java.util.ArrayList;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070051import java.util.Collection;
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +090052import java.util.Collections;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070053import java.util.HashSet;
Jeff Sharkey66516692013-08-06 11:26:10 -070054import java.util.List;
Kenny Roote6585b32013-12-13 12:00:26 -080055import java.util.Objects;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070056import java.util.concurrent.CountDownLatch;
57import java.util.concurrent.TimeUnit;
Jeff Sharkey66516692013-08-06 11:26:10 -070058
59/**
60 * Cache of known storage backends and their roots.
61 */
62public class RootsCache {
Jeff Sharkey46de7b52013-10-23 09:59:06 -070063 public static final Uri sNotificationUri = Uri.parse(
64 "content://com.android.documentsui.roots/");
Jeff Sharkey66516692013-08-06 11:26:10 -070065
Steve McKayea9ec292016-03-01 15:11:56 -080066 private static final String TAG = "RootsCache";
67
Jeff Sharkey87314082016-03-11 17:25:11 -070068 private static final boolean ENABLE_SYSTEM_CACHE = true;
69
Jeff Sharkey4eb407a2013-08-18 17:38:20 -070070 private final Context mContext;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070071 private final ContentObserver mObserver;
Daichi Hirono60e9a072015-12-25 11:08:42 +090072 private OnCacheUpdateListener mCacheUpdateListener;
Jeff Sharkey66516692013-08-06 11:26:10 -070073
Steve McKay4a1ca862016-02-17 18:25:47 -080074 private final RootInfo mRecentsRoot;
Jeff Sharkey66516692013-08-06 11:26:10 -070075
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070076 private final Object mLock = new Object();
77 private final CountDownLatch mFirstLoad = new CountDownLatch(1);
78
79 @GuardedBy("mLock")
Jeff Sharkey87314082016-03-11 17:25:11 -070080 private boolean mFirstLoadDone;
81 @GuardedBy("mLock")
82 private PendingResult mBootCompletedResult;
83
84 @GuardedBy("mLock")
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070085 private Multimap<String, RootInfo> mRoots = ArrayListMultimap.create();
86 @GuardedBy("mLock")
Steve McKay58efce32015-08-20 16:19:38 +000087 private HashSet<String> mStoppedAuthorities = new HashSet<>();
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070088
89 @GuardedBy("mObservedAuthorities")
Steve McKay58efce32015-08-20 16:19:38 +000090 private final HashSet<String> mObservedAuthorities = new HashSet<>();
Jeff Sharkey4eb407a2013-08-18 17:38:20 -070091
92 public RootsCache(Context context) {
93 mContext = context;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070094 mObserver = new RootsChangedObserver();
Steve McKay4a1ca862016-02-17 18:25:47 -080095
96 // Create a new anonymous "Recents" RootInfo. It's a faker.
97 mRecentsRoot = new RootInfo() {{
Steve McKay008e9482016-02-18 15:32:16 -080098 // Special root for recents
99 derivedIcon = R.drawable.ic_root_recent;
100 derivedType = RootInfo.TYPE_RECENTS;
Steve McKay327c3132016-02-26 15:30:19 -0800101 flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD
102 | Root.FLAG_SUPPORTS_CREATE;
Steve McKay008e9482016-02-18 15:32:16 -0800103 title = mContext.getString(R.string.root_recent);
104 availableBytes = -1;
105 }};
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700106 }
107
108 private class RootsChangedObserver extends ContentObserver {
109 public RootsChangedObserver() {
110 super(new Handler());
111 }
112
113 @Override
114 public void onChange(boolean selfChange, Uri uri) {
Steve McKay83df8c02015-09-16 15:07:31 -0700115 if (DEBUG) Log.d(TAG, "Updating roots due to change at " + uri);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700116 updateAuthorityAsync(uri.getAuthority());
117 }
Jeff Sharkey4eb407a2013-08-18 17:38:20 -0700118 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700119
Daichi Hirono60e9a072015-12-25 11:08:42 +0900120 static interface OnCacheUpdateListener {
121 void onCacheUpdate();
122 }
123
Jeff Sharkey66516692013-08-06 11:26:10 -0700124 /**
125 * Gather roots from all known storage providers.
126 */
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700127 public void updateAsync() {
Steve McKay355d82b2016-03-01 12:57:44 -0800128
129 // NOTE: This method is called when the UI language changes.
Jeff Sharkey87314082016-03-11 17:25:11 -0700130 // For that reason we update our RecentsRoot to reflect
Steve McKay355d82b2016-03-01 12:57:44 -0800131 // the current language.
132 mRecentsRoot.title = mContext.getString(R.string.root_recent);
133
134 // Nothing else about the root should ever change.
Steve McKaya1f76802016-02-25 13:34:03 -0800135 assert(mRecentsRoot.authority == null);
136 assert(mRecentsRoot.rootId == null);
137 assert(mRecentsRoot.derivedIcon == R.drawable.ic_root_recent);
138 assert(mRecentsRoot.derivedType == RootInfo.TYPE_RECENTS);
139 assert(mRecentsRoot.flags == (Root.FLAG_LOCAL_ONLY
140 | Root.FLAG_SUPPORTS_IS_CHILD
141 | Root.FLAG_SUPPORTS_CREATE));
Steve McKaya1f76802016-02-25 13:34:03 -0800142 assert(mRecentsRoot.availableBytes == -1);
143
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700144 new UpdateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
145 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700146
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700147 /**
148 * Gather roots from storage providers belonging to given package name.
149 */
150 public void updatePackageAsync(String packageName) {
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700151 new UpdateTask(packageName).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
152 }
153
154 /**
155 * Gather roots from storage providers belonging to given authority.
156 */
157 public void updateAuthorityAsync(String authority) {
158 final ProviderInfo info = mContext.getPackageManager().resolveContentProvider(authority, 0);
159 if (info != null) {
160 updatePackageAsync(info.packageName);
161 }
162 }
163
Jeff Sharkey87314082016-03-11 17:25:11 -0700164 public void setBootCompletedResult(PendingResult result) {
165 synchronized (mLock) {
166 // Quickly check if we've already finished loading, otherwise hang
167 // out until first pass is finished.
168 if (mFirstLoadDone) {
169 result.finish();
170 } else {
171 mBootCompletedResult = result;
172 }
173 }
174 }
175
176 /**
177 * Block until the first {@link UpdateTask} pass has finished.
178 *
179 * @return {@code true} if cached roots is ready to roll, otherwise
180 * {@code false} if we timed out while waiting.
181 */
182 private boolean waitForFirstLoad() {
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700183 boolean success = false;
184 try {
185 success = mFirstLoad.await(15, TimeUnit.SECONDS);
186 } catch (InterruptedException e) {
187 }
188 if (!success) {
189 Log.w(TAG, "Timeout waiting for first update");
190 }
Jeff Sharkey87314082016-03-11 17:25:11 -0700191 return success;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700192 }
193
194 /**
195 * Load roots from authorities that are in stopped state. Normal
196 * {@link UpdateTask} passes ignore stopped applications.
197 */
198 private void loadStoppedAuthorities() {
199 final ContentResolver resolver = mContext.getContentResolver();
200 synchronized (mLock) {
201 for (String authority : mStoppedAuthorities) {
Steve McKay83df8c02015-09-16 15:07:31 -0700202 if (DEBUG) Log.d(TAG, "Loading stopped authority " + authority);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700203 mRoots.putAll(authority, loadRootsForAuthority(resolver, authority));
204 }
205 mStoppedAuthorities.clear();
206 }
207 }
208
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900209 /**
210 * Load roots from a stopped authority. Normal {@link UpdateTask} passes
211 * ignore stopped applications.
212 */
213 private void loadStoppedAuthority(String authority) {
214 final ContentResolver resolver = mContext.getContentResolver();
215 synchronized (mLock) {
Tomasz Mikolajewskidf676dc2016-02-03 15:18:22 +0900216 if (!mStoppedAuthorities.contains(authority)) {
217 return;
218 }
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900219 if (DEBUG) {
220 Log.d(TAG, "Loading stopped authority " + authority);
221 }
222 mRoots.putAll(authority, loadRootsForAuthority(resolver, authority));
223 mStoppedAuthorities.remove(authority);
224 }
225 }
226
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700227 private class UpdateTask extends AsyncTask<Void, Void, Void> {
228 private final String mFilterPackage;
229
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700230 private final Multimap<String, RootInfo> mTaskRoots = ArrayListMultimap.create();
Steve McKay58efce32015-08-20 16:19:38 +0000231 private final HashSet<String> mTaskStoppedAuthorities = new HashSet<>();
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700232
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700233 /**
234 * Update all roots.
235 */
236 public UpdateTask() {
237 this(null);
Jeff Sharkey66516692013-08-06 11:26:10 -0700238 }
239
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700240 /**
241 * Only update roots belonging to given package name. Other roots will
242 * be copied from cached {@link #mRoots} values.
243 */
244 public UpdateTask(String filterPackage) {
245 mFilterPackage = filterPackage;
246 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700247
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700248 @Override
249 protected Void doInBackground(Void... params) {
250 final long start = SystemClock.elapsedRealtime();
251
Jeff Sharkeyc9d71502014-09-10 11:23:15 -0700252 if (mFilterPackage != null) {
Jeff Sharkey87314082016-03-11 17:25:11 -0700253 // We must have previously cached values to fill in non-matching
254 // packages, so wait around for successful first load.
255 if (!waitForFirstLoad()) {
256 return null;
257 }
Jeff Sharkeyc9d71502014-09-10 11:23:15 -0700258 }
259
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700260 mTaskRoots.put(mRecentsRoot.authority, mRecentsRoot);
Jeff Sharkey5545f562013-09-21 13:57:33 -0700261
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700262 final ContentResolver resolver = mContext.getContentResolver();
263 final PackageManager pm = mContext.getPackageManager();
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700264
265 // Pick up provider with action string
266 final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE);
267 final List<ResolveInfo> providers = pm.queryIntentContentProviders(intent, 0);
268 for (ResolveInfo info : providers) {
269 handleDocumentsProvider(info.providerInfo);
270 }
271
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700272 final long delta = SystemClock.elapsedRealtime() - start;
Steve McKay83df8c02015-09-16 15:07:31 -0700273 if (DEBUG)
274 Log.d(TAG, "Update found " + mTaskRoots.size() + " roots in " + delta + "ms");
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700275 synchronized (mLock) {
Jeff Sharkey87314082016-03-11 17:25:11 -0700276 mFirstLoadDone = true;
277 if (mBootCompletedResult != null) {
278 mBootCompletedResult.finish();
279 mBootCompletedResult = null;
280 }
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700281 mRoots = mTaskRoots;
282 mStoppedAuthorities = mTaskStoppedAuthorities;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700283 }
284 mFirstLoad.countDown();
Jeff Sharkey46de7b52013-10-23 09:59:06 -0700285 resolver.notifyChange(sNotificationUri, null, false);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700286 return null;
287 }
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700288
Daichi Hirono60e9a072015-12-25 11:08:42 +0900289 @Override
290 protected void onPostExecute(Void result) {
291 if (mCacheUpdateListener != null) {
292 mCacheUpdateListener.onCacheUpdate();
293 }
294 }
295
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700296 private void handleDocumentsProvider(ProviderInfo info) {
297 // Ignore stopped packages for now; we might query them
298 // later during UI interaction.
299 if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
Steve McKay83df8c02015-09-16 15:07:31 -0700300 if (DEBUG) Log.d(TAG, "Ignoring stopped authority " + info.authority);
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700301 mTaskStoppedAuthorities.add(info.authority);
302 return;
303 }
304
305 // Try using cached roots if filtering
306 boolean cacheHit = false;
307 if (mFilterPackage != null && !mFilterPackage.equals(info.packageName)) {
308 synchronized (mLock) {
309 if (mTaskRoots.putAll(info.authority, mRoots.get(info.authority))) {
Steve McKay83df8c02015-09-16 15:07:31 -0700310 if (DEBUG) Log.d(TAG, "Used cached roots for " + info.authority);
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700311 cacheHit = true;
312 }
313 }
314 }
315
316 // Cache miss, or loading everything
317 if (!cacheHit) {
318 mTaskRoots.putAll(info.authority,
319 loadRootsForAuthority(mContext.getContentResolver(), info.authority));
320 }
321 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700322 }
323
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700324 /**
325 * Bring up requested provider and query for all active roots.
326 */
327 private Collection<RootInfo> loadRootsForAuthority(ContentResolver resolver, String authority) {
Steve McKay83df8c02015-09-16 15:07:31 -0700328 if (DEBUG) Log.d(TAG, "Loading roots for " + authority);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700329
330 synchronized (mObservedAuthorities) {
331 if (mObservedAuthorities.add(authority)) {
332 // Watch for any future updates
333 final Uri rootsUri = DocumentsContract.buildRootsUri(authority);
334 mContext.getContentResolver().registerContentObserver(rootsUri, true, mObserver);
335 }
336 }
337
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700338 final Uri rootsUri = DocumentsContract.buildRootsUri(authority);
Jeff Sharkey87314082016-03-11 17:25:11 -0700339 if (ENABLE_SYSTEM_CACHE) {
340 // Look for roots data that we might have cached for ourselves in the
341 // long-lived system process.
342 final Bundle systemCache = resolver.getCache(rootsUri);
343 if (systemCache != null) {
344 if (DEBUG) Log.d(TAG, "System cache hit for " + authority);
345 return systemCache.getParcelableArrayList(TAG);
346 }
347 }
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700348
Jeff Sharkey87314082016-03-11 17:25:11 -0700349 final ArrayList<RootInfo> roots = new ArrayList<>();
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700350 ContentProviderClient client = null;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700351 Cursor cursor = null;
352 try {
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700353 client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700354 cursor = client.query(rootsUri, null, null, null, null);
355 while (cursor.moveToNext()) {
356 final RootInfo root = RootInfo.fromRootsCursor(authority, cursor);
357 roots.add(root);
358 }
359 } catch (Exception e) {
360 Log.w(TAG, "Failed to load some roots from " + authority + ": " + e);
361 } finally {
362 IoUtils.closeQuietly(cursor);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700363 ContentProviderClient.releaseQuietly(client);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700364 }
Jeff Sharkey87314082016-03-11 17:25:11 -0700365
366 if (ENABLE_SYSTEM_CACHE) {
367 // Cache these freshly parsed roots over in the long-lived system
368 // process, in case our process goes away. The system takes care of
369 // invalidating the cache if the package or Uri changes.
370 final Bundle systemCache = new Bundle();
371 systemCache.putParcelableArrayList(TAG, roots);
372 resolver.putCache(rootsUri, systemCache);
373 }
374
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700375 return roots;
376 }
377
378 /**
379 * Return the requested {@link RootInfo}, but only loading the roots for the
380 * requested authority. This is useful when we want to load fast without
381 * waiting for all the other roots to come back.
382 */
383 public RootInfo getRootOneshot(String authority, String rootId) {
384 synchronized (mLock) {
385 RootInfo root = getRootLocked(authority, rootId);
386 if (root == null) {
387 mRoots.putAll(
388 authority, loadRootsForAuthority(mContext.getContentResolver(), authority));
389 root = getRootLocked(authority, rootId);
390 }
391 return root;
392 }
393 }
394
395 public RootInfo getRootBlocking(String authority, String rootId) {
396 waitForFirstLoad();
397 loadStoppedAuthorities();
398 synchronized (mLock) {
399 return getRootLocked(authority, rootId);
400 }
401 }
402
403 private RootInfo getRootLocked(String authority, String rootId) {
404 for (RootInfo root : mRoots.get(authority)) {
Kenny Roote6585b32013-12-13 12:00:26 -0800405 if (Objects.equals(root.rootId, rootId)) {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700406 return root;
407 }
408 }
409 return null;
Jeff Sharkey66516692013-08-06 11:26:10 -0700410 }
411
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700412 public boolean isIconUniqueBlocking(RootInfo root) {
413 waitForFirstLoad();
414 loadStoppedAuthorities();
415 synchronized (mLock) {
416 final int rootIcon = root.derivedIcon != 0 ? root.derivedIcon : root.icon;
417 for (RootInfo test : mRoots.get(root.authority)) {
Kenny Roote6585b32013-12-13 12:00:26 -0800418 if (Objects.equals(test.rootId, root.rootId)) {
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700419 continue;
420 }
Jeff Sharkeyf6db1542013-09-13 13:42:19 -0700421 final int testIcon = test.derivedIcon != 0 ? test.derivedIcon : test.icon;
422 if (testIcon == rootIcon) {
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700423 return false;
424 }
425 }
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700426 return true;
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700427 }
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700428 }
429
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700430 public RootInfo getRecentsRoot() {
Jeff Sharkey4eb407a2013-08-18 17:38:20 -0700431 return mRecentsRoot;
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700432 }
433
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700434 public boolean isRecentsRoot(RootInfo root) {
Steve McKay4a1ca862016-02-17 18:25:47 -0800435 return mRecentsRoot.equals(root);
Jeff Sharkey66516692013-08-06 11:26:10 -0700436 }
437
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700438 public Collection<RootInfo> getRootsBlocking() {
439 waitForFirstLoad();
440 loadStoppedAuthorities();
441 synchronized (mLock) {
442 return mRoots.values();
443 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700444 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700445
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700446 public Collection<RootInfo> getMatchingRootsBlocking(State state) {
447 waitForFirstLoad();
448 loadStoppedAuthorities();
449 synchronized (mLock) {
450 return getMatchingRoots(mRoots.values(), state);
451 }
Jeff Sharkey6d97d3c2013-09-06 10:43:45 -0700452 }
453
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900454 /**
455 * Returns a list of roots for the specified authority. If not found, then
456 * an empty list is returned.
457 */
458 public Collection<RootInfo> getRootsForAuthorityBlocking(String authority) {
459 waitForFirstLoad();
460 loadStoppedAuthority(authority);
461 synchronized (mLock) {
462 final Collection<RootInfo> roots = mRoots.get(authority);
463 return roots != null ? roots : Collections.<RootInfo>emptyList();
464 }
465 }
466
Daichi Hirono60e9a072015-12-25 11:08:42 +0900467 public void setOnCacheUpdateListener(OnCacheUpdateListener cacheUpdateListener) {
468 mCacheUpdateListener = cacheUpdateListener;
469 }
470
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700471 @VisibleForTesting
472 static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) {
Steve McKay58efce32015-08-20 16:19:38 +0000473 final List<RootInfo> matching = new ArrayList<>();
Jeff Sharkey6d97d3c2013-09-06 10:43:45 -0700474 for (RootInfo root : roots) {
Steve McKayea9ec292016-03-01 15:11:56 -0800475
476 if (DEBUG) Log.d(TAG, "Evaluating " + root);
477
478 if (state.action == State.ACTION_CREATE && !root.supportsCreate()) {
479 if (DEBUG) Log.d(TAG, "Excluding read-only root because: ACTION_CREATE.");
480 continue;
481 }
482
Steve McKay4a1ca862016-02-17 18:25:47 -0800483 if (state.action == State.ACTION_PICK_COPY_DESTINATION
Steve McKayea9ec292016-03-01 15:11:56 -0800484 && !root.supportsCreate()) {
485 if (DEBUG) Log.d(
486 TAG, "Excluding read-only root because: ACTION_PICK_COPY_DESTINATION.");
487 continue;
488 }
489
490 if (state.action == State.ACTION_OPEN_TREE && !root.supportsChildren()) {
491 if (DEBUG) Log.d(
492 TAG, "Excluding root !supportsChildren because: ACTION_OPEN_TREE.");
493 continue;
494 }
495
Steve McKayea9ec292016-03-01 15:11:56 -0800496 if (state.localOnly && !root.isLocalOnly()) {
497 if (DEBUG) Log.d(TAG, "Excluding root because: unwanted non-local device.");
498 continue;
499 }
500
Steve McKayea9ec292016-03-01 15:11:56 -0800501 if (state.directoryCopy && root.isDownloads()) {
502 if (DEBUG) Log.d(
503 TAG, "Excluding downloads root because: unsupported directory copy.");
504 continue;
505 }
Steve McKay83df8c02015-09-16 15:07:31 -0700506
Steve McKayea9ec292016-03-01 15:11:56 -0800507 if (state.action == State.ACTION_OPEN && root.isEmpty()) {
508 if (DEBUG) Log.d(TAG, "Excluding empty root because: ACTION_OPEN.");
509 continue;
510 }
511
Steve McKayea9ec292016-03-01 15:11:56 -0800512 if (state.action == State.ACTION_GET_CONTENT && root.isEmpty()) {
513 if (DEBUG) Log.d(TAG, "Excluding empty root because: ACTION_GET_CONTENT.");
Steve McKay83df8c02015-09-16 15:07:31 -0700514 continue;
515 }
Jeff Sharkey348ad682013-09-02 17:19:40 -0700516
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700517 final boolean overlap =
518 MimePredicate.mimeMatches(root.derivedMimeTypes, state.acceptMimes) ||
519 MimePredicate.mimeMatches(state.acceptMimes, root.derivedMimeTypes);
Jeff Sharkey6d97d3c2013-09-06 10:43:45 -0700520 if (!overlap) {
Steve McKayea9ec292016-03-01 15:11:56 -0800521 if (DEBUG) Log.d(
522 TAG, "Excluding root because: unsupported content types > "
523 + state.acceptMimes);
Jeff Sharkey923396b2013-09-05 13:55:35 -0700524 continue;
Jeff Sharkey348ad682013-09-02 17:19:40 -0700525 }
526
Ben Kwa77797402015-05-29 15:40:31 -0700527 if (state.excludedAuthorities.contains(root.authority)) {
Steve McKay7c439582016-03-01 15:41:47 -0800528 if (DEBUG) Log.d(TAG, "Excluding root because: owned by calling package.");
Ben Kwa77797402015-05-29 15:40:31 -0700529 continue;
530 }
531
Steve McKayea9ec292016-03-01 15:11:56 -0800532 if (DEBUG) Log.d(TAG, "Including " + root);
Jeff Sharkey348ad682013-09-02 17:19:40 -0700533 matching.add(root);
534 }
535 return matching;
536 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700537}