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 import com.google.common.util.concurrent.MoreExecutors; 66 67 import java.util.ArrayList; 68 import java.util.Collection; 69 import java.util.Collections; 70 import java.util.HashMap; 71 import java.util.HashSet; 72 import java.util.List; 73 import java.util.Map; 74 import java.util.Objects; 75 import java.util.concurrent.CountDownLatch; 76 import java.util.concurrent.ExecutorService; 77 import java.util.concurrent.Executors; 78 import java.util.concurrent.Semaphore; 79 import java.util.concurrent.ThreadPoolExecutor; 80 import java.util.concurrent.TimeUnit; 81 import java.util.function.Function; 82 83 /** 84 * Cache of known storage backends and their roots. 85 */ 86 public class ProvidersCache implements ProvidersAccess, LookupApplicationName { 87 private static final String TAG = "ProvidersCache"; 88 89 // Not all providers are equally well written. If a provider returns 90 // empty results we don't cache them...unless they're in this magical list 91 // of beloved providers. 92 private static final List<String> PERMIT_EMPTY_CACHE = List.of( 93 // MTP provider commonly returns no roots (if no devices are attached). 94 Providers.AUTHORITY_MTP, 95 // ArchivesProvider doesn't support any roots. 96 ArchivesProvider.AUTHORITY); 97 private static final int FIRST_LOAD_TIMEOUT_MS = 5000; 98 99 private final Context mContext; 100 101 @GuardedBy("mRootsChangedObservers") 102 private final Map<UserId, RootsChangedObserver> mRootsChangedObservers = new HashMap<>(); 103 104 @GuardedBy("mRecentsRoots") 105 private final Map<UserId, RootInfo> mRecentsRoots = new HashMap<>(); 106 107 private final Object mLock = new Object(); 108 private final CountDownLatch mFirstLoad = new CountDownLatch(1); 109 110 @GuardedBy("mLock") 111 private boolean mFirstLoadDone; 112 @GuardedBy("mLock") 113 private PendingResult mBootCompletedResult; 114 115 @GuardedBy("mLock") 116 private Multimap<UserAuthority, RootInfo> mRoots = ArrayListMultimap.create(); 117 @GuardedBy("mLock") 118 private HashSet<UserAuthority> mStoppedAuthorities = new HashSet<>(); 119 private final Semaphore mMultiProviderUpdateTaskSemaphore = new Semaphore(1); 120 121 @GuardedBy("mObservedAuthoritiesDetails") 122 private final Map<UserAuthority, PackageDetails> mObservedAuthoritiesDetails = new HashMap<>(); 123 ProvidersCache(Context context)124 public ProvidersCache(Context context) { 125 mContext = context; 126 } 127 128 /** 129 * Generates recent root for the provided user id 130 */ generateRecentsRoot(UserId rootUserId)131 private RootInfo generateRecentsRoot(UserId rootUserId) { 132 return new RootInfo() {{ 133 // Special root for recents 134 userId = rootUserId; 135 derivedIcon = R.drawable.ic_root_recent; 136 derivedType = RootInfo.TYPE_RECENTS; 137 flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_SEARCH; 138 queryArgs = QUERY_ARG_MIME_TYPES; 139 title = mContext.getString(R.string.root_recent); 140 availableBytes = -1; 141 }}; 142 } 143 144 private RootInfo createOrGetRecentsRoot(UserId userId) { 145 return createOrGetByUserId(mRecentsRoots, userId, user -> generateRecentsRoot(user)); 146 } 147 148 private RootsChangedObserver createOrGetRootsChangedObserver(UserId userId) { 149 return createOrGetByUserId(mRootsChangedObservers, userId, 150 user -> new RootsChangedObserver(user)); 151 } 152 153 private static <T> T createOrGetByUserId(Map<UserId, T> map, UserId userId, 154 Function<UserId, T> supplier) { 155 synchronized (map) { 156 if (!map.containsKey(userId)) { 157 map.put(userId, supplier.apply(userId)); 158 } 159 } 160 return map.get(userId); 161 } 162 163 private class RootsChangedObserver extends ContentObserver { 164 165 private final UserId mUserId; 166 167 RootsChangedObserver(UserId userId) { 168 super(new Handler(Looper.getMainLooper())); 169 mUserId = userId; 170 } 171 172 @Override 173 public void onChange(boolean selfChange, Uri uri) { 174 if (uri == null) { 175 Log.w(TAG, "Received onChange event for null uri. Skipping."); 176 return; 177 } 178 if (DEBUG) { 179 Log.i(TAG, "Updating roots due to change on user " + mUserId + "at " + uri); 180 } 181 updateAuthorityAsync(mUserId, uri.getAuthority()); 182 } 183 } 184 185 @Override 186 public String getApplicationName(UserId userId, String authority) { 187 return mObservedAuthoritiesDetails.get( 188 new UserAuthority(userId, authority)).applicationName; 189 } 190 191 @Override 192 public String getPackageName(UserId userId, String authority) { 193 return mObservedAuthoritiesDetails.get(new UserAuthority(userId, authority)).packageName; 194 } 195 196 public void updateAsync(boolean forceRefreshAll, @Nullable Runnable callback) { 197 198 // NOTE: This method is called when the UI language changes. 199 // For that reason we update our RecentsRoot to reflect 200 // the current language. 201 final String title = mContext.getString(R.string.root_recent); 202 List<UserId> userIds = new ArrayList<>(getUserIds()); 203 for (UserId userId : userIds) { 204 RootInfo recentRoot = createOrGetRecentsRoot(userId); 205 recentRoot.title = title; 206 // Nothing else about the root should ever change. 207 assert (recentRoot.authority == null); 208 assert (recentRoot.rootId == null); 209 assert (recentRoot.derivedIcon == R.drawable.ic_root_recent); 210 assert (recentRoot.derivedType == RootInfo.TYPE_RECENTS); 211 assert (recentRoot.flags == (Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD)); 212 assert (recentRoot.availableBytes == -1); 213 } 214 215 new MultiProviderUpdateTask(forceRefreshAll, null, callback).executeOnExecutor( 216 AsyncTask.THREAD_POOL_EXECUTOR); 217 } 218 219 public void updatePackageAsync(UserId userId, String packageName) { 220 new MultiProviderUpdateTask( 221 /* forceRefreshAll= */ false, 222 new UserPackage(userId, packageName), 223 /* callback= */ null) 224 .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 225 } 226 227 public void updateAuthorityAsync(UserId userId, String authority) { 228 final ProviderInfo info = userId.getPackageManager(mContext).resolveContentProvider( 229 authority, 0); 230 if (info != null) { 231 updatePackageAsync(userId, info.packageName); 232 } 233 } 234 235 void setBootCompletedResult(PendingResult result) { 236 synchronized (mLock) { 237 // Quickly check if we've already finished loading, otherwise hang 238 // out until first pass is finished. 239 if (mFirstLoadDone) { 240 result.finish(); 241 } else { 242 mBootCompletedResult = result; 243 } 244 } 245 } 246 247 /** 248 * Block until the first {@link MultiProviderUpdateTask} pass has finished. 249 * 250 * @return {@code true} if cached roots is ready to roll, otherwise 251 * {@code false} if we timed out while waiting. 252 */ 253 private boolean waitForFirstLoad() { 254 boolean success = false; 255 try { 256 success = mFirstLoad.await(FIRST_LOAD_TIMEOUT_MS, TimeUnit.MILLISECONDS); 257 } catch (InterruptedException e) { 258 } 259 if (!success) { 260 Log.w(TAG, "Timeout waiting for first update"); 261 } 262 return success; 263 } 264 265 /** 266 * Load roots from authorities that are in stopped state. Normal 267 * {@link MultiProviderUpdateTask} passes ignore stopped applications. 268 */ 269 private void loadStoppedAuthorities() { 270 synchronized (mLock) { 271 for (UserAuthority userAuthority : mStoppedAuthorities) { 272 mRoots.replaceValues(userAuthority, loadRootsForAuthority(userAuthority, true)); 273 } 274 mStoppedAuthorities.clear(); 275 } 276 } 277 278 /** 279 * Load roots from a stopped authority. Normal {@link MultiProviderUpdateTask} passes 280 * ignore stopped applications. 281 */ 282 private void loadStoppedAuthority(UserAuthority userAuthority) { 283 synchronized (mLock) { 284 if (!mStoppedAuthorities.contains(userAuthority)) { 285 return; 286 } 287 if (DEBUG) { 288 Log.d(TAG, "Loading stopped authority " + userAuthority); 289 } 290 mRoots.replaceValues(userAuthority, loadRootsForAuthority(userAuthority, true)); 291 mStoppedAuthorities.remove(userAuthority); 292 } 293 } 294 295 /** 296 * Bring up requested provider and query for all active roots. Will consult cached 297 * roots if not forceRefresh. Will query when cached roots is empty (which should never happen). 298 */ 299 private Collection<RootInfo> loadRootsForAuthority(UserAuthority userAuthority, 300 boolean forceRefresh) { 301 UserId userId = userAuthority.userId; 302 String authority = userAuthority.authority; 303 if (VERBOSE) Log.v(TAG, "Loading roots on user " + userId + " for " + authority); 304 305 ContentResolver resolver = userId.getContentResolver(mContext); 306 final ArrayList<RootInfo> roots = new ArrayList<>(); 307 final PackageManager pm = userId.getPackageManager(mContext); 308 ProviderInfo provider = pm.resolveContentProvider( 309 authority, PackageManager.GET_META_DATA); 310 if (provider == null) { 311 Log.w(TAG, "Failed to get provider info for " + authority); 312 return roots; 313 } 314 if (!provider.exported) { 315 Log.w(TAG, "Provider is not exported. Failed to load roots for " + authority); 316 return roots; 317 } 318 if (!provider.grantUriPermissions) { 319 Log.w(TAG, "Provider doesn't grantUriPermissions. Failed to load roots for " 320 + authority); 321 return roots; 322 } 323 if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(provider.readPermission) 324 || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(provider.writePermission)) { 325 Log.w(TAG, "Provider is not protected by MANAGE_DOCUMENTS. Failed to load roots for " 326 + authority); 327 return roots; 328 } 329 330 synchronized (mObservedAuthoritiesDetails) { 331 if (!mObservedAuthoritiesDetails.containsKey(userAuthority)) { 332 CharSequence appName = pm.getApplicationLabel(provider.applicationInfo); 333 String packageName = provider.applicationInfo.packageName; 334 335 mObservedAuthoritiesDetails.put( 336 userAuthority, new PackageDetails(appName.toString(), packageName)); 337 338 // Watch for any future updates 339 final Uri rootsUri = DocumentsContract.buildRootsUri(authority); 340 resolver.registerContentObserver(rootsUri, true, 341 createOrGetRootsChangedObserver(userId)); 342 } 343 } 344 345 final Uri rootsUri = DocumentsContract.buildRootsUri(authority); 346 if (!forceRefresh) { 347 // Look for roots data that we might have cached for ourselves in the 348 // long-lived system process. 349 final Bundle systemCache = resolver.getCache(rootsUri); 350 if (systemCache != null) { 351 ArrayList<RootInfo> cachedRoots = systemCache.getParcelableArrayList(TAG); 352 assert (cachedRoots != null); 353 if (!cachedRoots.isEmpty() || PERMIT_EMPTY_CACHE.contains(authority)) { 354 if (VERBOSE) Log.v(TAG, "System cache hit for " + authority); 355 return cachedRoots; 356 } else { 357 Log.w(TAG, "Ignoring empty system cache hit for " + authority); 358 } 359 } 360 } 361 362 ContentProviderClient client = null; 363 Cursor cursor = null; 364 try { 365 client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority); 366 cursor = client.query(rootsUri, null, null, null, null); 367 while (cursor.moveToNext()) { 368 final RootInfo root = RootInfo.fromRootsCursor(userId, authority, cursor); 369 roots.add(root); 370 } 371 } catch (Exception e) { 372 Log.w(TAG, "Failed to load some roots from " + authority, e); 373 // We didn't load every root from the provider. Don't put it to 374 // system cache so that we'll try loading them again next time even 375 // if forceRefresh is false. 376 return roots; 377 } finally { 378 FileUtils.closeQuietly(cursor); 379 FileUtils.closeQuietly(client); 380 } 381 382 // Cache these freshly parsed roots over in the long-lived system 383 // process, in case our process goes away. The system takes care of 384 // invalidating the cache if the package or Uri changes. 385 final Bundle systemCache = new Bundle(); 386 if (roots.isEmpty() && !PERMIT_EMPTY_CACHE.contains(authority)) { 387 Log.i(TAG, "Provider returned no roots. Possibly naughty: " + authority); 388 } else { 389 systemCache.putParcelableArrayList(TAG, roots); 390 resolver.putCache(rootsUri, systemCache); 391 } 392 393 return roots; 394 } 395 396 @Override 397 public RootInfo getRootOneshot(UserId userId, String authority, String rootId) { 398 return getRootOneshot(userId, authority, rootId, false); 399 } 400 401 public RootInfo getRootOneshot(UserId userId, String authority, String rootId, 402 boolean forceRefresh) { 403 synchronized (mLock) { 404 UserAuthority userAuthority = new UserAuthority(userId, authority); 405 RootInfo root = forceRefresh ? null : getRootLocked(userAuthority, rootId); 406 if (root == null) { 407 mRoots.replaceValues(userAuthority, 408 loadRootsForAuthority(userAuthority, forceRefresh)); 409 root = getRootLocked(userAuthority, rootId); 410 } 411 return root; 412 } 413 } 414 415 public RootInfo getRootBlocking(UserId userId, String authority, String rootId) { 416 waitForFirstLoad(); 417 loadStoppedAuthorities(); 418 synchronized (mLock) { 419 return getRootLocked(new UserAuthority(userId, authority), rootId); 420 } 421 } 422 423 private RootInfo getRootLocked(UserAuthority userAuthority, String rootId) { 424 for (RootInfo root : mRoots.get(userAuthority)) { 425 if (Objects.equals(root.rootId, rootId)) { 426 return root; 427 } 428 } 429 return null; 430 } 431 432 @Override 433 public RootInfo getRecentsRoot(UserId userId) { 434 return createOrGetRecentsRoot(userId); 435 } 436 437 public boolean isRecentsRoot(RootInfo root) { 438 return mRecentsRoots.containsValue(root); 439 } 440 441 @Override 442 public Collection<RootInfo> getRootsBlocking() { 443 waitForFirstLoad(); 444 loadStoppedAuthorities(); 445 synchronized (mLock) { 446 return new HashSet<>(mRoots.values()); 447 } 448 } 449 450 @Override 451 public Collection<RootInfo> getMatchingRootsBlocking(State state) { 452 waitForFirstLoad(); 453 loadStoppedAuthorities(); 454 synchronized (mLock) { 455 return ProvidersAccess.getMatchingRoots(mRoots.values(), state); 456 } 457 } 458 459 @Override 460 public Collection<RootInfo> getRootsForAuthorityBlocking(UserId userId, String authority) { 461 waitForFirstLoad(); 462 UserAuthority userAuthority = new UserAuthority(userId, authority); 463 loadStoppedAuthority(userAuthority); 464 synchronized (mLock) { 465 final Collection<RootInfo> roots = mRoots.get(userAuthority); 466 return roots != null ? roots : Collections.<RootInfo>emptyList(); 467 } 468 } 469 470 @Override 471 public RootInfo getDefaultRootBlocking(State state) { 472 RootInfo root = ProvidersAccess.getDefaultRoot(getRootsBlocking(), state); 473 return root != null ? root : createOrGetRecentsRoot(UserId.CURRENT_USER); 474 } 475 476 public void logCache() { 477 StringBuilder output = new StringBuilder(); 478 479 for (UserAuthority userAuthority : mObservedAuthoritiesDetails.keySet()) { 480 List<String> roots = new ArrayList<>(); 481 Uri rootsUri = DocumentsContract.buildRootsUri(userAuthority.authority); 482 Bundle systemCache = userAuthority.userId.getContentResolver(mContext).getCache( 483 rootsUri); 484 if (systemCache != null) { 485 ArrayList<RootInfo> cachedRoots = systemCache.getParcelableArrayList(TAG); 486 for (RootInfo root : cachedRoots) { 487 roots.add(root.toDebugString()); 488 } 489 } 490 491 output.append((output.length() == 0) ? "System cache: " : ", "); 492 output.append(userAuthority).append("=").append(roots); 493 } 494 495 Log.i(TAG, output.toString()); 496 } 497 498 private class MultiProviderUpdateTask extends AsyncTask<Void, Void, Void> { 499 private final boolean mForceRefreshAll; 500 @Nullable 501 private final UserPackage mForceRefreshUserPackage; 502 @Nullable 503 private final Runnable mCallback; 504 505 @GuardedBy("mLock") 506 private Multimap<UserAuthority, RootInfo> mLocalRoots = ArrayListMultimap.create(); 507 @GuardedBy("mLock") 508 private HashSet<UserAuthority> mLocalStoppedAuthorities = new HashSet<>(); 509 510 /** 511 * Create task to update roots cache. 512 * 513 * @param forceRefreshAll when true, all previously cached values for 514 * all packages should be ignored. 515 * @param forceRefreshUserPackage when non-null, all previously cached 516 * values for this specific user package should be ignored. 517 * @param callback when non-null, it will be invoked after the task is 518 * executed. 519 */ 520 MultiProviderUpdateTask( 521 boolean forceRefreshAll, 522 @Nullable UserPackage forceRefreshUserPackage, 523 @Nullable Runnable callback) { 524 mForceRefreshAll = forceRefreshAll; 525 mForceRefreshUserPackage = forceRefreshUserPackage; 526 mCallback = callback; 527 } 528 529 @Override 530 protected Void doInBackground(Void... params) { 531 if (!mMultiProviderUpdateTaskSemaphore.tryAcquire()) { 532 // Abort, since previous update task is still running. 533 return null; 534 } 535 536 int previousPriority = Thread.currentThread().getPriority(); 537 Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 538 539 final long start = SystemClock.elapsedRealtime(); 540 541 List<UserId> userIds = new ArrayList<>(getUserIds()); 542 for (UserId userId : userIds) { 543 final RootInfo recents = createOrGetRecentsRoot(userId); 544 synchronized (mLock) { 545 mLocalRoots.put(new UserAuthority(recents.userId, recents.authority), recents); 546 } 547 } 548 549 List<SingleProviderUpdateTaskInfo> taskInfos = new ArrayList<>(); 550 for (UserId userId : userIds) { 551 final PackageManager pm = userId.getPackageManager(mContext); 552 // Pick up provider with action string 553 final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); 554 final List<ResolveInfo> providers = pm.queryIntentContentProviders(intent, 0); 555 for (ResolveInfo info : providers) { 556 ProviderInfo providerInfo = info.providerInfo; 557 if (providerInfo.authority != null) { 558 taskInfos.add(new SingleProviderUpdateTaskInfo(providerInfo, userId)); 559 } 560 } 561 } 562 563 if (!taskInfos.isEmpty()) { 564 CountDownLatch updateTaskInternalCountDown = new CountDownLatch(taskInfos.size()); 565 ExecutorService executor = MoreExecutors.getExitingExecutorService( 566 (ThreadPoolExecutor) Executors.newCachedThreadPool()); 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