blob: 88eeb49ec1691f936c95b93a37037f0bb30dc096 [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 Sharkey4eb407a2013-08-18 17:38:20 -070068 private final Context mContext;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070069 private final ContentObserver mObserver;
Jeff Sharkey66516692013-08-06 11:26:10 -070070
Steve McKay4a1ca862016-02-17 18:25:47 -080071 private final RootInfo mRecentsRoot;
Jeff Sharkey66516692013-08-06 11:26:10 -070072
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070073 private final Object mLock = new Object();
74 private final CountDownLatch mFirstLoad = new CountDownLatch(1);
75
76 @GuardedBy("mLock")
Jeff Sharkey87314082016-03-11 17:25:11 -070077 private boolean mFirstLoadDone;
78 @GuardedBy("mLock")
79 private PendingResult mBootCompletedResult;
80
81 @GuardedBy("mLock")
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070082 private Multimap<String, RootInfo> mRoots = ArrayListMultimap.create();
83 @GuardedBy("mLock")
Steve McKay58efce32015-08-20 16:19:38 +000084 private HashSet<String> mStoppedAuthorities = new HashSet<>();
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070085
86 @GuardedBy("mObservedAuthorities")
Steve McKay58efce32015-08-20 16:19:38 +000087 private final HashSet<String> mObservedAuthorities = new HashSet<>();
Jeff Sharkey4eb407a2013-08-18 17:38:20 -070088
89 public RootsCache(Context context) {
90 mContext = context;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070091 mObserver = new RootsChangedObserver();
Steve McKay4a1ca862016-02-17 18:25:47 -080092
93 // Create a new anonymous "Recents" RootInfo. It's a faker.
94 mRecentsRoot = new RootInfo() {{
Steve McKay008e9482016-02-18 15:32:16 -080095 // Special root for recents
96 derivedIcon = R.drawable.ic_root_recent;
97 derivedType = RootInfo.TYPE_RECENTS;
Steve McKay327c3132016-02-26 15:30:19 -080098 flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD
99 | Root.FLAG_SUPPORTS_CREATE;
Steve McKay008e9482016-02-18 15:32:16 -0800100 title = mContext.getString(R.string.root_recent);
101 availableBytes = -1;
102 }};
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700103 }
104
105 private class RootsChangedObserver extends ContentObserver {
106 public RootsChangedObserver() {
107 super(new Handler());
108 }
109
110 @Override
111 public void onChange(boolean selfChange, Uri uri) {
Steve McKay757fa1b2016-04-18 17:35:41 -0700112 if (uri == null) {
113 Log.w(TAG, "Received onChange event for null uri. Skipping.");
114 return;
115 }
Steve McKay83df8c02015-09-16 15:07:31 -0700116 if (DEBUG) Log.d(TAG, "Updating roots due to change at " + uri);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700117 updateAuthorityAsync(uri.getAuthority());
118 }
Jeff Sharkey4eb407a2013-08-18 17:38:20 -0700119 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700120
121 /**
122 * Gather roots from all known storage providers.
123 */
Jeff Sharkeya3ebfec2016-04-04 08:58:04 -0600124 public void updateAsync(boolean forceRefreshAll) {
Steve McKay355d82b2016-03-01 12:57:44 -0800125
126 // NOTE: This method is called when the UI language changes.
Jeff Sharkey87314082016-03-11 17:25:11 -0700127 // For that reason we update our RecentsRoot to reflect
Steve McKay355d82b2016-03-01 12:57:44 -0800128 // the current language.
129 mRecentsRoot.title = mContext.getString(R.string.root_recent);
130
131 // Nothing else about the root should ever change.
Steve McKaya1f76802016-02-25 13:34:03 -0800132 assert(mRecentsRoot.authority == null);
133 assert(mRecentsRoot.rootId == null);
134 assert(mRecentsRoot.derivedIcon == R.drawable.ic_root_recent);
135 assert(mRecentsRoot.derivedType == RootInfo.TYPE_RECENTS);
136 assert(mRecentsRoot.flags == (Root.FLAG_LOCAL_ONLY
137 | Root.FLAG_SUPPORTS_IS_CHILD
138 | Root.FLAG_SUPPORTS_CREATE));
Steve McKaya1f76802016-02-25 13:34:03 -0800139 assert(mRecentsRoot.availableBytes == -1);
140
Jeff Sharkeya3ebfec2016-04-04 08:58:04 -0600141 new UpdateTask(forceRefreshAll, null)
142 .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700143 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700144
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700145 /**
146 * Gather roots from storage providers belonging to given package name.
147 */
148 public void updatePackageAsync(String packageName) {
Jeff Sharkeya3ebfec2016-04-04 08:58:04 -0600149 new UpdateTask(false, packageName).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700150 }
151
152 /**
153 * Gather roots from storage providers belonging to given authority.
154 */
155 public void updateAuthorityAsync(String authority) {
156 final ProviderInfo info = mContext.getPackageManager().resolveContentProvider(authority, 0);
157 if (info != null) {
158 updatePackageAsync(info.packageName);
159 }
160 }
161
Jeff Sharkey87314082016-03-11 17:25:11 -0700162 public void setBootCompletedResult(PendingResult result) {
163 synchronized (mLock) {
164 // Quickly check if we've already finished loading, otherwise hang
165 // out until first pass is finished.
166 if (mFirstLoadDone) {
167 result.finish();
168 } else {
169 mBootCompletedResult = result;
170 }
171 }
172 }
173
174 /**
175 * Block until the first {@link UpdateTask} pass has finished.
176 *
177 * @return {@code true} if cached roots is ready to roll, otherwise
178 * {@code false} if we timed out while waiting.
179 */
180 private boolean waitForFirstLoad() {
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700181 boolean success = false;
182 try {
183 success = mFirstLoad.await(15, TimeUnit.SECONDS);
184 } catch (InterruptedException e) {
185 }
186 if (!success) {
187 Log.w(TAG, "Timeout waiting for first update");
188 }
Jeff Sharkey87314082016-03-11 17:25:11 -0700189 return success;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700190 }
191
192 /**
193 * Load roots from authorities that are in stopped state. Normal
194 * {@link UpdateTask} passes ignore stopped applications.
195 */
196 private void loadStoppedAuthorities() {
197 final ContentResolver resolver = mContext.getContentResolver();
198 synchronized (mLock) {
199 for (String authority : mStoppedAuthorities) {
Steve McKay83df8c02015-09-16 15:07:31 -0700200 if (DEBUG) Log.d(TAG, "Loading stopped authority " + authority);
Jeff Sharkey7732e1e2016-03-30 17:14:23 -0600201 mRoots.putAll(authority, loadRootsForAuthority(resolver, authority, true));
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700202 }
203 mStoppedAuthorities.clear();
204 }
205 }
206
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900207 /**
208 * Load roots from a stopped authority. Normal {@link UpdateTask} passes
209 * ignore stopped applications.
210 */
211 private void loadStoppedAuthority(String authority) {
212 final ContentResolver resolver = mContext.getContentResolver();
213 synchronized (mLock) {
Tomasz Mikolajewskidf676dc2016-02-03 15:18:22 +0900214 if (!mStoppedAuthorities.contains(authority)) {
215 return;
216 }
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900217 if (DEBUG) {
218 Log.d(TAG, "Loading stopped authority " + authority);
219 }
Jeff Sharkey7732e1e2016-03-30 17:14:23 -0600220 mRoots.putAll(authority, loadRootsForAuthority(resolver, authority, true));
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900221 mStoppedAuthorities.remove(authority);
222 }
223 }
224
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700225 private class UpdateTask extends AsyncTask<Void, Void, Void> {
Jeff Sharkeya3ebfec2016-04-04 08:58:04 -0600226 private final boolean mForceRefreshAll;
Jeff Sharkey7732e1e2016-03-30 17:14:23 -0600227 private final String mForceRefreshPackage;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700228
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700229 private final Multimap<String, RootInfo> mTaskRoots = ArrayListMultimap.create();
Steve McKay58efce32015-08-20 16:19:38 +0000230 private final HashSet<String> mTaskStoppedAuthorities = new HashSet<>();
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700231
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700232 /**
Jeff Sharkeya3ebfec2016-04-04 08:58:04 -0600233 * Create task to update roots cache.
234 *
235 * @param forceRefreshAll when true, all previously cached values for
236 * all packages should be ignored.
237 * @param forceRefreshPackage when non-null, all previously cached
238 * values for this specific package should be ignored.
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700239 */
Jeff Sharkeya3ebfec2016-04-04 08:58:04 -0600240 public UpdateTask(boolean forceRefreshAll, String forceRefreshPackage) {
241 mForceRefreshAll = forceRefreshAll;
Jeff Sharkey7732e1e2016-03-30 17:14:23 -0600242 mForceRefreshPackage = forceRefreshPackage;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700243 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700244
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700245 @Override
246 protected Void doInBackground(Void... params) {
247 final long start = SystemClock.elapsedRealtime();
248
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700249 mTaskRoots.put(mRecentsRoot.authority, mRecentsRoot);
Jeff Sharkey5545f562013-09-21 13:57:33 -0700250
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700251 final ContentResolver resolver = mContext.getContentResolver();
252 final PackageManager pm = mContext.getPackageManager();
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700253
254 // Pick up provider with action string
255 final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE);
256 final List<ResolveInfo> providers = pm.queryIntentContentProviders(intent, 0);
257 for (ResolveInfo info : providers) {
258 handleDocumentsProvider(info.providerInfo);
259 }
260
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700261 final long delta = SystemClock.elapsedRealtime() - start;
Steve McKay83df8c02015-09-16 15:07:31 -0700262 if (DEBUG)
263 Log.d(TAG, "Update found " + mTaskRoots.size() + " roots in " + delta + "ms");
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700264 synchronized (mLock) {
Jeff Sharkey87314082016-03-11 17:25:11 -0700265 mFirstLoadDone = true;
266 if (mBootCompletedResult != null) {
267 mBootCompletedResult.finish();
268 mBootCompletedResult = null;
269 }
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700270 mRoots = mTaskRoots;
271 mStoppedAuthorities = mTaskStoppedAuthorities;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700272 }
273 mFirstLoad.countDown();
Jeff Sharkey46de7b52013-10-23 09:59:06 -0700274 resolver.notifyChange(sNotificationUri, null, false);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700275 return null;
276 }
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700277
278 private void handleDocumentsProvider(ProviderInfo info) {
279 // Ignore stopped packages for now; we might query them
280 // later during UI interaction.
281 if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
Steve McKay83df8c02015-09-16 15:07:31 -0700282 if (DEBUG) Log.d(TAG, "Ignoring stopped authority " + info.authority);
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700283 mTaskStoppedAuthorities.add(info.authority);
284 return;
285 }
286
Jeff Sharkeya3ebfec2016-04-04 08:58:04 -0600287 final boolean forceRefresh = mForceRefreshAll
288 || Objects.equals(info.packageName, mForceRefreshPackage);
Jeff Sharkey7732e1e2016-03-30 17:14:23 -0600289 mTaskRoots.putAll(info.authority, loadRootsForAuthority(mContext.getContentResolver(),
290 info.authority, forceRefresh));
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700291 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700292 }
293
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700294 /**
295 * Bring up requested provider and query for all active roots.
296 */
Jeff Sharkey7732e1e2016-03-30 17:14:23 -0600297 private Collection<RootInfo> loadRootsForAuthority(ContentResolver resolver, String authority,
298 boolean forceRefresh) {
Steve McKay83df8c02015-09-16 15:07:31 -0700299 if (DEBUG) Log.d(TAG, "Loading roots for " + authority);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700300
301 synchronized (mObservedAuthorities) {
302 if (mObservedAuthorities.add(authority)) {
303 // Watch for any future updates
304 final Uri rootsUri = DocumentsContract.buildRootsUri(authority);
305 mContext.getContentResolver().registerContentObserver(rootsUri, true, mObserver);
306 }
307 }
308
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700309 final Uri rootsUri = DocumentsContract.buildRootsUri(authority);
Jeff Sharkey7732e1e2016-03-30 17:14:23 -0600310 if (!forceRefresh) {
Jeff Sharkey87314082016-03-11 17:25:11 -0700311 // Look for roots data that we might have cached for ourselves in the
312 // long-lived system process.
313 final Bundle systemCache = resolver.getCache(rootsUri);
314 if (systemCache != null) {
315 if (DEBUG) Log.d(TAG, "System cache hit for " + authority);
316 return systemCache.getParcelableArrayList(TAG);
317 }
318 }
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700319
Jeff Sharkey87314082016-03-11 17:25:11 -0700320 final ArrayList<RootInfo> roots = new ArrayList<>();
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700321 ContentProviderClient client = null;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700322 Cursor cursor = null;
323 try {
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700324 client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700325 cursor = client.query(rootsUri, null, null, null, null);
326 while (cursor.moveToNext()) {
327 final RootInfo root = RootInfo.fromRootsCursor(authority, cursor);
328 roots.add(root);
329 }
330 } catch (Exception e) {
331 Log.w(TAG, "Failed to load some roots from " + authority + ": " + e);
332 } finally {
333 IoUtils.closeQuietly(cursor);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700334 ContentProviderClient.releaseQuietly(client);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700335 }
Jeff Sharkey87314082016-03-11 17:25:11 -0700336
Jeff Sharkey7732e1e2016-03-30 17:14:23 -0600337 // Cache these freshly parsed roots over in the long-lived system
338 // process, in case our process goes away. The system takes care of
339 // invalidating the cache if the package or Uri changes.
340 final Bundle systemCache = new Bundle();
341 systemCache.putParcelableArrayList(TAG, roots);
342 resolver.putCache(rootsUri, systemCache);
Jeff Sharkey87314082016-03-11 17:25:11 -0700343
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700344 return roots;
345 }
346
347 /**
348 * Return the requested {@link RootInfo}, but only loading the roots for the
349 * requested authority. This is useful when we want to load fast without
350 * waiting for all the other roots to come back.
351 */
352 public RootInfo getRootOneshot(String authority, String rootId) {
353 synchronized (mLock) {
354 RootInfo root = getRootLocked(authority, rootId);
355 if (root == null) {
Jeff Sharkey7732e1e2016-03-30 17:14:23 -0600356 mRoots.putAll(authority,
357 loadRootsForAuthority(mContext.getContentResolver(), authority, false));
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700358 root = getRootLocked(authority, rootId);
359 }
360 return root;
361 }
362 }
363
364 public RootInfo getRootBlocking(String authority, String rootId) {
365 waitForFirstLoad();
366 loadStoppedAuthorities();
367 synchronized (mLock) {
368 return getRootLocked(authority, rootId);
369 }
370 }
371
372 private RootInfo getRootLocked(String authority, String rootId) {
373 for (RootInfo root : mRoots.get(authority)) {
Kenny Roote6585b32013-12-13 12:00:26 -0800374 if (Objects.equals(root.rootId, rootId)) {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700375 return root;
376 }
377 }
378 return null;
Jeff Sharkey66516692013-08-06 11:26:10 -0700379 }
380
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700381 public boolean isIconUniqueBlocking(RootInfo root) {
382 waitForFirstLoad();
383 loadStoppedAuthorities();
384 synchronized (mLock) {
385 final int rootIcon = root.derivedIcon != 0 ? root.derivedIcon : root.icon;
386 for (RootInfo test : mRoots.get(root.authority)) {
Kenny Roote6585b32013-12-13 12:00:26 -0800387 if (Objects.equals(test.rootId, root.rootId)) {
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700388 continue;
389 }
Jeff Sharkeyf6db1542013-09-13 13:42:19 -0700390 final int testIcon = test.derivedIcon != 0 ? test.derivedIcon : test.icon;
391 if (testIcon == rootIcon) {
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700392 return false;
393 }
394 }
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700395 return true;
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700396 }
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700397 }
398
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700399 public RootInfo getRecentsRoot() {
Jeff Sharkey4eb407a2013-08-18 17:38:20 -0700400 return mRecentsRoot;
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700401 }
402
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700403 public boolean isRecentsRoot(RootInfo root) {
Steve McKay4a1ca862016-02-17 18:25:47 -0800404 return mRecentsRoot.equals(root);
Jeff Sharkey66516692013-08-06 11:26:10 -0700405 }
406
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700407 public Collection<RootInfo> getRootsBlocking() {
408 waitForFirstLoad();
409 loadStoppedAuthorities();
410 synchronized (mLock) {
411 return mRoots.values();
412 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700413 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700414
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700415 public Collection<RootInfo> getMatchingRootsBlocking(State state) {
416 waitForFirstLoad();
417 loadStoppedAuthorities();
418 synchronized (mLock) {
419 return getMatchingRoots(mRoots.values(), state);
420 }
Jeff Sharkey6d97d3c2013-09-06 10:43:45 -0700421 }
422
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900423 /**
424 * Returns a list of roots for the specified authority. If not found, then
425 * an empty list is returned.
426 */
427 public Collection<RootInfo> getRootsForAuthorityBlocking(String authority) {
428 waitForFirstLoad();
429 loadStoppedAuthority(authority);
430 synchronized (mLock) {
431 final Collection<RootInfo> roots = mRoots.get(authority);
432 return roots != null ? roots : Collections.<RootInfo>emptyList();
433 }
434 }
435
Tomasz Mikolajewski30616fc2016-04-15 11:27:53 +0900436 /**
437 * Returns the default root for the specified state.
438 */
439 public RootInfo getDefaultRootBlocking(State state) {
440 for (RootInfo root : getMatchingRoots(getRootsBlocking(), state)) {
441 if (root.isDownloads()) {
442 return root;
443 }
444 }
445 return mRecentsRoot;
446 }
447
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700448 @VisibleForTesting
449 static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) {
Steve McKay58efce32015-08-20 16:19:38 +0000450 final List<RootInfo> matching = new ArrayList<>();
Jeff Sharkey6d97d3c2013-09-06 10:43:45 -0700451 for (RootInfo root : roots) {
Steve McKayea9ec292016-03-01 15:11:56 -0800452
453 if (DEBUG) Log.d(TAG, "Evaluating " + root);
454
455 if (state.action == State.ACTION_CREATE && !root.supportsCreate()) {
456 if (DEBUG) Log.d(TAG, "Excluding read-only root because: ACTION_CREATE.");
457 continue;
458 }
459
Steve McKay4a1ca862016-02-17 18:25:47 -0800460 if (state.action == State.ACTION_PICK_COPY_DESTINATION
Steve McKayea9ec292016-03-01 15:11:56 -0800461 && !root.supportsCreate()) {
462 if (DEBUG) Log.d(
463 TAG, "Excluding read-only root because: ACTION_PICK_COPY_DESTINATION.");
464 continue;
465 }
466
467 if (state.action == State.ACTION_OPEN_TREE && !root.supportsChildren()) {
468 if (DEBUG) Log.d(
469 TAG, "Excluding root !supportsChildren because: ACTION_OPEN_TREE.");
470 continue;
471 }
472
Aga Wronska774cc932016-03-30 18:07:59 -0700473 if (!state.showAdvanced && root.isAdvanced()) {
474 if (DEBUG) Log.d(TAG, "Excluding root because: unwanted advanced device.");
475 continue;
476 }
477
Steve McKayea9ec292016-03-01 15:11:56 -0800478 if (state.localOnly && !root.isLocalOnly()) {
479 if (DEBUG) Log.d(TAG, "Excluding root because: unwanted non-local device.");
480 continue;
481 }
482
Steve McKayea9ec292016-03-01 15:11:56 -0800483 if (state.directoryCopy && root.isDownloads()) {
484 if (DEBUG) Log.d(
485 TAG, "Excluding downloads root because: unsupported directory copy.");
486 continue;
487 }
Steve McKay83df8c02015-09-16 15:07:31 -0700488
Steve McKayea9ec292016-03-01 15:11:56 -0800489 if (state.action == State.ACTION_OPEN && root.isEmpty()) {
490 if (DEBUG) Log.d(TAG, "Excluding empty root because: ACTION_OPEN.");
491 continue;
492 }
493
Steve McKayea9ec292016-03-01 15:11:56 -0800494 if (state.action == State.ACTION_GET_CONTENT && root.isEmpty()) {
495 if (DEBUG) Log.d(TAG, "Excluding empty root because: ACTION_GET_CONTENT.");
Steve McKay83df8c02015-09-16 15:07:31 -0700496 continue;
497 }
Jeff Sharkey348ad682013-09-02 17:19:40 -0700498
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700499 final boolean overlap =
500 MimePredicate.mimeMatches(root.derivedMimeTypes, state.acceptMimes) ||
501 MimePredicate.mimeMatches(state.acceptMimes, root.derivedMimeTypes);
Jeff Sharkey6d97d3c2013-09-06 10:43:45 -0700502 if (!overlap) {
Steve McKayea9ec292016-03-01 15:11:56 -0800503 if (DEBUG) Log.d(
504 TAG, "Excluding root because: unsupported content types > "
505 + state.acceptMimes);
Jeff Sharkey923396b2013-09-05 13:55:35 -0700506 continue;
Jeff Sharkey348ad682013-09-02 17:19:40 -0700507 }
508
Ben Kwa77797402015-05-29 15:40:31 -0700509 if (state.excludedAuthorities.contains(root.authority)) {
Steve McKay7c439582016-03-01 15:41:47 -0800510 if (DEBUG) Log.d(TAG, "Excluding root because: owned by calling package.");
Ben Kwa77797402015-05-29 15:40:31 -0700511 continue;
512 }
513
Steve McKayea9ec292016-03-01 15:11:56 -0800514 if (DEBUG) Log.d(TAG, "Including " + root);
Jeff Sharkey348ad682013-09-02 17:19:40 -0700515 matching.add(root);
516 }
517 return matching;
518 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700519}