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.roots; 18 19 import static android.provider.DocumentsContract.QUERY_ARG_MIME_TYPES; 20 21 import static androidx.core.util.Preconditions.checkNotNull; 22 23 import static com.android.documentsui.base.SharedMinimal.DEBUG; 24 import static com.android.documentsui.base.SharedMinimal.VERBOSE; 25 26 import android.content.BroadcastReceiver.PendingResult; 27 import android.content.ContentProviderClient; 28 import android.content.ContentResolver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.pm.ApplicationInfo; 32 import android.content.pm.PackageManager; 33 import android.content.pm.ProviderInfo; 34 import android.content.pm.ResolveInfo; 35 import android.database.ContentObserver; 36 import android.database.Cursor; 37 import android.net.Uri; 38 import android.os.AsyncTask; 39 import android.os.Bundle; 40 import android.os.FileUtils; 41 import android.os.Handler; 42 import android.os.Looper; 43 import android.os.SystemClock; 44 import android.provider.DocumentsContract; 45 import android.provider.DocumentsContract.Root; 46 import android.util.Log; 47 48 import androidx.annotation.GuardedBy; 49 import androidx.annotation.Nullable; 50 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 51 52 import com.android.documentsui.DocumentsApplication; 53 import com.android.documentsui.R; 54 import com.android.documentsui.UserPackage; 55 import com.android.documentsui.archives.ArchivesProvider; 56 import com.android.documentsui.base.LookupApplicationName; 57 import com.android.documentsui.base.Providers; 58 import com.android.documentsui.base.RootInfo; 59 import com.android.documentsui.base.State; 60 import com.android.documentsui.base.UserId; 61 import com.android.modules.utils.build.SdkLevel; 62 63 import com.google.common.collect.ArrayListMultimap; 64 import com.google.common.collect.Multimap; 65 66 import java.util.ArrayList; 67 import java.util.Collection; 68 import java.util.Collections; 69 import java.util.HashMap; 70 import java.util.HashSet; 71 import java.util.List; 72 import java.util.Map; 73 import java.util.Objects; 74 import java.util.concurrent.CountDownLatch; 75 import java.util.concurrent.ExecutorService; 76 import java.util.concurrent.Executors; 77 import java.util.concurrent.Semaphore; 78 import java.util.concurrent.TimeUnit; 79 import java.util.function.Function; 80 81 /** 82 * Cache of known storage backends and their roots. 83 */ 84 public class ProvidersCache implements ProvidersAccess, LookupApplicationName { 85 private static final String TAG = "ProvidersCache"; 86 87 // Not all providers are equally well written. If a provider returns 88 // empty results we don't cache them...unless they're in this magical list 89 // of beloved providers. 90 private static final List<String> PERMIT_EMPTY_CACHE = List.of( 91 // MTP provider commonly returns no roots (if no devices are attached). 92 Providers.AUTHORITY_MTP, 93 // ArchivesProvider doesn't support any roots. 94 ArchivesProvider.AUTHORITY); 95 private static final int FIRST_LOAD_TIMEOUT_MS = 5000; 96 private static final int NUM_THREADS = 10; 97 private static final ExecutorService ASYNC_TASKS_THREAD_POOL = 98 Executors.newFixedThreadPool(NUM_THREADS); 99 100 private final Context mContext; 101 102 @GuardedBy("mRootsChangedObservers") 103 private final Map<UserId, RootsChangedObserver> mRootsChangedObservers = new HashMap<>(); 104 105 @GuardedBy("mRecentsRoots") 106 private final Map<UserId, RootInfo> mRecentsRoots = new HashMap<>(); 107 108 private final Object mLock = new Object(); 109 private final CountDownLatch mFirstLoad = new CountDownLatch(1); 110 111 @GuardedBy("mLock") 112 private boolean mFirstLoadDone; 113 @GuardedBy("mLock") 114 private PendingResult mBootCompletedResult; 115 116 @GuardedBy("mLock") 117 private Multimap<UserAuthority, RootInfo> mRoots = ArrayListMultimap.create(); 118 @GuardedBy("mLock") 119 private HashSet<UserAuthority> mStoppedAuthorities = new HashSet<>(); 120 private final Semaphore mMultiProviderUpdateTaskSemaphore = new Semaphore(1); 121 122 @GuardedBy("mObservedAuthoritiesDetails") 123 private final Map<UserAuthority, PackageDetails> mObservedAuthoritiesDetails = new HashMap<>(); 124 ProvidersCache(Context context)125 public ProvidersCache(Context context) { 126 mContext = context; 127 } 128 129 /** 130 * Generates recent root for the provided user id 131 */ generateRecentsRoot(UserId rootUserId)132 private RootInfo generateRecentsRoot(UserId rootUserId) { 133 return new RootInfo() {{ 134 // Special root for recents 135 userId = rootUserId; 136 derivedIcon = R.drawable.ic_root_recent; 137 derivedType = RootInfo.TYPE_RECENTS; 138 flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_SEARCH; 139 queryArgs = QUERY_ARG_MIME_TYPES; 140 title = mContext.getString(R.string.root_recent); 141 availableBytes = -1; 142 }}; 143 } 144 145 private RootInfo createOrGetRecentsRoot(UserId userId) { 146 return createOrGetByUserId(mRecentsRoots, userId, user -> generateRecentsRoot(user)); 147 } 148 149 private RootsChangedObserver createOrGetRootsChangedObserver(UserId userId) { 150 return createOrGetByUserId(mRootsChangedObservers, userId, 151 user -> new RootsChangedObserver(user)); 152 } 153 154 private static <T> T createOrGetByUserId(Map<UserId, T> map, UserId userId, 155 Function<UserId, T> supplier) { 156 synchronized (map) { 157 if (!map.containsKey(userId)) { 158 map.put(userId, supplier.apply(userId)); 159 } 160 } 161 return map.get(userId); 162 } 163 164 private class RootsChangedObserver extends ContentObserver { 165 166 private final UserId mUserId; 167 168 RootsChangedObserver(UserId userId) { 169 super(new Handler(Looper.getMainLooper())); 170 mUserId = userId; 171 } 172 173 @Override 174 public void onChange(boolean selfChange, Uri uri) { 175 if (uri == null) { 176 Log.w(TAG, "Received onChange event for null uri. Skipping."); 177 return; 178 } 179 if (DEBUG) { 180 Log.i(TAG, "Updating roots due to change on user " + mUserId + "at " + uri); 181 } 182 updateAuthorityAsync(mUserId, uri.getAuthority()); 183 } 184 } 185 186 @Override 187 public String getApplicationName(UserId userId, String authority) { 188 return mObservedAuthoritiesDetails.get( 189 new UserAuthority(userId, authority)).applicationName; 190 } 191 192 @Override 193 public String getPackageName(UserId userId, String authority) { 194 return mObservedAuthoritiesDetails.get(new UserAuthority(userId, authority)).packageName; 195 } 196 197 public void updateAsync(boolean forceRefreshAll, @Nullable Runnable callback) { 198 199 // NOTE: This method is called when the UI language changes. 200 // For that reason we update our RecentsRoot to reflect 201 // the current language. 202 final String title = mContext.getString(R.string.root_recent); 203 List<UserId> userIds = new ArrayList<>(getUserIds()); 204 for (UserId userId : userIds) { 205 RootInfo recentRoot = createOrGetRecentsRoot(userId); 206 recentRoot.title = title; 207 // Nothing else about the root should ever change. 208 assert (recentRoot.authority == null); 209 assert (recentRoot.rootId == null); 210 assert (recentRoot.derivedIcon == R.drawable.ic_root_recent); 211 assert (recentRoot.derivedType == RootInfo.TYPE_RECENTS); 212 assert (recentRoot.flags == (Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD)); 213 assert (recentRoot.availableBytes == -1); 214 } 215 216 new MultiProviderUpdateTask(forceRefreshAll, null, callback).executeOnExecutor( 217 AsyncTask.THREAD_POOL_EXECUTOR); 218 } 219 220 public void updatePackageAsync(UserId userId, String packageName) { 221 new MultiProviderUpdateTask( 222 /* forceRefreshAll= */ false, 223 new UserPackage(userId, packageName), 224 /* callback= */ null) 225 .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 226 } 227 228 public void updateAuthorityAsync(UserId userId, String authority) { 229 final ProviderInfo info = userId.getPackageManager(mContext).resolveContentProvider( 230 authority, 0); 231 if (info != null) { 232 updatePackageAsync(userId, info.packageName); 233 } 234 } 235 236 void setBootCompletedResult(PendingResult result) { 237 synchronized (mLock) { 238 // Quickly check if we've already finished loading, otherwise hang 239 // out until first pass is finished. 240 if (mFirstLoadDone) { 241 result.finish(); 242 } else { 243 mBootCompletedResult = result; 244 } 245 } 246 } 247 248 /** 249 * Block until the first {@link MultiProviderUpdateTask} pass has finished. 250 * 251 * @return {@code true} if cached roots is ready to roll, otherwise 252 * {@code false} if we timed out while waiting. 253 */ 254 private boolean waitForFirstLoad() { 255 boolean success = false; 256 try { 257 success = mFirstLoad.await(FIRST_LOAD_TIMEOUT_MS, TimeUnit.MILLISECONDS); 258 } catch (InterruptedException e) { 259 } 260 if (!success) { 261 Log.w(TAG, "Timeout waiting for first update"); 262 } 263 return success; 264 } 265 266 /** 267 * Load roots from authorities that are in stopped state. Normal 268 * {@link MultiProviderUpdateTask} passes ignore stopped applications. 269 */ 270 private void loadStoppedAuthorities() { 271 synchronized (mLock) { 272 for (UserAuthority userAuthority : mStoppedAuthorities) { 273 mRoots.replaceValues(userAuthority, loadRootsForAuthority(userAuthority, true)); 274 } 275 mStoppedAuthorities.clear(); 276 } 277 } 278 279 /** 280 * Load roots from a stopped authority. Normal {@link MultiProviderUpdateTask} passes 281 * ignore stopped applications. 282 */ 283 private void loadStoppedAuthority(UserAuthority userAuthority) { 284 synchronized (mLock) { 285 if (!mStoppedAuthorities.contains(userAuthority)) { 286 return; 287 } 288 if (DEBUG) { 289 Log.d(TAG, "Loading stopped authority " + userAuthority); 290 } 291 mRoots.replaceValues(userAuthority, loadRootsForAuthority(userAuthority, true)); 292 mStoppedAuthorities.remove(userAuthority); 293 } 294 } 295 296 /** 297 * Bring up requested provider and query for all active roots. Will consult cached 298 * roots if not forceRefresh. Will query when cached roots is empty (which should never happen). 299 */ 300 private Collection<RootInfo> loadRootsForAuthority(UserAuthority userAuthority, 301 boolean forceRefresh) { 302 UserId userId = userAuthority.userId; 303 String authority = userAuthority.authority; 304 if (VERBOSE) Log.v(TAG, "Loading roots on user " + userId + " for " + authority); 305 306 ContentResolver resolver = userId.getContentResolver(mContext); 307 final ArrayList<RootInfo> roots = new ArrayList<>(); 308 final PackageManager pm = userId.getPackageManager(mContext); 309 ProviderInfo provider = pm.resolveContentProvider( 310 authority, PackageManager.GET_META_DATA); 311 if (provider == null) { 312 Log.w(TAG, "Failed to get provider info for " + authority); 313 return roots; 314 } 315 if (!provider.exported) { 316 Log.w(TAG, "Provider is not exported. Failed to load roots for " + authority); 317 return roots; 318 } 319 if (!provider.grantUriPermissions) { 320 Log.w(TAG, "Provider doesn't grantUriPermissions. Failed to load roots for " 321 + authority); 322 return roots; 323 } 324 if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(provider.readPermission) 325 || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(provider.writePermission)) { 326 Log.w(TAG, "Provider is not protected by MANAGE_DOCUMENTS. Failed to load roots for " 327 + authority); 328 return roots; 329 } 330 331 synchronized (mObservedAuthoritiesDetails) { 332 if (!mObservedAuthoritiesDetails.containsKey(userAuthority)) { 333 CharSequence appName = pm.getApplicationLabel(provider.applicationInfo); 334 String packageName = provider.applicationInfo.packageName; 335 336 mObservedAuthoritiesDetails.put( 337 userAuthority, new PackageDetails(appName.toString(), packageName)); 338 339 // Watch for any future updates 340 final Uri rootsUri = DocumentsContract.buildRootsUri(authority); 341 resolver.registerContentObserver(rootsUri, true, 342 createOrGetRootsChangedObserver(userId)); 343 } 344 } 345 346 final Uri rootsUri = DocumentsContract.buildRootsUri(authority); 347 if (!forceRefresh) { 348 // Look for roots data that we might have cached for ourselves in the 349 // long-lived system process. 350 final Bundle systemCache = resolver.getCache(rootsUri); 351 if (systemCache != null) { 352 ArrayList<RootInfo> cachedRoots = systemCache.getParcelableArrayList(TAG); 353 assert (cachedRoots != null); 354 if (!cachedRoots.isEmpty() || PERMIT_EMPTY_CACHE.contains(authority)) { 355 if (VERBOSE) Log.v(TAG, "System cache hit for " + authority); 356 return cachedRoots; 357 } else { 358 Log.w(TAG, "Ignoring empty system cache hit for " + authority); 359 } 360 } 361 } 362 363 ContentProviderClient client = null; 364 Cursor cursor = null; 365 try { 366 client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority); 367 cursor = client.query(rootsUri, null, null, null, null); 368 while (cursor.moveToNext()) { 369 final RootInfo root = RootInfo.fromRootsCursor(userId, authority, cursor); 370 roots.add(root); 371 } 372 } catch (Exception e) { 373 Log.w(TAG, "Failed to load some roots from " + authority, e); 374 // We didn't load every root from the provider. Don't put it to 375 // system cache so that we'll try loading them again next time even 376 // if forceRefresh is false. 377 return roots; 378 } finally { 379 FileUtils.closeQuietly(cursor); 380 FileUtils.closeQuietly(client); 381 } 382 383 // Cache these freshly parsed roots over in the long-lived system 384 // process, in case our process goes away. The system takes care of 385 // invalidating the cache if the package or Uri changes. 386 final Bundle systemCache = new Bundle(); 387 if (roots.isEmpty() && !PERMIT_EMPTY_CACHE.contains(authority)) { 388 Log.i(TAG, "Provider returned no roots. Possibly naughty: " + authority); 389 } else { 390 systemCache.putParcelableArrayList(TAG, roots); 391 resolver.putCache(rootsUri, systemCache); 392 } 393 394 return roots; 395 } 396 397 @Override 398 public RootInfo getRootOneshot(UserId userId, String authority, String rootId) { 399 return getRootOneshot(userId, authority, rootId, false); 400 } 401 402 public RootInfo getRootOneshot(UserId userId, String authority, String rootId, 403 boolean forceRefresh) { 404 synchronized (mLock) { 405 UserAuthority userAuthority = new UserAuthority(userId, authority); 406 RootInfo root = forceRefresh ? null : getRootLocked(userAuthority, rootId); 407 if (root == null) { 408 mRoots.replaceValues(userAuthority, 409 loadRootsForAuthority(userAuthority, forceRefresh)); 410 root = getRootLocked(userAuthority, rootId); 411 } 412 return root; 413 } 414 } 415 416 public RootInfo getRootBlocking(UserId userId, String authority, String rootId) { 417 waitForFirstLoad(); 418 loadStoppedAuthorities(); 419 synchronized (mLock) { 420 return getRootLocked(new UserAuthority(userId, authority), rootId); 421 } 422 } 423 424 private RootInfo getRootLocked(UserAuthority userAuthority, String rootId) { 425 for (RootInfo root : mRoots.get(userAuthority)) { 426 if (Objects.equals(root.rootId, rootId)) { 427 return root; 428 } 429 } 430 return null; 431 } 432 433 @Override 434 public RootInfo getRecentsRoot(UserId userId) { 435 return createOrGetRecentsRoot(userId); 436 } 437 438 public boolean isRecentsRoot(RootInfo root) { 439 return mRecentsRoots.containsValue(root); 440 } 441 442 @Override 443 public Collection<RootInfo> getRootsBlocking() { 444 waitForFirstLoad(); 445 loadStoppedAuthorities(); 446 synchronized (mLock) { 447 return new HashSet<>(mRoots.values()); 448 } 449 } 450 451 @Override 452 public Collection<RootInfo> getMatchingRootsBlocking(State state) { 453 waitForFirstLoad(); 454 loadStoppedAuthorities(); 455 synchronized (mLock) { 456 return ProvidersAccess.getMatchingRoots(mRoots.values(), state); 457 } 458 } 459 460 @Override 461 public Collection<RootInfo> getRootsForAuthorityBlocking(UserId userId, String authority) { 462 waitForFirstLoad(); 463 UserAuthority userAuthority = new UserAuthority(userId, authority); 464 loadStoppedAuthority(userAuthority); 465 synchronized (mLock) { 466 final Collection<RootInfo> roots = mRoots.get(userAuthority); 467 return roots != null ? roots : Collections.<RootInfo>emptyList(); 468 } 469 } 470 471 @Override 472 public RootInfo getDefaultRootBlocking(State state) { 473 RootInfo root = ProvidersAccess.getDefaultRoot(getRootsBlocking(), state); 474 return root != null ? root : createOrGetRecentsRoot(UserId.CURRENT_USER); 475 } 476 477 public void logCache() { 478 StringBuilder output = new StringBuilder(); 479 480 for (UserAuthority userAuthority : mObservedAuthoritiesDetails.keySet()) { 481 List<String> roots = new ArrayList<>(); 482 Uri rootsUri = DocumentsContract.buildRootsUri(userAuthority.authority); 483 Bundle systemCache = userAuthority.userId.getContentResolver(mContext).getCache( 484 rootsUri); 485 if (systemCache != null) { 486 ArrayList<RootInfo> cachedRoots = systemCache.getParcelableArrayList(TAG); 487 for (RootInfo root : cachedRoots) { 488 roots.add(root.toDebugString()); 489 } 490 } 491 492 output.append((output.length() == 0) ? "System cache: " : ", "); 493 output.append(userAuthority).append("=").append(roots); 494 } 495 496 Log.i(TAG, output.toString()); 497 } 498 499 private class MultiProviderUpdateTask extends AsyncTask<Void, Void, Void> { 500 private final boolean mForceRefreshAll; 501 @Nullable 502 private final UserPackage mForceRefreshUserPackage; 503 @Nullable 504 private final Runnable mCallback; 505 506 @GuardedBy("mLock") 507 private Multimap<UserAuthority, RootInfo> mLocalRoots = ArrayListMultimap.create(); 508 @GuardedBy("mLock") 509 private HashSet<UserAuthority> mLocalStoppedAuthorities = new HashSet<>(); 510 511 /** 512 * Create task to update roots cache. 513 * 514 * @param forceRefreshAll when true, all previously cached values for 515 * all packages should be ignored. 516 * @param forceRefreshUserPackage when non-null, all previously cached 517 * values for this specific user package should be ignored. 518 * @param callback when non-null, it will be invoked after the task is 519 * executed. 520 */ 521 MultiProviderUpdateTask( 522 boolean forceRefreshAll, 523 @Nullable UserPackage forceRefreshUserPackage, 524 @Nullable Runnable callback) { 525 mForceRefreshAll = forceRefreshAll; 526 mForceRefreshUserPackage = forceRefreshUserPackage; 527 mCallback = callback; 528 } 529 530 @Override 531 protected Void doInBackground(Void... params) { 532 if (!mMultiProviderUpdateTaskSemaphore.tryAcquire()) { 533 // Abort, since previous update task is still running. 534 return null; 535 } 536 537 int previousPriority = Thread.currentThread().getPriority(); 538 Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 539 540 final long start = SystemClock.elapsedRealtime(); 541 542 List<UserId> userIds = new ArrayList<>(getUserIds()); 543 for (UserId userId : userIds) { 544 final RootInfo recents = createOrGetRecentsRoot(userId); 545 synchronized (mLock) { 546 mLocalRoots.put(new UserAuthority(recents.userId, recents.authority), recents); 547 } 548 } 549 550 List<SingleProviderUpdateTaskInfo> taskInfos = new ArrayList<>(); 551 for (UserId userId : userIds) { 552 final PackageManager pm = userId.getPackageManager(mContext); 553 // Pick up provider with action string 554 final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); 555 final List<ResolveInfo> providers = pm.queryIntentContentProviders(intent, 0); 556 for (ResolveInfo info : providers) { 557 ProviderInfo providerInfo = info.providerInfo; 558 if (providerInfo.authority != null) { 559 taskInfos.add(new SingleProviderUpdateTaskInfo(providerInfo, userId)); 560 } 561 } 562 } 563 564 if (!taskInfos.isEmpty()) { 565 CountDownLatch updateTaskInternalCountDown = new CountDownLatch(taskInfos.size()); 566 ExecutorService executor = ASYNC_TASKS_THREAD_POOL; 567 for (SingleProviderUpdateTaskInfo taskInfo : taskInfos) { 568 executor.submit(() -> 569 startSingleProviderUpdateTask( 570 taskInfo.providerInfo, 571 taskInfo.userId, 572 updateTaskInternalCountDown)); 573 } 574 575 // Block until all SingleProviderUpdateTask threads finish executing. 576 // Use a shorter timeout for first load since it could block picker UI. 577 long timeoutMs = mFirstLoadDone ? 15000 : FIRST_LOAD_TIMEOUT_MS; 578 boolean success = false; 579 try { 580 success = updateTaskInternalCountDown.await(timeoutMs, TimeUnit.MILLISECONDS); 581 } catch (InterruptedException e) { 582 } 583 if (!success) { 584 Log.w(TAG, "Timeout executing update task!"); 585 } 586 } 587 588 final long delta = SystemClock.elapsedRealtime() - start; 589 synchronized (mLock) { 590 mFirstLoadDone = true; 591 if (mBootCompletedResult != null) { 592 mBootCompletedResult.finish(); 593 mBootCompletedResult = null; 594 } 595 mRoots = mLocalRoots; 596 mStoppedAuthorities = mLocalStoppedAuthorities; 597 } 598 if (VERBOSE) { 599 Log.v(TAG, "Update found " + mLocalRoots.size() + " roots in " + delta + "ms"); 600 } 601 602 mFirstLoad.countDown(); 603 LocalBroadcastManager.getInstance(mContext).sendBroadcast(new Intent(BROADCAST_ACTION)); 604 mMultiProviderUpdateTaskSemaphore.release(); 605 606 Thread.currentThread().setPriority(previousPriority); 607 return null; 608 } 609 610 @Override 611 protected void onPostExecute(Void aVoid) { 612 if (mCallback != null) { 613 mCallback.run(); 614 } 615 } 616 617 private void startSingleProviderUpdateTask( 618 ProviderInfo providerInfo, 619 UserId userId, 620 CountDownLatch updateCountDown) { 621 int previousPriority = Thread.currentThread().getPriority(); 622 Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 623 handleDocumentsProvider(providerInfo, userId); 624 updateCountDown.countDown(); 625 Thread.currentThread().setPriority(previousPriority); 626 } 627 628 private void handleDocumentsProvider(ProviderInfo info, UserId userId) { 629 UserAuthority userAuthority = new UserAuthority(userId, info.authority); 630 // Ignore stopped packages for now; we might query them 631 // later during UI interaction. 632 if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) { 633 if (VERBOSE) { 634 Log.v(TAG, "Ignoring stopped authority " + info.authority + ", user " + userId); 635 } 636 synchronized (mLock) { 637 mLocalStoppedAuthorities.add(userAuthority); 638 } 639 return; 640 } 641 642 final boolean forceRefresh = mForceRefreshAll 643 || Objects.equals( 644 new UserPackage(userId, info.packageName), mForceRefreshUserPackage); 645 synchronized (mLock) { 646 mLocalRoots.putAll(userAuthority, 647 loadRootsForAuthority(userAuthority, forceRefresh)); 648 } 649 } 650 } 651 652 private static class UserAuthority { 653 private final UserId userId; 654 @Nullable 655 private final String authority; 656 657 private UserAuthority(UserId userId, @Nullable String authority) { 658 this.userId = checkNotNull(userId); 659 this.authority = authority; 660 } 661 662 @Override 663 public boolean equals(Object o) { 664 if (o == null) { 665 return false; 666 } 667 668 if (this == o) { 669 return true; 670 } 671 672 if (o instanceof UserAuthority) { 673 UserAuthority other = (UserAuthority) o; 674 return Objects.equals(userId, other.userId) 675 && Objects.equals(authority, other.authority); 676 } 677 678 return false; 679 } 680 681 682 @Override 683 public int hashCode() { 684 return Objects.hash(userId, authority); 685 } 686 } 687 688 private static class SingleProviderUpdateTaskInfo { 689 private final ProviderInfo providerInfo; 690 private final UserId userId; 691 692 SingleProviderUpdateTaskInfo(ProviderInfo providerInfo, UserId userId) { 693 this.providerInfo = providerInfo; 694 this.userId = userId; 695 } 696 } 697 698 private static class PackageDetails { 699 private String applicationName; 700 private String packageName; 701 702 public PackageDetails(String appName, String pckgName) { 703 applicationName = appName; 704 packageName = pckgName; 705 } 706 } 707 708 private List<UserId> getUserIds() { 709 if (DocumentsApplication.getConfigStore().isPrivateSpaceInDocsUIEnabled() 710 && SdkLevel.isAtLeastS()) { 711 return DocumentsApplication.getUserManagerState(mContext).getUserIds(); 712 } 713 return DocumentsApplication.getUserIdManager(mContext).getUserIds(); 714 } 715 } 716