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.UserIdManager; 55 import com.android.documentsui.UserPackage; 56 import com.android.documentsui.archives.ArchivesProvider; 57 import com.android.documentsui.base.LookupApplicationName; 58 import com.android.documentsui.base.Providers; 59 import com.android.documentsui.base.RootInfo; 60 import com.android.documentsui.base.State; 61 import com.android.documentsui.base.UserId; 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 = new ArrayList<String>() {{ 93 // MTP provider commonly returns no roots (if no devices are attached). 94 add(Providers.AUTHORITY_MTP); 95 // ArchivesProvider doesn't support any roots. 96 add(ArchivesProvider.AUTHORITY); 97 }}; 98 private static final int FIRST_LOAD_TIMEOUT_MS = 5000; 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 125 private final UserIdManager mUserIdManager; 126 ProvidersCache(Context context, UserIdManager userIdManager)127 public ProvidersCache(Context context, UserIdManager userIdManager) { 128 mContext = context; 129 mUserIdManager = userIdManager; 130 } 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 for (UserId userId : mUserIdManager.getUserIds()) { 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 executed. 518 */ 519 MultiProviderUpdateTask( 520 boolean forceRefreshAll, 521 @Nullable UserPackage forceRefreshUserPackage, 522 @Nullable Runnable callback) { 523 mForceRefreshAll = forceRefreshAll; 524 mForceRefreshUserPackage = forceRefreshUserPackage; 525 mCallback = callback; 526 } 527 528 @Override 529 protected Void doInBackground(Void... params) { 530 if (!mMultiProviderUpdateTaskSemaphore.tryAcquire()) { 531 // Abort, since previous update task is still running. 532 return null; 533 } 534 535 int previousPriority = Thread.currentThread().getPriority(); 536 Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 537 538 final long start = SystemClock.elapsedRealtime(); 539 540 for (UserId userId : mUserIdManager.getUserIds()) { 541 final RootInfo recents = createOrGetRecentsRoot(userId); 542 synchronized (mLock) { 543 mLocalRoots.put(new UserAuthority(recents.userId, recents.authority), recents); 544 } 545 } 546 547 List<SingleProviderUpdateTaskInfo> taskInfos = new ArrayList<>(); 548 for (UserId userId : mUserIdManager.getUserIds()) { 549 final PackageManager pm = userId.getPackageManager(mContext); 550 // Pick up provider with action string 551 final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); 552 final List<ResolveInfo> providers = pm.queryIntentContentProviders(intent, 0); 553 for (ResolveInfo info : providers) { 554 ProviderInfo providerInfo = info.providerInfo; 555 if (providerInfo.authority != null) { 556 taskInfos.add(new SingleProviderUpdateTaskInfo(providerInfo, userId)); 557 } 558 } 559 } 560 561 if (!taskInfos.isEmpty()) { 562 CountDownLatch updateTaskInternalCountDown = new CountDownLatch(taskInfos.size()); 563 ExecutorService executor = MoreExecutors.getExitingExecutorService( 564 (ThreadPoolExecutor) Executors.newCachedThreadPool()); 565 for (SingleProviderUpdateTaskInfo taskInfo: taskInfos) { 566 executor.submit(() -> 567 startSingleProviderUpdateTask( 568 taskInfo.providerInfo, 569 taskInfo.userId, 570 updateTaskInternalCountDown)); 571 } 572 573 // Block until all SingleProviderUpdateTask threads finish executing. 574 // Use a shorter timeout for first load since it could block picker UI. 575 long timeoutMs = mFirstLoadDone ? 15000 : FIRST_LOAD_TIMEOUT_MS; 576 boolean success = false; 577 try { 578 success = updateTaskInternalCountDown.await(timeoutMs, TimeUnit.MILLISECONDS); 579 } catch (InterruptedException e) { 580 } 581 if (!success) { 582 Log.w(TAG, "Timeout executing update task!"); 583 } 584 } 585 586 final long delta = SystemClock.elapsedRealtime() - start; 587 synchronized (mLock) { 588 mFirstLoadDone = true; 589 if (mBootCompletedResult != null) { 590 mBootCompletedResult.finish(); 591 mBootCompletedResult = null; 592 } 593 mRoots = mLocalRoots; 594 mStoppedAuthorities = mLocalStoppedAuthorities; 595 } 596 if (VERBOSE) { 597 Log.v(TAG, "Update found " + mLocalRoots.size() + " roots in " + delta + "ms"); 598 } 599 600 mFirstLoad.countDown(); 601 LocalBroadcastManager.getInstance(mContext).sendBroadcast(new Intent(BROADCAST_ACTION)); 602 mMultiProviderUpdateTaskSemaphore.release(); 603 604 Thread.currentThread().setPriority(previousPriority); 605 return null; 606 } 607 608 @Override 609 protected void onPostExecute(Void aVoid) { 610 if (mCallback != null) { 611 mCallback.run(); 612 } 613 } 614 615 private void startSingleProviderUpdateTask( 616 ProviderInfo providerInfo, 617 UserId userId, 618 CountDownLatch updateCountDown) { 619 int previousPriority = Thread.currentThread().getPriority(); 620 Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 621 handleDocumentsProvider(providerInfo, userId); 622 updateCountDown.countDown(); 623 Thread.currentThread().setPriority(previousPriority); 624 } 625 626 private void handleDocumentsProvider(ProviderInfo info, UserId userId) { 627 UserAuthority userAuthority = new UserAuthority(userId, info.authority); 628 // Ignore stopped packages for now; we might query them 629 // later during UI interaction. 630 if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) { 631 if (VERBOSE) { 632 Log.v(TAG, "Ignoring stopped authority " + info.authority + ", user " + userId); 633 } 634 synchronized (mLock) { 635 mLocalStoppedAuthorities.add(userAuthority); 636 } 637 return; 638 } 639 640 final boolean forceRefresh = mForceRefreshAll 641 || Objects.equals( 642 new UserPackage(userId, info.packageName), mForceRefreshUserPackage); 643 synchronized (mLock) { 644 mLocalRoots.putAll(userAuthority, 645 loadRootsForAuthority(userAuthority, forceRefresh)); 646 } 647 } 648 } 649 650 private static class UserAuthority { 651 private final UserId userId; 652 @Nullable 653 private final String authority; 654 655 private UserAuthority(UserId userId, @Nullable String authority) { 656 this.userId = checkNotNull(userId); 657 this.authority = authority; 658 } 659 660 @Override 661 public boolean equals(Object o) { 662 if (o == null) { 663 return false; 664 } 665 666 if (this == o) { 667 return true; 668 } 669 670 if (o instanceof UserAuthority) { 671 UserAuthority other = (UserAuthority) o; 672 return Objects.equals(userId, other.userId) 673 && Objects.equals(authority, other.authority); 674 } 675 676 return false; 677 } 678 679 680 @Override 681 public int hashCode() { 682 return Objects.hash(userId, authority); 683 } 684 } 685 686 private static class SingleProviderUpdateTaskInfo { 687 private final ProviderInfo providerInfo; 688 private final UserId userId; 689 690 SingleProviderUpdateTaskInfo(ProviderInfo providerInfo, UserId userId) { 691 this.providerInfo = providerInfo; 692 this.userId = userId; 693 } 694 } 695 696 private static class PackageDetails { 697 private String applicationName; 698 private String packageName; 699 700 public PackageDetails(String appName, String pckgName) { 701 applicationName = appName; 702 packageName = pckgName; 703 } 704 } 705 } 706