blob: 216509d47c2048550d77b939de10c143cfe2a17f [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;
Steve McKay7a3b88c2015-09-23 17:21:40 -070020import static com.android.documentsui.Shared.TAG;
Steve McKay008e9482016-02-18 15:32:16 -080021import static com.android.internal.util.Preconditions.checkState;
Jeff Sharkey66516692013-08-06 11:26:10 -070022
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070023import android.content.ContentProviderClient;
24import android.content.ContentResolver;
Jeff Sharkey66516692013-08-06 11:26:10 -070025import android.content.Context;
Jeff Sharkey85f5f812013-10-07 10:16:12 -070026import android.content.Intent;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070027import android.content.pm.ApplicationInfo;
Jeff Sharkey66516692013-08-06 11:26:10 -070028import android.content.pm.PackageManager;
29import android.content.pm.ProviderInfo;
Jeff Sharkey85f5f812013-10-07 10:16:12 -070030import android.content.pm.ResolveInfo;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070031import android.database.ContentObserver;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070032import android.database.Cursor;
Jeff Sharkey66516692013-08-06 11:26:10 -070033import android.net.Uri;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070034import android.os.AsyncTask;
35import 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 McKay008e9482016-02-18 15:32:16 -080044import com.android.internal.util.Preconditions;
Steve McKay58efce32015-08-20 16:19:38 +000045
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070046import com.google.common.collect.ArrayListMultimap;
47import com.google.common.collect.Multimap;
Jeff Sharkey66516692013-08-06 11:26:10 -070048
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070049import libcore.io.IoUtils;
50
Steve McKay58efce32015-08-20 16:19:38 +000051import java.util.ArrayList;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070052import java.util.Collection;
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +090053import java.util.Collections;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070054import java.util.HashSet;
Jeff Sharkey66516692013-08-06 11:26:10 -070055import java.util.List;
Kenny Roote6585b32013-12-13 12:00:26 -080056import java.util.Objects;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070057import java.util.concurrent.CountDownLatch;
58import java.util.concurrent.TimeUnit;
Jeff Sharkey66516692013-08-06 11:26:10 -070059
60/**
61 * Cache of known storage backends and their roots.
62 */
63public class RootsCache {
Jeff Sharkey46de7b52013-10-23 09:59:06 -070064 public static final Uri sNotificationUri = Uri.parse(
65 "content://com.android.documentsui.roots/");
Jeff Sharkey66516692013-08-06 11:26:10 -070066
Jeff Sharkey4eb407a2013-08-18 17:38:20 -070067 private final Context mContext;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070068 private final ContentObserver mObserver;
Daichi Hirono60e9a072015-12-25 11:08:42 +090069 private OnCacheUpdateListener mCacheUpdateListener;
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")
77 private Multimap<String, RootInfo> mRoots = ArrayListMultimap.create();
78 @GuardedBy("mLock")
Steve McKay58efce32015-08-20 16:19:38 +000079 private HashSet<String> mStoppedAuthorities = new HashSet<>();
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070080
81 @GuardedBy("mObservedAuthorities")
Steve McKay58efce32015-08-20 16:19:38 +000082 private final HashSet<String> mObservedAuthorities = new HashSet<>();
Jeff Sharkey4eb407a2013-08-18 17:38:20 -070083
84 public RootsCache(Context context) {
85 mContext = context;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070086 mObserver = new RootsChangedObserver();
Steve McKay4a1ca862016-02-17 18:25:47 -080087
88 // Create a new anonymous "Recents" RootInfo. It's a faker.
89 mRecentsRoot = new RootInfo() {{
Steve McKay008e9482016-02-18 15:32:16 -080090 // 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 Sharkeya9ce0492013-09-19 15:25:56 -070097 }
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 McKay83df8c02015-09-16 15:07:31 -0700106 if (DEBUG) Log.d(TAG, "Updating roots due to change at " + uri);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700107 updateAuthorityAsync(uri.getAuthority());
108 }
Jeff Sharkey4eb407a2013-08-18 17:38:20 -0700109 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700110
Daichi Hirono60e9a072015-12-25 11:08:42 +0900111 static interface OnCacheUpdateListener {
112 void onCacheUpdate();
113 }
114
Jeff Sharkey66516692013-08-06 11:26:10 -0700115 /**
116 * Gather roots from all known storage providers.
117 */
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700118 public void updateAsync() {
Steve McKay008e9482016-02-18 15:32:16 -0800119 // 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 Sharkeya9ce0492013-09-19 15:25:56 -0700129 new UpdateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
130 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700131
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700132 /**
133 * Gather roots from storage providers belonging to given package name.
134 */
135 public void updatePackageAsync(String packageName) {
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700136 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 McKay83df8c02015-09-16 15:07:31 -0700168 if (DEBUG) Log.d(TAG, "Loading stopped authority " + authority);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700169 mRoots.putAll(authority, loadRootsForAuthority(resolver, authority));
170 }
171 mStoppedAuthorities.clear();
172 }
173 }
174
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900175 /**
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 Mikolajewskidf676dc2016-02-03 15:18:22 +0900182 if (!mStoppedAuthorities.contains(authority)) {
183 return;
184 }
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900185 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 Sharkeya9ce0492013-09-19 15:25:56 -0700193 private class UpdateTask extends AsyncTask<Void, Void, Void> {
194 private final String mFilterPackage;
195
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700196 private final Multimap<String, RootInfo> mTaskRoots = ArrayListMultimap.create();
Steve McKay58efce32015-08-20 16:19:38 +0000197 private final HashSet<String> mTaskStoppedAuthorities = new HashSet<>();
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700198
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700199 /**
200 * Update all roots.
201 */
202 public UpdateTask() {
203 this(null);
Jeff Sharkey66516692013-08-06 11:26:10 -0700204 }
205
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700206 /**
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 Sharkey66516692013-08-06 11:26:10 -0700213
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700214 @Override
215 protected Void doInBackground(Void... params) {
216 final long start = SystemClock.elapsedRealtime();
217
Jeff Sharkeyc9d71502014-09-10 11:23:15 -0700218 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 Sharkey85f5f812013-10-07 10:16:12 -0700224 mTaskRoots.put(mRecentsRoot.authority, mRecentsRoot);
Jeff Sharkey5545f562013-09-21 13:57:33 -0700225
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700226 final ContentResolver resolver = mContext.getContentResolver();
227 final PackageManager pm = mContext.getPackageManager();
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700228
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 Sharkeya9ce0492013-09-19 15:25:56 -0700236 final long delta = SystemClock.elapsedRealtime() - start;
Steve McKay83df8c02015-09-16 15:07:31 -0700237 if (DEBUG)
238 Log.d(TAG, "Update found " + mTaskRoots.size() + " roots in " + delta + "ms");
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700239 synchronized (mLock) {
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700240 mRoots = mTaskRoots;
241 mStoppedAuthorities = mTaskStoppedAuthorities;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700242 }
243 mFirstLoad.countDown();
Jeff Sharkey46de7b52013-10-23 09:59:06 -0700244 resolver.notifyChange(sNotificationUri, null, false);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700245 return null;
246 }
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700247
Daichi Hirono60e9a072015-12-25 11:08:42 +0900248 @Override
249 protected void onPostExecute(Void result) {
250 if (mCacheUpdateListener != null) {
251 mCacheUpdateListener.onCacheUpdate();
252 }
253 }
254
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700255 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 McKay83df8c02015-09-16 15:07:31 -0700259 if (DEBUG) Log.d(TAG, "Ignoring stopped authority " + info.authority);
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700260 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 McKay83df8c02015-09-16 15:07:31 -0700269 if (DEBUG) Log.d(TAG, "Used cached roots for " + info.authority);
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700270 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 Sharkey66516692013-08-06 11:26:10 -0700281 }
282
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700283 /**
284 * Bring up requested provider and query for all active roots.
285 */
286 private Collection<RootInfo> loadRootsForAuthority(ContentResolver resolver, String authority) {
Steve McKay83df8c02015-09-16 15:07:31 -0700287 if (DEBUG) Log.d(TAG, "Loading roots for " + authority);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700288
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 McKay58efce32015-08-20 16:19:38 +0000297 final List<RootInfo> roots = new ArrayList<>();
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700298 final Uri rootsUri = DocumentsContract.buildRootsUri(authority);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700299
300 ContentProviderClient client = null;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700301 Cursor cursor = null;
302 try {
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700303 client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700304 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 Sharkey7aa76012013-09-30 14:26:27 -0700313 ContentProviderClient.releaseQuietly(client);
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700314 }
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 Roote6585b32013-12-13 12:00:26 -0800345 if (Objects.equals(root.rootId, rootId)) {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700346 return root;
347 }
348 }
349 return null;
Jeff Sharkey66516692013-08-06 11:26:10 -0700350 }
351
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700352 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 Roote6585b32013-12-13 12:00:26 -0800358 if (Objects.equals(test.rootId, root.rootId)) {
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700359 continue;
360 }
Jeff Sharkeyf6db1542013-09-13 13:42:19 -0700361 final int testIcon = test.derivedIcon != 0 ? test.derivedIcon : test.icon;
362 if (testIcon == rootIcon) {
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700363 return false;
364 }
365 }
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700366 return true;
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700367 }
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700368 }
369
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700370 public RootInfo getRecentsRoot() {
Jeff Sharkey4eb407a2013-08-18 17:38:20 -0700371 return mRecentsRoot;
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700372 }
373
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700374 public boolean isRecentsRoot(RootInfo root) {
Steve McKay4a1ca862016-02-17 18:25:47 -0800375 return mRecentsRoot.equals(root);
Jeff Sharkey66516692013-08-06 11:26:10 -0700376 }
377
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700378 public Collection<RootInfo> getRootsBlocking() {
379 waitForFirstLoad();
380 loadStoppedAuthorities();
381 synchronized (mLock) {
382 return mRoots.values();
383 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700384 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700385
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700386 public Collection<RootInfo> getMatchingRootsBlocking(State state) {
387 waitForFirstLoad();
388 loadStoppedAuthorities();
389 synchronized (mLock) {
390 return getMatchingRoots(mRoots.values(), state);
391 }
Jeff Sharkey6d97d3c2013-09-06 10:43:45 -0700392 }
393
Tomasz Mikolajewskia6120da2016-01-27 17:36:51 +0900394 /**
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 Hirono60e9a072015-12-25 11:08:42 +0900407 public void setOnCacheUpdateListener(OnCacheUpdateListener cacheUpdateListener) {
408 mCacheUpdateListener = cacheUpdateListener;
409 }
410
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700411 @VisibleForTesting
412 static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) {
Steve McKay58efce32015-08-20 16:19:38 +0000413 final List<RootInfo> matching = new ArrayList<>();
Jeff Sharkey6d97d3c2013-09-06 10:43:45 -0700414 for (RootInfo root : roots) {
Jeff Sharkey348ad682013-09-02 17:19:40 -0700415 // Exclude read-only devices when creating
Steve McKay4a1ca862016-02-17 18:25:47 -0800416 if (state.action == State.ACTION_CREATE && !root.supportsCreate()) continue;
417 if (state.action == State.ACTION_PICK_COPY_DESTINATION
418 && !root.supportsCreate()) continue;
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700419 // Exclude roots that don't support directory picking
Steve McKay4a1ca862016-02-17 18:25:47 -0800420 if (state.action == State.ACTION_OPEN_TREE && !root.supportsChildren()) continue;
Jeff Sharkey348ad682013-09-02 17:19:40 -0700421 // Exclude advanced devices when not requested
Steve McKay4a1ca862016-02-17 18:25:47 -0800422 if (!state.showAdvanced && root.isAdvanced()) continue;
Jeff Sharkey348ad682013-09-02 17:19:40 -0700423 // Exclude non-local devices when local only
Steve McKay4a1ca862016-02-17 18:25:47 -0800424 if (state.localOnly && !root.isLocalOnly()) continue;
Steve McKay008e9482016-02-18 15:32:16 -0800425 // 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 McKay83df8c02015-09-16 15:07:31 -0700429
430 // Only show empty roots when creating, or in browse mode.
Steve McKay4a1ca862016-02-17 18:25:47 -0800431 if (root.isEmpty() && (state.action == State.ACTION_OPEN
Steve McKay0200e342015-09-23 16:16:32 -0700432 || state.action == State.ACTION_GET_CONTENT)) {
Steve McKay83df8c02015-09-16 15:07:31 -0700433 if (DEBUG) Log.i(TAG, "Skipping empty root: " + root);
434 continue;
435 }
Jeff Sharkey348ad682013-09-02 17:19:40 -0700436
Jeff Sharkey923396b2013-09-05 13:55:35 -0700437 // Only include roots that serve requested content
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700438 final boolean overlap =
439 MimePredicate.mimeMatches(root.derivedMimeTypes, state.acceptMimes) ||
440 MimePredicate.mimeMatches(state.acceptMimes, root.derivedMimeTypes);
Jeff Sharkey6d97d3c2013-09-06 10:43:45 -0700441 if (!overlap) {
Jeff Sharkey923396b2013-09-05 13:55:35 -0700442 continue;
Jeff Sharkey348ad682013-09-02 17:19:40 -0700443 }
444
Ben Kwa77797402015-05-29 15:40:31 -0700445 // Exclude roots from the calling package.
446 if (state.excludedAuthorities.contains(root.authority)) {
Steve McKay4a1ca862016-02-17 18:25:47 -0800447 if (DEBUG) Log.d(
448 TAG, "Excluding root " + root.authority + " from calling package.");
Ben Kwa77797402015-05-29 15:40:31 -0700449 continue;
450 }
451
Steve McKay4a1ca862016-02-17 18:25:47 -0800452 if (DEBUG) Log.d(
453 TAG, "Including root " + root + " in roots list.");
Jeff Sharkey348ad682013-09-02 17:19:40 -0700454 matching.add(root);
455 }
456 return matching;
457 }
Jeff Sharkey66516692013-08-06 11:26:10 -0700458}