1 /* 2 * Copyright (C) 2015 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.settingslib.applications; 18 19 import android.annotation.IntDef; 20 import android.app.ActivityManager; 21 import android.app.AppGlobals; 22 import android.app.Application; 23 import android.app.usage.StorageStats; 24 import android.app.usage.StorageStatsManager; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.Flags; 31 import android.content.pm.IPackageManager; 32 import android.content.pm.IPackageStatsObserver; 33 import android.content.pm.ModuleInfo; 34 import android.content.pm.PackageManager; 35 import android.content.pm.PackageManager.NameNotFoundException; 36 import android.content.pm.PackageStats; 37 import android.content.pm.ParceledListSlice; 38 import android.content.pm.ResolveInfo; 39 import android.content.pm.UserInfo; 40 import android.content.pm.UserProperties; 41 import android.graphics.drawable.Drawable; 42 import android.net.Uri; 43 import android.os.Build; 44 import android.os.Handler; 45 import android.os.HandlerThread; 46 import android.os.Looper; 47 import android.os.Message; 48 import android.os.Process; 49 import android.os.RemoteException; 50 import android.os.SystemClock; 51 import android.os.UserHandle; 52 import android.os.UserManager; 53 import android.text.format.Formatter; 54 import android.util.ArrayMap; 55 import android.util.Log; 56 import android.util.SparseArray; 57 58 import androidx.annotation.NonNull; 59 import androidx.annotation.VisibleForTesting; 60 import androidx.lifecycle.Lifecycle; 61 import androidx.lifecycle.LifecycleObserver; 62 import androidx.lifecycle.OnLifecycleEvent; 63 64 import com.android.internal.R; 65 import com.android.internal.util.ArrayUtils; 66 import com.android.settingslib.Utils; 67 import com.android.settingslib.utils.ThreadUtils; 68 69 import java.io.File; 70 import java.io.IOException; 71 import java.lang.annotation.Retention; 72 import java.lang.annotation.RetentionPolicy; 73 import java.lang.ref.WeakReference; 74 import java.text.Collator; 75 import java.text.Normalizer; 76 import java.text.Normalizer.Form; 77 import java.util.ArrayList; 78 import java.util.Collections; 79 import java.util.Comparator; 80 import java.util.HashMap; 81 import java.util.HashSet; 82 import java.util.List; 83 import java.util.Objects; 84 import java.util.UUID; 85 import java.util.regex.Pattern; 86 87 import javax.annotation.Nullable; 88 89 /** 90 * Keeps track of information about all installed applications, lazy-loading 91 * as needed. 92 */ 93 public class ApplicationsState { 94 private static final String TAG = "ApplicationsState"; 95 96 public static final int SIZE_UNKNOWN = -1; 97 public static final int SIZE_INVALID = -2; 98 99 private static final boolean DEBUG = false; 100 private static final boolean DEBUG_LOCKING = false; 101 private static final Object sLock = new Object(); 102 private static final Pattern REMOVE_DIACRITICALS_PATTERN 103 = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); 104 private static final String SETTING_PKG = "com.android.settings"; 105 106 @VisibleForTesting 107 static ApplicationsState sInstance; 108 109 // Whether the app icon cache mechanism is enabled or not. 110 private static boolean sAppIconCacheEnabled = false; 111 getInstance(Application app)112 public static ApplicationsState getInstance(Application app) { 113 return getInstance(app, AppGlobals.getPackageManager()); 114 } 115 116 @VisibleForTesting getInstance(Application app, IPackageManager iPackageManager)117 static ApplicationsState getInstance(Application app, IPackageManager iPackageManager) { 118 synchronized (sLock) { 119 if (sInstance == null) { 120 sInstance = new ApplicationsState(app, iPackageManager); 121 } 122 return sInstance; 123 } 124 } 125 126 /** Set whether the app icon cache mechanism is enabled or not. */ setAppIconCacheEnabled(boolean enabled)127 public static void setAppIconCacheEnabled(boolean enabled) { 128 sAppIconCacheEnabled = enabled; 129 } 130 131 final Context mContext; 132 final PackageManager mPm; 133 final IPackageManager mIpm; 134 final UserManager mUm; 135 final StorageStatsManager mStats; 136 final int mAdminRetrieveFlags; 137 final int mRetrieveFlags; 138 PackageIntentReceiver mPackageIntentReceiver; 139 PackageIntentReceiver mClonePackageIntentReceiver; 140 141 boolean mResumed; 142 boolean mHaveDisabledApps; 143 boolean mHaveInstantApps; 144 145 // Information about all applications. Synchronize on mEntriesMap 146 // to protect access to these. 147 final ArrayList<Session> mSessions = new ArrayList<>(); 148 final ArrayList<Session> mRebuildingSessions = new ArrayList<>(); 149 private InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); 150 // Map: userid => (Map: package name => AppEntry) 151 final SparseArray<HashMap<String, AppEntry>> mEntriesMap = new SparseArray<>(); 152 final ArrayList<AppEntry> mAppEntries = new ArrayList<>(); 153 List<ApplicationInfo> mApplications = new ArrayList<>(); 154 long mCurId = 1; 155 UUID mCurComputingSizeUuid; 156 String mCurComputingSizePkg; 157 int mCurComputingSizeUserId; 158 boolean mSessionsChanged; 159 // Maps all installed modules on the system to whether they're hidden or not. 160 final HashMap<String, Boolean> mSystemModules = new HashMap<>(); 161 162 // Temporary for dispatching session callbacks. Only touched by main thread. 163 final ArrayList<WeakReference<Session>> mActiveSessions = new ArrayList<>(); 164 165 final HandlerThread mThread; 166 final BackgroundHandler mBackgroundHandler; 167 final MainHandler mMainHandler = new MainHandler(Looper.getMainLooper()); 168 169 /** Requests that the home app is loaded. */ 170 public static final int FLAG_SESSION_REQUEST_HOME_APP = 1 << 0; 171 172 /** Requests that icons are loaded. */ 173 public static final int FLAG_SESSION_REQUEST_ICONS = 1 << 1; 174 175 /** Requests that sizes are loaded. */ 176 public static final int FLAG_SESSION_REQUEST_SIZES = 1 << 2; 177 178 /** Requests that launcher intents are resolved. */ 179 public static final int FLAG_SESSION_REQUEST_LAUNCHER = 1 << 3; 180 181 /** Requests that leanback launcher intents are resolved. */ 182 public static final int FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER = 1 << 4; 183 184 /** 185 * Flags to configure the session to request various types of info. 186 */ 187 @IntDef(prefix = {"FLAG_SESSION_"}, value = { 188 FLAG_SESSION_REQUEST_HOME_APP, 189 FLAG_SESSION_REQUEST_ICONS, 190 FLAG_SESSION_REQUEST_SIZES, 191 FLAG_SESSION_REQUEST_LAUNCHER, 192 FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER 193 }) 194 @Retention(RetentionPolicy.SOURCE) 195 public @interface SessionFlags { 196 } 197 198 @VisibleForTesting setInterestingConfigChanges(InterestingConfigChanges interestingConfigChanges)199 void setInterestingConfigChanges(InterestingConfigChanges interestingConfigChanges) { 200 mInterestingConfigChanges = interestingConfigChanges; 201 } 202 203 @SessionFlags 204 public static final int DEFAULT_SESSION_FLAGS = 205 FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS | 206 FLAG_SESSION_REQUEST_SIZES | FLAG_SESSION_REQUEST_LAUNCHER; 207 ApplicationsState(Application app, IPackageManager iPackageManager)208 private ApplicationsState(Application app, IPackageManager iPackageManager) { 209 mContext = app; 210 mPm = mContext.getPackageManager(); 211 mIpm = iPackageManager; 212 mUm = mContext.getSystemService(UserManager.class); 213 mStats = mContext.getSystemService(StorageStatsManager.class); 214 for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) { 215 mEntriesMap.put(userId, new HashMap<>()); 216 } 217 218 mThread = new HandlerThread("ApplicationsState.Loader"); 219 mThread.start(); 220 mBackgroundHandler = new BackgroundHandler(mThread.getLooper()); 221 222 // Only the owner can see all apps. 223 mAdminRetrieveFlags = PackageManager.MATCH_ANY_USER | 224 PackageManager.MATCH_DISABLED_COMPONENTS | 225 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; 226 mRetrieveFlags = PackageManager.MATCH_DISABLED_COMPONENTS | 227 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; 228 229 final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */); 230 for (ModuleInfo info : moduleInfos) { 231 mSystemModules.put(info.getPackageName(), info.isHidden()); 232 if (Flags.provideInfoOfApkInApex()) { 233 for (String apkInApexPackageName : info.getApkInApexPackageNames()) { 234 mSystemModules.put(apkInApexPackageName, info.isHidden()); 235 } 236 } 237 } 238 239 /** 240 * This is a trick to prevent the foreground thread from being delayed. 241 * The problem is that Dalvik monitors are initially spin locks, to keep 242 * them lightweight. This leads to unfair contention -- Even though the 243 * background thread only holds the lock for a short amount of time, if 244 * it keeps running and locking again it can prevent the main thread from 245 * acquiring its lock for a long time... sometimes even > 5 seconds 246 * (leading to an ANR). 247 * 248 * Dalvik will promote a monitor to a "real" lock if it detects enough 249 * contention on it. It doesn't figure this out fast enough for us 250 * here, though, so this little trick will force it to turn into a real 251 * lock immediately. 252 */ 253 synchronized (mEntriesMap) { 254 try { 255 mEntriesMap.wait(1); 256 } catch (InterruptedException e) { 257 } 258 } 259 } 260 getBackgroundLooper()261 public Looper getBackgroundLooper() { 262 return mThread.getLooper(); 263 } 264 newSession(Callbacks callbacks)265 public Session newSession(Callbacks callbacks) { 266 return newSession(callbacks, null); 267 } 268 newSession(Callbacks callbacks, Lifecycle lifecycle)269 public Session newSession(Callbacks callbacks, Lifecycle lifecycle) { 270 Session s = new Session(callbacks, lifecycle); 271 synchronized (mEntriesMap) { 272 mSessions.add(s); 273 } 274 return s; 275 } 276 doResumeIfNeededLocked()277 void doResumeIfNeededLocked() { 278 if (mResumed) { 279 return; 280 } 281 mResumed = true; 282 if (mPackageIntentReceiver == null) { 283 mPackageIntentReceiver = new PackageIntentReceiver(); 284 mPackageIntentReceiver.registerReceiver(); 285 } 286 287 // Listen to any package additions in clone user to refresh the app list. 288 if (mClonePackageIntentReceiver == null) { 289 int cloneUserId = AppUtils.getCloneUserId(mContext); 290 if (cloneUserId != -1) { 291 mClonePackageIntentReceiver = new PackageIntentReceiver(); 292 mClonePackageIntentReceiver.registerReceiverForClone(cloneUserId); 293 } 294 } 295 296 final List<ApplicationInfo> prevApplications = mApplications; 297 mApplications = new ArrayList<>(); 298 for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) { 299 try { 300 // If this user is new, it needs a map created. 301 if (mEntriesMap.indexOfKey(user.id) < 0) { 302 mEntriesMap.put(user.id, new HashMap<>()); 303 } 304 @SuppressWarnings("unchecked") 305 ParceledListSlice<ApplicationInfo> list = 306 mIpm.getInstalledApplications( 307 user.isAdmin() ? mAdminRetrieveFlags : mRetrieveFlags, 308 user.id); 309 mApplications.addAll(list.getList()); 310 } catch (Exception e) { 311 Log.e(TAG, "Error during doResumeIfNeededLocked", e); 312 } 313 } 314 315 if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { 316 // If an interesting part of the configuration has changed, we 317 // should completely reload the app entries. 318 clearEntries(); 319 } else { 320 for (int i = 0; i < mAppEntries.size(); i++) { 321 mAppEntries.get(i).sizeStale = true; 322 } 323 } 324 325 mHaveDisabledApps = false; 326 mHaveInstantApps = false; 327 for (int i = 0; i < mApplications.size(); i++) { 328 final ApplicationInfo info = mApplications.get(i); 329 // Need to trim out any applications that are disabled by 330 // something different than the user. 331 if (!info.enabled) { 332 if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { 333 mApplications.remove(i); 334 i--; 335 continue; 336 } 337 mHaveDisabledApps = true; 338 } 339 if (isHiddenModule(info.packageName)) { 340 mApplications.remove(i--); 341 continue; 342 } 343 if (!mHaveInstantApps && AppUtils.isInstant(info)) { 344 mHaveInstantApps = true; 345 } 346 347 int userId = UserHandle.getUserId(info.uid); 348 final AppEntry entry = mEntriesMap.get(userId).get(info.packageName); 349 if (entry != null) { 350 entry.info = info; 351 } 352 } 353 354 if (anyAppIsRemoved(prevApplications, mApplications)) { 355 // some apps have been uninstalled. 356 clearEntries(); 357 } 358 mCurComputingSizePkg = null; 359 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { 360 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); 361 } 362 } 363 364 /* The original design is mAppEntries.size() > mApplications.size(). 365 It's correct if there is only the owner user and only one app is removed. 366 Problem 1: 367 If there is a user profile, the size of mAppEntries < mApplications is normal because 368 the number of app entries on UI (mAppEntries) should be equal to the number of apps got 369 from PMS (mApplications). 370 371 owner only case: 372 mApplications: user 0: 191 373 mAppEntries : user 0: 191 374 total mAppEntries: 191, mApplications: 191 375 If an app is removed, cached mAppEntries: 191 , mApplications: 191 -> 190, it is detected 376 as the number of apps becomes less. 377 378 If there is a work profile, mAppEntries removes some apps that are not installed for the 379 owner user. 380 381 For example, in the following case, 6 apps are removed from mAppEntries for the owner. 382 mApplications: user 0: 197, user 10: 189 => total 386 383 mAppEntries : user 0: 191, user 10: 189 => total 380 384 If an app is removed, cached mAppEntries: 380 , mApplications: 386 -> 385, the size of 385 mAppEntries is still not larger than mApplications, then does not clear mAppEntries. 386 387 Problem 2: 388 If remove an app and add another app outside Settings (e.g. Play Store) and back to 389 Settings, the amount of apps are not changed, it causes the entries keep the removed app. 390 391 Another case, if adding more apps than removing apps (e.g. add 2 apps and remove 1 app), 392 the final number of apps (mApplications) is even increased, 393 394 Therefore, should not only count on number of apps to determine any app is removed. 395 Compare the change of applications instead. 396 */ anyAppIsRemoved(List<ApplicationInfo> prevApplications, List<ApplicationInfo> applications)397 private static boolean anyAppIsRemoved(List<ApplicationInfo> prevApplications, 398 List<ApplicationInfo> applications) { 399 400 // No cache 401 if (prevApplications.size() == 0) { 402 return false; 403 } 404 405 if (applications.size() < prevApplications.size()) { 406 return true; 407 } 408 409 // build package sets of all applications <userId, HashSet of packages> 410 final HashMap<String, HashSet<String>> packageMap = new HashMap<>(); 411 for (ApplicationInfo application : applications) { 412 final String userId = String.valueOf(UserHandle.getUserId(application.uid)); 413 414 HashSet<String> appPackages = packageMap.get(userId); 415 if (appPackages == null) { 416 appPackages = new HashSet<>(); 417 packageMap.put(userId, appPackages); 418 } 419 if (hasFlag(application.flags, ApplicationInfo.FLAG_INSTALLED)) { 420 appPackages.add(application.packageName); 421 } 422 } 423 424 // detect any previous app is removed 425 for (ApplicationInfo prevApplication : prevApplications) { 426 if (!hasFlag(prevApplication.flags, ApplicationInfo.FLAG_INSTALLED)) { 427 continue; 428 } 429 final String userId = String.valueOf(UserHandle.getUserId(prevApplication.uid)); 430 431 final HashSet<String> packagesSet = packageMap.get(userId); 432 if (packagesSet == null || !packagesSet.remove(prevApplication.packageName)) { 433 return true; 434 } 435 } 436 437 return false; 438 } 439 440 @VisibleForTesting clearEntries()441 void clearEntries() { 442 for (int i = 0; i < mEntriesMap.size(); i++) { 443 mEntriesMap.valueAt(i).clear(); 444 } 445 mAppEntries.clear(); 446 } 447 haveDisabledApps()448 public boolean haveDisabledApps() { 449 return mHaveDisabledApps; 450 } 451 haveInstantApps()452 public boolean haveInstantApps() { 453 return mHaveInstantApps; 454 } 455 isHiddenModule(String packageName)456 boolean isHiddenModule(String packageName) { 457 Boolean isHidden = mSystemModules.get(packageName); 458 if (isHidden == null) { 459 return false; 460 } 461 462 return isHidden; 463 } 464 isSystemModule(String packageName)465 boolean isSystemModule(String packageName) { 466 return mSystemModules.containsKey(packageName); 467 } 468 doPauseIfNeededLocked()469 void doPauseIfNeededLocked() { 470 if (!mResumed) { 471 return; 472 } 473 for (int i = 0; i < mSessions.size(); i++) { 474 if (mSessions.get(i).mResumed) { 475 return; 476 } 477 } 478 doPauseLocked(); 479 } 480 doPauseLocked()481 void doPauseLocked() { 482 mResumed = false; 483 if (mPackageIntentReceiver != null) { 484 mPackageIntentReceiver.unregisterReceiver(); 485 mPackageIntentReceiver = null; 486 } 487 if (mClonePackageIntentReceiver != null) { 488 mClonePackageIntentReceiver.unregisterReceiver(); 489 mClonePackageIntentReceiver = null; 490 } 491 } 492 getEntry(String packageName, int userId)493 public AppEntry getEntry(String packageName, int userId) { 494 if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock..."); 495 synchronized (mEntriesMap) { 496 AppEntry entry = null; 497 HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId); 498 if (userEntriesMap != null) { 499 entry = userEntriesMap.get(packageName); 500 } 501 if (entry == null) { 502 ApplicationInfo info = getAppInfoLocked(packageName, userId); 503 if (info == null) { 504 try { 505 info = mIpm.getApplicationInfo(packageName, 506 PackageManager.MATCH_ARCHIVED_PACKAGES, userId); 507 } catch (RemoteException e) { 508 Log.w(TAG, "getEntry couldn't reach PackageManager", e); 509 return null; 510 } 511 } 512 if (info != null) { 513 entry = getEntryLocked(info); 514 } 515 } 516 if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock"); 517 return entry; 518 } 519 } 520 getAppInfoLocked(String pkg, int userId)521 private ApplicationInfo getAppInfoLocked(String pkg, int userId) { 522 for (int i = 0; i < mApplications.size(); i++) { 523 ApplicationInfo info = mApplications.get(i); 524 if (pkg.equals(info.packageName) 525 && userId == UserHandle.getUserId(info.uid)) { 526 return info; 527 } 528 } 529 return null; 530 } 531 532 /** 533 * Starting Android T, this method will not be used if {@link AppIconCacheManager} is applied. 534 */ ensureIcon(AppEntry entry)535 public void ensureIcon(AppEntry entry) { 536 if (entry.icon != null) { 537 return; 538 } 539 synchronized (entry) { 540 entry.ensureIconLocked(mContext); 541 } 542 } 543 544 /** 545 * To generate and cache the label description. 546 * 547 * @param entry contain the entries of an app 548 */ ensureLabelDescription(AppEntry entry)549 public void ensureLabelDescription(AppEntry entry) { 550 if (entry.labelDescription != null) { 551 return; 552 } 553 synchronized (entry) { 554 entry.ensureLabelDescriptionLocked(mContext); 555 } 556 } 557 requestSize(String packageName, int userId)558 public void requestSize(String packageName, int userId) { 559 if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock..."); 560 synchronized (mEntriesMap) { 561 AppEntry entry = mEntriesMap.get(userId).get(packageName); 562 if (entry != null && hasFlag(entry.info.flags, ApplicationInfo.FLAG_INSTALLED)) { 563 mBackgroundHandler.post( 564 () -> { 565 try { 566 final StorageStats stats = 567 mStats.queryStatsForPackage( 568 entry.info.storageUuid, 569 packageName, 570 UserHandle.of(userId)); 571 final long cacheQuota = 572 mStats.getCacheQuotaBytes( 573 entry.info.storageUuid.toString(), entry.info.uid); 574 final PackageStats legacy = new PackageStats(packageName, userId); 575 legacy.codeSize = stats.getAppBytes(); 576 legacy.dataSize = stats.getDataBytes(); 577 legacy.cacheSize = Math.min(stats.getCacheBytes(), cacheQuota); 578 try { 579 mBackgroundHandler.mStatsObserver.onGetStatsCompleted( 580 legacy, true); 581 } catch (RemoteException ignored) { 582 } 583 } catch (NameNotFoundException | IOException e) { 584 Log.w(TAG, "Failed to query stats: " + e); 585 try { 586 mBackgroundHandler.mStatsObserver.onGetStatsCompleted( 587 null, false); 588 } catch (RemoteException ignored) { 589 } 590 } 591 }); 592 } 593 if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock"); 594 } 595 } 596 sumCacheSizes()597 long sumCacheSizes() { 598 long sum = 0; 599 if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock..."); 600 synchronized (mEntriesMap) { 601 if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock"); 602 for (int i = mAppEntries.size() - 1; i >= 0; i--) { 603 sum += mAppEntries.get(i).cacheSize; 604 } 605 if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock"); 606 } 607 return sum; 608 } 609 indexOfApplicationInfoLocked(String pkgName, int userId)610 int indexOfApplicationInfoLocked(String pkgName, int userId) { 611 for (int i = mApplications.size() - 1; i >= 0; i--) { 612 ApplicationInfo appInfo = mApplications.get(i); 613 if (appInfo.packageName.equals(pkgName) 614 && UserHandle.getUserId(appInfo.uid) == userId) { 615 return i; 616 } 617 } 618 return -1; 619 } 620 addPackage(String pkgName, int userId)621 void addPackage(String pkgName, int userId) { 622 try { 623 synchronized (mEntriesMap) { 624 if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock"); 625 if (DEBUG) Log.i(TAG, "Adding package " + pkgName); 626 if (!mResumed) { 627 // If we are not resumed, we will do a full query the 628 // next time we resume, so there is no reason to do work 629 // here. 630 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed"); 631 return; 632 } 633 if (indexOfApplicationInfoLocked(pkgName, userId) >= 0) { 634 if (DEBUG) Log.i(TAG, "Package already exists!"); 635 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists"); 636 return; 637 } 638 ApplicationInfo info = mIpm.getApplicationInfo(pkgName, 639 mUm.isUserAdmin(userId) ? mAdminRetrieveFlags : mRetrieveFlags, 640 userId); 641 if (info == null) { 642 return; 643 } 644 if (!info.enabled) { 645 if (info.enabledSetting 646 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { 647 return; 648 } 649 mHaveDisabledApps = true; 650 } 651 if (AppUtils.isInstant(info)) { 652 mHaveInstantApps = true; 653 } 654 mApplications.add(info); 655 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { 656 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); 657 } 658 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { 659 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); 660 } 661 if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock"); 662 } 663 } catch (RemoteException e) { 664 } 665 } 666 removePackage(String pkgName, int userId)667 public void removePackage(String pkgName, int userId) { 668 synchronized (mEntriesMap) { 669 if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock"); 670 int idx = indexOfApplicationInfoLocked(pkgName, userId); 671 if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx); 672 if (idx >= 0) { 673 AppEntry entry = mEntriesMap.get(userId).get(pkgName); 674 if (DEBUG) Log.i(TAG, "removePackage: " + entry); 675 if (entry != null) { 676 mEntriesMap.get(userId).remove(pkgName); 677 mAppEntries.remove(entry); 678 } 679 ApplicationInfo info = mApplications.get(idx); 680 mApplications.remove(idx); 681 if (!info.enabled) { 682 mHaveDisabledApps = false; 683 for (ApplicationInfo otherInfo : mApplications) { 684 if (!otherInfo.enabled) { 685 mHaveDisabledApps = true; 686 break; 687 } 688 } 689 } 690 if (AppUtils.isInstant(info)) { 691 mHaveInstantApps = false; 692 for (ApplicationInfo otherInfo : mApplications) { 693 if (AppUtils.isInstant(otherInfo)) { 694 mHaveInstantApps = true; 695 break; 696 } 697 } 698 } 699 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { 700 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); 701 } 702 } 703 if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock"); 704 } 705 } 706 invalidatePackage(String pkgName, int userId)707 public void invalidatePackage(String pkgName, int userId) { 708 removePackage(pkgName, userId); 709 addPackage(pkgName, userId); 710 } 711 addUser(int userId)712 private void addUser(int userId) { 713 final int profileIds[] = mUm.getProfileIdsWithDisabled(UserHandle.myUserId()); 714 if (ArrayUtils.contains(profileIds, userId)) { 715 synchronized (mEntriesMap) { 716 mEntriesMap.put(userId, new HashMap<String, AppEntry>()); 717 if (mResumed) { 718 // If resumed, Manually pause, then cause a resume to repopulate the app list. 719 // This is the simplest way to reload the packages so that the new user 720 // is included. Otherwise the list will be repopulated on next resume. 721 doPauseLocked(); 722 doResumeIfNeededLocked(); 723 } 724 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { 725 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); 726 } 727 } 728 } 729 } 730 removeUser(int userId)731 private void removeUser(int userId) { 732 synchronized (mEntriesMap) { 733 HashMap<String, AppEntry> userMap = mEntriesMap.get(userId); 734 if (userMap != null) { 735 for (AppEntry appEntry : userMap.values()) { 736 mAppEntries.remove(appEntry); 737 mApplications.remove(appEntry.info); 738 } 739 mEntriesMap.remove(userId); 740 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { 741 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); 742 } 743 } 744 } 745 } 746 getEntryLocked(ApplicationInfo info)747 private AppEntry getEntryLocked(ApplicationInfo info) { 748 int userId = UserHandle.getUserId(info.uid); 749 AppEntry entry = null; 750 HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId); 751 if (userEntriesMap != null) { 752 entry = userEntriesMap.get(info.packageName); 753 } 754 if (DEBUG) { 755 Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry); 756 } 757 if (entry == null) { 758 if (isHiddenModule(info.packageName)) { 759 if (DEBUG) { 760 Log.i(TAG, "No AppEntry for " + info.packageName + " (hidden module)"); 761 } 762 return null; 763 } 764 if (DEBUG) { 765 Log.i(TAG, "Creating AppEntry for " + info.packageName); 766 } 767 entry = new AppEntry(mContext, info, mCurId++); 768 userEntriesMap = mEntriesMap.get(userId); 769 if (userEntriesMap != null) { 770 userEntriesMap.put(info.packageName, entry); 771 mAppEntries.add(entry); 772 } 773 } else if (entry.info != info) { 774 entry.info = info; 775 } 776 return entry; 777 } 778 779 // -------------------------------------------------------------- 780 getTotalInternalSize(PackageStats ps)781 private long getTotalInternalSize(PackageStats ps) { 782 if (ps != null) { 783 // We subtract the cache size because the system can clear it automatically and 784 // |dataSize| is a superset of |cacheSize|. 785 return ps.codeSize + ps.dataSize - ps.cacheSize; 786 } 787 return SIZE_INVALID; 788 } 789 getTotalExternalSize(PackageStats ps)790 private long getTotalExternalSize(PackageStats ps) { 791 if (ps != null) { 792 // We also include the cache size here because for non-emulated 793 // we don't automatically clean cache files. 794 return ps.externalCodeSize + ps.externalDataSize 795 + ps.externalCacheSize 796 + ps.externalMediaSize + ps.externalObbSize; 797 } 798 return SIZE_INVALID; 799 } 800 getSizeStr(long size)801 private String getSizeStr(long size) { 802 if (size >= 0) { 803 return Formatter.formatFileSize(mContext, size); 804 } 805 return null; 806 } 807 isAppIconCacheEnabled(Context context)808 private static boolean isAppIconCacheEnabled(Context context) { 809 return SETTING_PKG.equals(context.getPackageName()) 810 || sAppIconCacheEnabled; 811 } 812 rebuildActiveSessions()813 void rebuildActiveSessions() { 814 synchronized (mEntriesMap) { 815 if (!mSessionsChanged) { 816 return; 817 } 818 mActiveSessions.clear(); 819 for (int i = 0; i < mSessions.size(); i++) { 820 Session s = mSessions.get(i); 821 if (s.mResumed) { 822 mActiveSessions.add(new WeakReference<>(s)); 823 } 824 } 825 } 826 } 827 normalize(String str)828 public static String normalize(String str) { 829 String tmp = Normalizer.normalize(str, Form.NFD); 830 return REMOVE_DIACRITICALS_PATTERN.matcher(tmp) 831 .replaceAll("").toLowerCase(); 832 } 833 834 public class Session implements LifecycleObserver { 835 836 final Callbacks mCallbacks; 837 boolean mResumed; 838 839 // Rebuilding of app list. Synchronized on mRebuildSync. 840 final Object mRebuildSync = new Object(); 841 boolean mRebuildRequested; 842 AppFilter mRebuildFilter; 843 Comparator<AppEntry> mRebuildComparator; 844 ArrayList<AppEntry> mLastAppList; 845 boolean mRebuildForeground; 846 847 private final boolean mHasLifecycle; 848 @SessionFlags 849 private int mFlags = DEFAULT_SESSION_FLAGS; 850 Session(Callbacks callbacks, Lifecycle lifecycle)851 Session(Callbacks callbacks, Lifecycle lifecycle) { 852 mCallbacks = callbacks; 853 if (lifecycle != null) { 854 lifecycle.addObserver(this); 855 mHasLifecycle = true; 856 } else { 857 mHasLifecycle = false; 858 } 859 860 if (isAppIconCacheEnabled(mContext)) { 861 // Skip the preloading all icons step to save memory usage. 862 mFlags = mFlags & ~FLAG_SESSION_REQUEST_ICONS; 863 } 864 } 865 866 @SessionFlags getSessionFlags()867 public int getSessionFlags() { 868 return mFlags; 869 } 870 setSessionFlags(@essionFlags int flags)871 public void setSessionFlags(@SessionFlags int flags) { 872 if (isAppIconCacheEnabled(mContext)) { 873 // Skip the preloading all icons step to save memory usage. 874 mFlags = flags & ~FLAG_SESSION_REQUEST_ICONS; 875 } else { 876 mFlags = flags; 877 } 878 } 879 880 @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) onResume()881 public void onResume() { 882 if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock..."); 883 synchronized (mEntriesMap) { 884 if (!mResumed) { 885 mResumed = true; 886 mSessionsChanged = true; 887 doPauseLocked(); 888 doResumeIfNeededLocked(); 889 } 890 } 891 if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock"); 892 } 893 894 @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) onPause()895 public void onPause() { 896 if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock..."); 897 synchronized (mEntriesMap) { 898 if (mResumed) { 899 mResumed = false; 900 mSessionsChanged = true; 901 mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this); 902 doPauseIfNeededLocked(); 903 } 904 if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock"); 905 } 906 } 907 908 /** 909 * Activate session to enable a class that implements Callbacks to receive the callback. 910 */ activateSession()911 public void activateSession() { 912 synchronized (mEntriesMap) { 913 if (!mResumed) { 914 mResumed = true; 915 mSessionsChanged = true; 916 } 917 } 918 } 919 920 /** 921 * Deactivate session to disable a class that implements Callbacks to get the callback. 922 */ deactivateSession()923 public void deactivateSession() { 924 synchronized (mEntriesMap) { 925 if (mResumed) { 926 mResumed = false; 927 mSessionsChanged = true; 928 } 929 } 930 } 931 getAllApps()932 public ArrayList<AppEntry> getAllApps() { 933 synchronized (mEntriesMap) { 934 return new ArrayList<>(mAppEntries); 935 } 936 } 937 938 // Creates a new list of app entries with the given filter and comparator. rebuild(AppFilter filter, Comparator<AppEntry> comparator)939 public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) { 940 return rebuild(filter, comparator, true); 941 } 942 rebuild(AppFilter filter, Comparator<AppEntry> comparator, boolean foreground)943 public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator, 944 boolean foreground) { 945 synchronized (mRebuildSync) { 946 synchronized (mRebuildingSessions) { 947 mRebuildingSessions.add(this); 948 mRebuildRequested = true; 949 mRebuildFilter = filter; 950 mRebuildComparator = comparator; 951 mRebuildForeground = foreground; 952 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) { 953 Message msg = mBackgroundHandler.obtainMessage( 954 BackgroundHandler.MSG_REBUILD_LIST); 955 mBackgroundHandler.sendMessage(msg); 956 } 957 } 958 959 return null; 960 } 961 } 962 handleRebuildList()963 void handleRebuildList() { 964 AppFilter filter; 965 Comparator<AppEntry> comparator; 966 967 if (!mResumed) { 968 return; 969 } 970 synchronized (mRebuildSync) { 971 if (!mRebuildRequested) { 972 return; 973 } 974 975 filter = mRebuildFilter; 976 comparator = mRebuildComparator; 977 mRebuildRequested = false; 978 mRebuildFilter = null; 979 mRebuildComparator = null; 980 if (mRebuildForeground) { 981 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); 982 mRebuildForeground = false; 983 } 984 } 985 986 if (filter != null) { 987 filter.init(mContext); 988 } 989 990 final List<AppEntry> apps; 991 synchronized (mEntriesMap) { 992 apps = new ArrayList<>(mAppEntries); 993 } 994 995 ArrayMap<UserHandle, Boolean> profileHideInQuietModeStatus = new ArrayMap<>(); 996 ArrayList<AppEntry> filteredApps = new ArrayList<>(); 997 if (DEBUG) { 998 Log.i(TAG, "Rebuilding..."); 999 } 1000 for (AppEntry entry : apps) { 1001 if (android.multiuser.Flags.enablePrivateSpaceFeatures() 1002 && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) { 1003 UserHandle userHandle = UserHandle.of(UserHandle.getUserId(entry.info.uid)); 1004 if (!profileHideInQuietModeStatus.containsKey(userHandle)) { 1005 profileHideInQuietModeStatus.put( 1006 userHandle, isHideInQuietEnabledForProfile(mUm, userHandle)); 1007 } 1008 filter.refreshAppEntryOnRebuild( 1009 entry, profileHideInQuietModeStatus.get(userHandle)); 1010 } 1011 if (entry != null && (filter == null || filter.filterApp(entry))) { 1012 synchronized (mEntriesMap) { 1013 if (DEBUG_LOCKING) { 1014 Log.v(TAG, "rebuild acquired lock"); 1015 } 1016 if (comparator != null) { 1017 // Only need the label if we are going to be sorting. 1018 entry.ensureLabel(mContext); 1019 } 1020 if (DEBUG) { 1021 Log.i(TAG, "Using " + entry.info.packageName + ": " + entry); 1022 } 1023 filteredApps.add(entry); 1024 if (DEBUG_LOCKING) { 1025 Log.v(TAG, "rebuild releasing lock"); 1026 } 1027 } 1028 } 1029 } 1030 1031 if (comparator != null) { 1032 synchronized (mEntriesMap) { 1033 // Locking to ensure that the background handler does not mutate 1034 // the size of AppEntries used for ordering while sorting. 1035 Collections.sort(filteredApps, comparator); 1036 } 1037 } 1038 1039 synchronized (mRebuildSync) { 1040 if (!mRebuildRequested) { 1041 mLastAppList = filteredApps; 1042 if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) { 1043 Message msg = mMainHandler.obtainMessage( 1044 MainHandler.MSG_REBUILD_COMPLETE, this); 1045 mMainHandler.sendMessage(msg); 1046 } 1047 } 1048 } 1049 1050 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 1051 } 1052 1053 @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) onDestroy()1054 public void onDestroy() { 1055 if (!mHasLifecycle) { 1056 // TODO: Legacy, remove this later once all usages are switched to Lifecycle 1057 onPause(); 1058 } 1059 synchronized (mEntriesMap) { 1060 mSessions.remove(this); 1061 } 1062 } 1063 } 1064 1065 class MainHandler extends Handler { 1066 static final int MSG_REBUILD_COMPLETE = 1; 1067 static final int MSG_PACKAGE_LIST_CHANGED = 2; 1068 static final int MSG_PACKAGE_ICON_CHANGED = 3; 1069 static final int MSG_PACKAGE_SIZE_CHANGED = 4; 1070 static final int MSG_ALL_SIZES_COMPUTED = 5; 1071 static final int MSG_RUNNING_STATE_CHANGED = 6; 1072 static final int MSG_LAUNCHER_INFO_CHANGED = 7; 1073 static final int MSG_LOAD_ENTRIES_COMPLETE = 8; 1074 MainHandler(Looper looper)1075 public MainHandler(Looper looper) { 1076 super(looper); 1077 } 1078 1079 @Override handleMessage(Message msg)1080 public void handleMessage(Message msg) { 1081 rebuildActiveSessions(); 1082 switch (msg.what) { 1083 case MSG_REBUILD_COMPLETE: { 1084 Session s = (Session) msg.obj; 1085 for (WeakReference<Session> sessionRef : mActiveSessions) { 1086 final Session session = sessionRef.get(); 1087 if (session != null && session == s) { 1088 s.mCallbacks.onRebuildComplete(s.mLastAppList); 1089 } 1090 } 1091 } break; 1092 case MSG_PACKAGE_LIST_CHANGED: { 1093 for (WeakReference<Session> sessionRef : mActiveSessions) { 1094 final Session session = sessionRef.get(); 1095 if (session != null) { 1096 session.mCallbacks.onPackageListChanged(); 1097 } 1098 } 1099 } break; 1100 case MSG_PACKAGE_ICON_CHANGED: { 1101 for (WeakReference<Session> sessionRef : mActiveSessions) { 1102 final Session session = sessionRef.get(); 1103 if (session != null) { 1104 session.mCallbacks.onPackageIconChanged(); 1105 } 1106 } 1107 } break; 1108 case MSG_PACKAGE_SIZE_CHANGED: { 1109 for (WeakReference<Session> sessionRef : mActiveSessions) { 1110 final Session session = sessionRef.get(); 1111 if (session != null) { 1112 session.mCallbacks.onPackageSizeChanged( 1113 (String) msg.obj); 1114 } 1115 } 1116 } break; 1117 case MSG_ALL_SIZES_COMPUTED: { 1118 for (WeakReference<Session> sessionRef : mActiveSessions) { 1119 final Session session = sessionRef.get(); 1120 if (session != null) { 1121 session.mCallbacks.onAllSizesComputed(); 1122 } 1123 } 1124 } break; 1125 case MSG_RUNNING_STATE_CHANGED: { 1126 for (WeakReference<Session> sessionRef : mActiveSessions) { 1127 final Session session = sessionRef.get(); 1128 if (session != null) { 1129 session.mCallbacks.onRunningStateChanged( 1130 msg.arg1 != 0); 1131 } 1132 } 1133 } break; 1134 case MSG_LAUNCHER_INFO_CHANGED: { 1135 for (WeakReference<Session> sessionRef : mActiveSessions) { 1136 final Session session = sessionRef.get(); 1137 if (session != null) { 1138 session.mCallbacks.onLauncherInfoChanged(); 1139 } 1140 } 1141 } break; 1142 case MSG_LOAD_ENTRIES_COMPLETE: { 1143 for (WeakReference<Session> sessionRef : mActiveSessions) { 1144 final Session session = sessionRef.get(); 1145 if (session != null) { 1146 session.mCallbacks.onLoadEntriesCompleted(); 1147 } 1148 } 1149 } break; 1150 } 1151 } 1152 } 1153 1154 private class BackgroundHandler extends Handler { 1155 static final int MSG_REBUILD_LIST = 1; 1156 static final int MSG_LOAD_ENTRIES = 2; 1157 static final int MSG_LOAD_HOME_APP = 3; 1158 static final int MSG_LOAD_LAUNCHER = 4; 1159 static final int MSG_LOAD_LEANBACK_LAUNCHER = 5; 1160 static final int MSG_LOAD_ICONS = 6; 1161 static final int MSG_LOAD_SIZES = 7; 1162 1163 boolean mRunning; 1164 BackgroundHandler(Looper looper)1165 BackgroundHandler(Looper looper) { 1166 super(looper); 1167 } 1168 1169 @Override handleMessage(Message msg)1170 public void handleMessage(Message msg) { 1171 // Always try rebuilding list first thing, if needed. 1172 ArrayList<Session> rebuildingSessions = null; 1173 synchronized (mRebuildingSessions) { 1174 if (mRebuildingSessions.size() > 0) { 1175 rebuildingSessions = new ArrayList<Session>(mRebuildingSessions); 1176 mRebuildingSessions.clear(); 1177 } 1178 } 1179 if (rebuildingSessions != null) { 1180 for (Session session : rebuildingSessions) { 1181 session.handleRebuildList(); 1182 } 1183 } 1184 1185 int flags = getCombinedSessionFlags(mSessions); 1186 1187 switch (msg.what) { 1188 case MSG_REBUILD_LIST: { 1189 } break; 1190 case MSG_LOAD_ENTRIES: { 1191 int numDone = 0; 1192 synchronized (mEntriesMap) { 1193 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock"); 1194 for (int i = 0; i < mApplications.size() && numDone < 6; i++) { 1195 if (!mRunning) { 1196 mRunning = true; 1197 Message m = mMainHandler.obtainMessage( 1198 MainHandler.MSG_RUNNING_STATE_CHANGED, 1); 1199 mMainHandler.sendMessage(m); 1200 } 1201 ApplicationInfo info = mApplications.get(i); 1202 int userId = UserHandle.getUserId(info.uid); 1203 if (mEntriesMap.get(userId).get(info.packageName) == null) { 1204 numDone++; 1205 getEntryLocked(info); 1206 } 1207 if (userId != 0 && mEntriesMap.indexOfKey(0) >= 0) { 1208 // If this app is for a profile and we are on the owner, remove 1209 // the owner entry if it isn't installed. This will prevent 1210 // duplicates of work only apps showing up as 'not installed 1211 // for this user'. 1212 // Note: This depends on us traversing the users in order, which 1213 // happens because of the way we generate the list in 1214 // doResumeIfNeededLocked. 1215 AppEntry entry = mEntriesMap.get(0).get(info.packageName); 1216 if (entry != null && !hasFlag(entry.info.flags, 1217 ApplicationInfo.FLAG_INSTALLED)) { 1218 mEntriesMap.get(0).remove(info.packageName); 1219 mAppEntries.remove(entry); 1220 } 1221 } 1222 } 1223 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock"); 1224 } 1225 1226 if (numDone >= 6) { 1227 sendEmptyMessage(MSG_LOAD_ENTRIES); 1228 } else { 1229 if (!mMainHandler.hasMessages(MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) { 1230 mMainHandler.sendEmptyMessage(MainHandler.MSG_LOAD_ENTRIES_COMPLETE); 1231 } 1232 sendEmptyMessage(MSG_LOAD_HOME_APP); 1233 } 1234 } break; 1235 case MSG_LOAD_HOME_APP: { 1236 if (hasFlag(flags, FLAG_SESSION_REQUEST_HOME_APP)) { 1237 final List<ResolveInfo> homeActivities = new ArrayList<>(); 1238 mPm.getHomeActivities(homeActivities); 1239 synchronized (mEntriesMap) { 1240 final int entryCount = mEntriesMap.size(); 1241 for (int i = 0; i < entryCount; i++) { 1242 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP acquired lock"); 1243 final HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt( 1244 i); 1245 for (ResolveInfo activity : homeActivities) { 1246 String packageName = activity.activityInfo.packageName; 1247 AppEntry entry = userEntries.get(packageName); 1248 if (entry != null) { 1249 entry.isHomeApp = true; 1250 } 1251 } 1252 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP releasing lock"); 1253 } 1254 } 1255 } 1256 sendEmptyMessage(MSG_LOAD_LAUNCHER); 1257 } break; 1258 case MSG_LOAD_LAUNCHER: 1259 case MSG_LOAD_LEANBACK_LAUNCHER: { 1260 if ((msg.what == MSG_LOAD_LAUNCHER && 1261 hasFlag(flags, FLAG_SESSION_REQUEST_LAUNCHER)) 1262 || (msg.what == MSG_LOAD_LEANBACK_LAUNCHER && 1263 hasFlag(flags, FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER))) { 1264 1265 Intent launchIntent = new Intent(Intent.ACTION_MAIN, null); 1266 launchIntent.addCategory(msg.what == MSG_LOAD_LAUNCHER 1267 ? Intent.CATEGORY_LAUNCHER : Intent.CATEGORY_LEANBACK_LAUNCHER); 1268 for (int i = 0; i < mEntriesMap.size(); i++) { 1269 int userId = mEntriesMap.keyAt(i); 1270 // If we do not specify MATCH_DIRECT_BOOT_AWARE or 1271 // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags 1272 // according to the user's lock state. When the user is locked, 1273 // components with ComponentInfo#directBootAware == false will be 1274 // filtered. W should explicitly include both direct boot aware and 1275 // unaware component here. 1276 List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser( 1277 launchIntent, 1278 PackageManager.MATCH_DISABLED_COMPONENTS 1279 | PackageManager.MATCH_DIRECT_BOOT_AWARE 1280 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 1281 userId 1282 ); 1283 synchronized (mEntriesMap) { 1284 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock"); 1285 HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i); 1286 final int N = intents.size(); 1287 for (int j = 0; j < N; j++) { 1288 ResolveInfo resolveInfo = intents.get(j); 1289 String packageName = resolveInfo.activityInfo.packageName; 1290 AppEntry entry = userEntries.get(packageName); 1291 if (entry != null) { 1292 entry.hasLauncherEntry = true; 1293 entry.launcherEntryEnabled |= 1294 resolveInfo.activityInfo.enabled; 1295 } else { 1296 Log.w(TAG, "Cannot find pkg: " + packageName 1297 + " on user " + userId); 1298 } 1299 } 1300 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock"); 1301 } 1302 } 1303 1304 if (!mMainHandler.hasMessages(MainHandler.MSG_LAUNCHER_INFO_CHANGED)) { 1305 mMainHandler.sendEmptyMessage(MainHandler.MSG_LAUNCHER_INFO_CHANGED); 1306 } 1307 } 1308 if (msg.what == MSG_LOAD_LAUNCHER) { 1309 sendEmptyMessage(MSG_LOAD_LEANBACK_LAUNCHER); 1310 } else { 1311 sendEmptyMessage(MSG_LOAD_ICONS); 1312 } 1313 } break; 1314 case MSG_LOAD_ICONS: { 1315 if (hasFlag(flags, FLAG_SESSION_REQUEST_ICONS)) { 1316 int numDone = 0; 1317 synchronized (mEntriesMap) { 1318 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock"); 1319 for (int i = 0; i < mAppEntries.size() && numDone < 2; i++) { 1320 AppEntry entry = mAppEntries.get(i); 1321 if (entry.icon == null || !entry.mounted) { 1322 synchronized (entry) { 1323 if (entry.ensureIconLocked(mContext)) { 1324 if (!mRunning) { 1325 mRunning = true; 1326 Message m = mMainHandler.obtainMessage( 1327 MainHandler.MSG_RUNNING_STATE_CHANGED, 1); 1328 mMainHandler.sendMessage(m); 1329 } 1330 numDone++; 1331 } 1332 } 1333 } 1334 } 1335 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock"); 1336 } 1337 if (numDone > 0) { 1338 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) { 1339 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED); 1340 } 1341 } 1342 if (numDone >= 2) { 1343 sendEmptyMessage(MSG_LOAD_ICONS); 1344 break; 1345 } 1346 } 1347 sendEmptyMessage(MSG_LOAD_SIZES); 1348 } break; 1349 case MSG_LOAD_SIZES: { 1350 if (hasFlag(flags, FLAG_SESSION_REQUEST_SIZES)) { 1351 synchronized (mEntriesMap) { 1352 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock"); 1353 if (mCurComputingSizePkg != null) { 1354 if (DEBUG_LOCKING) { 1355 Log.v(TAG, 1356 "MSG_LOAD_SIZES releasing: currently computing"); 1357 } 1358 return; 1359 } 1360 1361 long now = SystemClock.uptimeMillis(); 1362 for (int i = 0; i < mAppEntries.size(); i++) { 1363 AppEntry entry = mAppEntries.get(i); 1364 if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_INSTALLED) 1365 && (entry.size == SIZE_UNKNOWN || entry.sizeStale)) { 1366 if (entry.sizeLoadStart == 0 || 1367 (entry.sizeLoadStart < (now - 20 * 1000))) { 1368 if (!mRunning) { 1369 mRunning = true; 1370 Message m = mMainHandler.obtainMessage( 1371 MainHandler.MSG_RUNNING_STATE_CHANGED, 1); 1372 mMainHandler.sendMessage(m); 1373 } 1374 entry.sizeLoadStart = now; 1375 mCurComputingSizeUuid = entry.info.storageUuid; 1376 mCurComputingSizePkg = entry.info.packageName; 1377 mCurComputingSizeUserId = UserHandle.getUserId( 1378 entry.info.uid); 1379 1380 mBackgroundHandler.post(() -> { 1381 try { 1382 final StorageStats stats = 1383 mStats.queryStatsForPackage( 1384 mCurComputingSizeUuid, 1385 mCurComputingSizePkg, 1386 UserHandle.of( 1387 mCurComputingSizeUserId)); 1388 final PackageStats legacy = new PackageStats( 1389 mCurComputingSizePkg, 1390 mCurComputingSizeUserId); 1391 legacy.codeSize = stats.getAppBytes(); 1392 legacy.dataSize = stats.getDataBytes(); 1393 legacy.cacheSize = stats.getCacheBytes(); 1394 try { 1395 mStatsObserver.onGetStatsCompleted(legacy, 1396 true); 1397 } catch (RemoteException ignored) { 1398 } 1399 } catch (NameNotFoundException | IOException e) { 1400 Log.w(TAG, "Failed to query stats: " + e); 1401 try { 1402 mStatsObserver.onGetStatsCompleted(null, false); 1403 } catch (RemoteException ignored) { 1404 } 1405 } 1406 1407 }); 1408 } 1409 if (DEBUG_LOCKING) { 1410 Log.v(TAG, 1411 "MSG_LOAD_SIZES releasing: now computing"); 1412 } 1413 return; 1414 } 1415 } 1416 if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) { 1417 mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED); 1418 mRunning = false; 1419 Message m = mMainHandler.obtainMessage( 1420 MainHandler.MSG_RUNNING_STATE_CHANGED, 0); 1421 mMainHandler.sendMessage(m); 1422 } 1423 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock"); 1424 } 1425 } 1426 } break; 1427 } 1428 } 1429 1430 @SessionFlags getCombinedSessionFlags(List<Session> sessions)1431 private int getCombinedSessionFlags(List<Session> sessions) { 1432 synchronized (mEntriesMap) { 1433 int flags = 0; 1434 for (Session session : sessions) { 1435 flags |= session.mFlags; 1436 } 1437 return flags; 1438 } 1439 } 1440 1441 final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() { 1442 public void onGetStatsCompleted(PackageStats stats, boolean succeeded) { 1443 if (!succeeded) { 1444 // There is no meaningful information in stats if the call failed. 1445 return; 1446 } 1447 1448 boolean sizeChanged = false; 1449 synchronized (mEntriesMap) { 1450 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock"); 1451 HashMap<String, AppEntry> userMap = mEntriesMap.get(stats.userHandle); 1452 if (userMap == null) { 1453 // The user must have been removed. 1454 return; 1455 } 1456 AppEntry entry = userMap.get(stats.packageName); 1457 if (entry != null) { 1458 synchronized (entry) { 1459 entry.sizeStale = false; 1460 entry.sizeLoadStart = 0; 1461 long externalCodeSize = stats.externalCodeSize 1462 + stats.externalObbSize; 1463 long externalDataSize = stats.externalDataSize 1464 + stats.externalMediaSize; 1465 long newSize = externalCodeSize + externalDataSize 1466 + getTotalInternalSize(stats); 1467 if (entry.size != newSize || 1468 entry.cacheSize != stats.cacheSize || 1469 entry.codeSize != stats.codeSize || 1470 entry.dataSize != stats.dataSize || 1471 entry.externalCodeSize != externalCodeSize || 1472 entry.externalDataSize != externalDataSize || 1473 entry.externalCacheSize != stats.externalCacheSize) { 1474 entry.size = newSize; 1475 entry.cacheSize = stats.cacheSize; 1476 entry.codeSize = stats.codeSize; 1477 entry.dataSize = stats.dataSize; 1478 entry.externalCodeSize = externalCodeSize; 1479 entry.externalDataSize = externalDataSize; 1480 entry.externalCacheSize = stats.externalCacheSize; 1481 entry.sizeStr = getSizeStr(entry.size); 1482 entry.internalSize = getTotalInternalSize(stats); 1483 entry.internalSizeStr = getSizeStr(entry.internalSize); 1484 entry.externalSize = getTotalExternalSize(stats); 1485 entry.externalSizeStr = getSizeStr(entry.externalSize); 1486 if (DEBUG) { 1487 Log.i(TAG, "Set size of " + entry.label + " " + entry 1488 + ": " + entry.sizeStr); 1489 } 1490 sizeChanged = true; 1491 } 1492 } 1493 if (sizeChanged) { 1494 Message msg = mMainHandler.obtainMessage( 1495 MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName); 1496 mMainHandler.sendMessage(msg); 1497 } 1498 } 1499 if (mCurComputingSizePkg != null 1500 && (mCurComputingSizePkg.equals(stats.packageName) 1501 && mCurComputingSizeUserId == stats.userHandle)) { 1502 mCurComputingSizePkg = null; 1503 sendEmptyMessage(MSG_LOAD_SIZES); 1504 } 1505 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock"); 1506 } 1507 } 1508 }; 1509 } 1510 1511 /** 1512 * Receives notifications when applications are added/removed. 1513 */ 1514 private class PackageIntentReceiver extends BroadcastReceiver { registerReceiver()1515 void registerReceiver() { 1516 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 1517 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 1518 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 1519 filter.addDataScheme("package"); 1520 mContext.registerReceiver(this, filter); 1521 // Register for events related to sdcard installation. 1522 IntentFilter sdFilter = new IntentFilter(); 1523 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); 1524 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); 1525 mContext.registerReceiver(this, sdFilter); 1526 // Register for events related to user creation/deletion. 1527 IntentFilter userFilter = new IntentFilter(); 1528 userFilter.addAction(Intent.ACTION_USER_ADDED); 1529 userFilter.addAction(Intent.ACTION_USER_REMOVED); 1530 mContext.registerReceiver(this, userFilter); 1531 } 1532 unregisterReceiver()1533 void unregisterReceiver() { 1534 mContext.unregisterReceiver(this); 1535 } 1536 1537 @Override onReceive(Context context, Intent intent)1538 public void onReceive(Context context, Intent intent) { 1539 String actionStr = intent.getAction(); 1540 if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) { 1541 Uri data = intent.getData(); 1542 String pkgName = data.getEncodedSchemeSpecificPart(); 1543 for (int i = 0; i < mEntriesMap.size(); i++) { 1544 addPackage(pkgName, mEntriesMap.keyAt(i)); 1545 } 1546 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) { 1547 Uri data = intent.getData(); 1548 String pkgName = data.getEncodedSchemeSpecificPart(); 1549 for (int i = 0; i < mEntriesMap.size(); i++) { 1550 removePackage(pkgName, mEntriesMap.keyAt(i)); 1551 } 1552 } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) { 1553 Uri data = intent.getData(); 1554 String pkgName = data.getEncodedSchemeSpecificPart(); 1555 for (int i = 0; i < mEntriesMap.size(); i++) { 1556 invalidatePackage(pkgName, mEntriesMap.keyAt(i)); 1557 } 1558 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) || 1559 Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) { 1560 // When applications become available or unavailable (perhaps because 1561 // the SD card was inserted or ejected) we need to refresh the 1562 // AppInfo with new label, icon and size information as appropriate 1563 // given the newfound (un)availability of the application. 1564 // A simple way to do that is to treat the refresh as a package 1565 // removal followed by a package addition. 1566 String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 1567 if (pkgList == null || pkgList.length == 0) { 1568 // Ignore 1569 return; 1570 } 1571 boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr); 1572 if (avail) { 1573 for (String pkgName : pkgList) { 1574 for (int i = 0; i < mEntriesMap.size(); i++) { 1575 invalidatePackage(pkgName, mEntriesMap.keyAt(i)); 1576 } 1577 } 1578 } 1579 } else if (Intent.ACTION_USER_ADDED.equals(actionStr)) { 1580 addUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL)); 1581 } else if (Intent.ACTION_USER_REMOVED.equals(actionStr)) { 1582 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL)); 1583 } 1584 } 1585 registerReceiverForClone(int cloneId)1586 public void registerReceiverForClone(int cloneId) { 1587 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 1588 filter.addDataScheme("package"); 1589 mContext.registerReceiverAsUser(this, UserHandle.of(cloneId), filter, null, null); 1590 } 1591 } 1592 1593 /** 1594 * Whether the packages for the user have been initialized. 1595 */ isUserAdded(int userId)1596 public boolean isUserAdded(int userId) { 1597 return mEntriesMap.contains(userId); 1598 } 1599 1600 public interface Callbacks { onRunningStateChanged(boolean running)1601 void onRunningStateChanged(boolean running); 1602 onPackageListChanged()1603 void onPackageListChanged(); 1604 onRebuildComplete(ArrayList<AppEntry> apps)1605 void onRebuildComplete(ArrayList<AppEntry> apps); 1606 onPackageIconChanged()1607 void onPackageIconChanged(); 1608 onPackageSizeChanged(String packageName)1609 void onPackageSizeChanged(String packageName); 1610 onAllSizesComputed()1611 void onAllSizesComputed(); 1612 onLauncherInfoChanged()1613 void onLauncherInfoChanged(); 1614 onLoadEntriesCompleted()1615 void onLoadEntriesCompleted(); 1616 } 1617 1618 public static class SizeInfo { 1619 public long cacheSize; 1620 public long codeSize; 1621 public long dataSize; 1622 public long externalCodeSize; 1623 public long externalDataSize; 1624 1625 // This is the part of externalDataSize that is in the cache 1626 // section of external storage. Note that we don't just combine 1627 // this with cacheSize because currently the platform can't 1628 // automatically trim this data when needed, so it is something 1629 // the user may need to manage. The externalDataSize also includes 1630 // this value, since what this is here is really the part of 1631 // externalDataSize that we can just consider to be "cache" files 1632 // for purposes of cleaning them up in the app details UI. 1633 public long externalCacheSize; 1634 } 1635 1636 public static class AppEntry extends SizeInfo { 1637 @VisibleForTesting String mProfileType; 1638 @Nullable public final File apkFile; 1639 public final long id; 1640 public String label; 1641 public long size; 1642 public long internalSize; 1643 public long externalSize; 1644 public String labelDescription; 1645 public boolean mounted; 1646 public boolean showInPersonalTab; 1647 1648 /** 1649 * Setting this to {@code true} prevents the entry to be filtered by 1650 * {@link #FILTER_DOWNLOADED_AND_LAUNCHER}. 1651 */ 1652 public boolean hasLauncherEntry; 1653 1654 /** 1655 * Whether the component that has a launcher intent filter is enabled. 1656 */ 1657 public boolean launcherEntryEnabled; 1658 1659 /** 1660 * Whether or not it's a Home app. 1661 */ 1662 public boolean isHomeApp; 1663 1664 /** 1665 * Whether the app should be hidden for user when quiet mode is enabled. 1666 */ 1667 public boolean hideInQuietMode; 1668 getNormalizedLabel()1669 public String getNormalizedLabel() { 1670 if (normalizedLabel != null) { 1671 return normalizedLabel; 1672 } 1673 normalizedLabel = normalize(label); 1674 return normalizedLabel; 1675 } 1676 1677 // Need to synchronize on 'this' for the following. 1678 public ApplicationInfo info; 1679 /** 1680 * Starting Android T, this field will not be used if {@link AppIconCacheManager} is 1681 * applied. 1682 */ 1683 public Drawable icon; 1684 public String sizeStr; 1685 public String internalSizeStr; 1686 public String externalSizeStr; 1687 public boolean sizeStale; 1688 public long sizeLoadStart; 1689 1690 public String normalizedLabel; 1691 1692 // A location where extra info can be placed to be used by custom filters. 1693 public Object extraInfo; 1694 1695 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) AppEntry(Context context, ApplicationInfo info, long id)1696 public AppEntry(Context context, ApplicationInfo info, long id) { 1697 this.apkFile = info.sourceDir != null ? new File(info.sourceDir) : null; 1698 this.id = id; 1699 this.info = info; 1700 this.size = SIZE_UNKNOWN; 1701 this.sizeStale = true; 1702 ensureLabel(context); 1703 // Speed up the cache of the label description if they haven't been created. 1704 if (this.labelDescription == null) { 1705 var unused = ThreadUtils.getBackgroundExecutor().submit( 1706 () -> this.ensureLabelDescriptionLocked(context)); 1707 } 1708 UserManager um = UserManager.get(context); 1709 UserInfo userInfo = um.getUserInfo(UserHandle.getUserId(info.uid)); 1710 mProfileType = userInfo.userType; 1711 this.showInPersonalTab = shouldShowInPersonalTab(um, info.uid); 1712 hideInQuietMode = shouldHideInQuietMode(um, info.uid); 1713 } 1714 isClonedProfile()1715 public boolean isClonedProfile() { 1716 return UserManager.USER_TYPE_PROFILE_CLONE.equals(mProfileType); 1717 } 1718 isManagedProfile()1719 public boolean isManagedProfile() { 1720 return UserManager.USER_TYPE_PROFILE_MANAGED.equals(mProfileType); 1721 } 1722 isPrivateProfile()1723 public boolean isPrivateProfile() { 1724 return android.os.Flags.allowPrivateProfile() 1725 && android.multiuser.Flags.enablePrivateSpaceFeatures() 1726 && UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType); 1727 } 1728 1729 /** 1730 * Checks if the user that the app belongs to have the property 1731 * {@link UserProperties#SHOW_IN_SETTINGS_WITH_PARENT} set. 1732 */ 1733 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) shouldShowInPersonalTab(UserManager userManager, int uid)1734 boolean shouldShowInPersonalTab(UserManager userManager, int uid) { 1735 int userId = UserHandle.getUserId(uid); 1736 1737 // Regardless of apk version, if the app belongs to the current user then return true. 1738 if (userId == ActivityManager.getCurrentUser()) { 1739 return true; 1740 } 1741 1742 // For sdk version < 34, if the app doesn't belong to the current user, 1743 // then as per earlier behaviour the app shouldn't be displayed in personal tab. 1744 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 1745 return false; 1746 } 1747 1748 UserProperties userProperties = userManager.getUserProperties( 1749 UserHandle.of(userId)); 1750 return userProperties.getShowInSettings() 1751 == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT; 1752 } 1753 ensureLabel(Context context)1754 public void ensureLabel(Context context) { 1755 if (this.label == null || !this.mounted) { 1756 if (this.apkFile != null && this.apkFile.exists()) { 1757 this.mounted = true; 1758 CharSequence label = info.loadLabel(context.getPackageManager()); 1759 this.label = label != null ? label.toString() : info.packageName; 1760 } else { 1761 this.mounted = false; 1762 this.label = info.packageName; 1763 } 1764 } 1765 } 1766 1767 /** 1768 * Starting Android T, this method will not be used if {@link AppIconCacheManager} is 1769 * applied. 1770 */ ensureIconLocked(Context context)1771 boolean ensureIconLocked(Context context) { 1772 if (isAppIconCacheEnabled(context)) { 1773 return false; 1774 } 1775 1776 if (this.icon == null) { 1777 if (this.apkFile != null && this.apkFile.exists()) { 1778 this.icon = Utils.getBadgedIcon(context, info); 1779 return true; 1780 } else { 1781 this.mounted = false; 1782 this.icon = context.getDrawable(R.drawable.sym_app_on_sd_unavailable_icon); 1783 } 1784 } else if (!this.mounted) { 1785 // If the app wasn't mounted but is now mounted, reload 1786 // its icon. 1787 if (this.apkFile != null && this.apkFile.exists()) { 1788 this.mounted = true; 1789 this.icon = Utils.getBadgedIcon(context, info); 1790 return true; 1791 } 1792 } 1793 return false; 1794 } 1795 getVersion(Context context)1796 public String getVersion(Context context) { 1797 try { 1798 return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName; 1799 } catch (PackageManager.NameNotFoundException e) { 1800 return ""; 1801 } 1802 } 1803 1804 /** 1805 * Get the label description which distinguishes a personal app from a work app for 1806 * accessibility purpose. If the app is in a work profile, then add a "work" prefix to the 1807 * app label. 1808 * 1809 * @param context The application context 1810 */ ensureLabelDescriptionLocked(Context context)1811 public void ensureLabelDescriptionLocked(Context context) { 1812 final int userId = UserHandle.getUserId(this.info.uid); 1813 if (UserManager.get(context).isManagedProfile(userId)) { 1814 this.labelDescription = context.getString( 1815 com.android.settingslib.utils.R 1816 .string.accessibility_work_profile_app_description, 1817 this.label); 1818 } else { 1819 this.labelDescription = this.label; 1820 } 1821 } 1822 1823 /** 1824 * Returns true if profile is in quiet mode and the profile should not be visible when the 1825 * quiet mode is enabled, false otherwise. 1826 */ shouldHideInQuietMode(@onNull UserManager userManager, int uid)1827 private boolean shouldHideInQuietMode(@NonNull UserManager userManager, int uid) { 1828 if (android.multiuser.Flags.enablePrivateSpaceFeatures() 1829 && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) { 1830 UserHandle userHandle = UserHandle.of(UserHandle.getUserId(uid)); 1831 return isHideInQuietEnabledForProfile(userManager, userHandle); 1832 } 1833 return false; 1834 } 1835 } 1836 hasFlag(int flags, int flag)1837 private static boolean hasFlag(int flags, int flag) { 1838 return (flags & flag) != 0; 1839 } 1840 isHideInQuietEnabledForProfile( UserManager userManager, UserHandle userHandle)1841 private static boolean isHideInQuietEnabledForProfile( 1842 UserManager userManager, UserHandle userHandle) { 1843 return userManager.isQuietModeEnabled(userHandle) 1844 && userManager.getUserProperties(userHandle).getShowInQuietMode() 1845 == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN; 1846 } 1847 1848 /** 1849 * Compare by label, then package name, then uid. 1850 */ 1851 public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() { 1852 private final Collator sCollator = Collator.getInstance(); 1853 1854 @Override 1855 public int compare(AppEntry object1, AppEntry object2) { 1856 int compareResult = sCollator.compare(object1.label, object2.label); 1857 if (compareResult != 0) { 1858 return compareResult; 1859 } 1860 if (object1.info != null && object2.info != null) { 1861 compareResult = 1862 sCollator.compare(object1.info.packageName, object2.info.packageName); 1863 if (compareResult != 0) { 1864 return compareResult; 1865 } 1866 } 1867 1868 return object1.info.uid - object2.info.uid; 1869 } 1870 }; 1871 1872 public static final Comparator<AppEntry> SIZE_COMPARATOR 1873 = new Comparator<AppEntry>() { 1874 @Override 1875 public int compare(AppEntry object1, AppEntry object2) { 1876 if (object1.size < object2.size) return 1; 1877 if (object1.size > object2.size) return -1; 1878 return ALPHA_COMPARATOR.compare(object1, object2); 1879 } 1880 }; 1881 1882 public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR 1883 = new Comparator<AppEntry>() { 1884 @Override 1885 public int compare(AppEntry object1, AppEntry object2) { 1886 if (object1.internalSize < object2.internalSize) return 1; 1887 if (object1.internalSize > object2.internalSize) return -1; 1888 return ALPHA_COMPARATOR.compare(object1, object2); 1889 } 1890 }; 1891 1892 public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR 1893 = new Comparator<AppEntry>() { 1894 @Override 1895 public int compare(AppEntry object1, AppEntry object2) { 1896 if (object1.externalSize < object2.externalSize) return 1; 1897 if (object1.externalSize > object2.externalSize) return -1; 1898 return ALPHA_COMPARATOR.compare(object1, object2); 1899 } 1900 }; 1901 1902 public interface AppFilter { init()1903 void init(); 1904 init(Context context)1905 default void init(Context context) { 1906 init(); 1907 } 1908 filterApp(AppEntry info)1909 boolean filterApp(AppEntry info); 1910 1911 /** 1912 * Updates AppEntry based on whether quiet mode is enabled and should not be 1913 * visible for the corresponding profile. 1914 */ refreshAppEntryOnRebuild( @onNull AppEntry appEntry, boolean hideInQuietMode)1915 default void refreshAppEntryOnRebuild( 1916 @NonNull AppEntry appEntry, 1917 boolean hideInQuietMode) { 1918 } 1919 } 1920 1921 public static final AppFilter FILTER_PERSONAL = new AppFilter() { 1922 private int mCurrentUser; 1923 1924 @Override 1925 public void init() { 1926 mCurrentUser = ActivityManager.getCurrentUser(); 1927 } 1928 1929 @Override 1930 public boolean filterApp(AppEntry entry) { 1931 return entry.showInPersonalTab; 1932 } 1933 }; 1934 1935 public static final AppFilter FILTER_WITHOUT_DISABLED_UNTIL_USED = new AppFilter() { 1936 @Override 1937 public void init() { 1938 // do nothing 1939 } 1940 1941 @Override 1942 public boolean filterApp(AppEntry entry) { 1943 return entry.info.enabledSetting 1944 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; 1945 } 1946 }; 1947 1948 public static final AppFilter FILTER_WORK = new AppFilter() { 1949 1950 @Override 1951 public void init() {} 1952 1953 @Override 1954 public boolean filterApp(AppEntry entry) { 1955 return !entry.showInPersonalTab && entry.isManagedProfile(); 1956 } 1957 }; 1958 1959 public static final AppFilter FILTER_PRIVATE_PROFILE = new AppFilter() { 1960 1961 @Override 1962 public void init() {} 1963 1964 @Override 1965 public boolean filterApp(AppEntry entry) { 1966 return !entry.showInPersonalTab && entry.isPrivateProfile(); 1967 } 1968 }; 1969 1970 /** 1971 * Displays a combined list with "downloaded" and "visible in launcher" apps only. 1972 */ 1973 public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER = new AppFilter() { 1974 @Override 1975 public void init() { 1976 } 1977 1978 @Override 1979 public boolean filterApp(AppEntry entry) { 1980 if (AppUtils.isInstant(entry.info)) { 1981 return false; 1982 } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { 1983 return true; 1984 } else if (!hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM)) { 1985 return true; 1986 } else if (entry.hasLauncherEntry) { 1987 return true; 1988 } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM) && entry.isHomeApp) { 1989 return true; 1990 } 1991 return false; 1992 } 1993 }; 1994 1995 /** 1996 * Displays a combined list with "downloaded" and "visible in launcher" apps which belong to a 1997 * user which is either not in quiet mode or allows showing apps even when in quiet mode. 1998 */ 1999 public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET = new AppFilter() { 2000 @Override 2001 public void init() { 2002 } 2003 2004 @Override 2005 public boolean filterApp(@NonNull AppEntry entry) { 2006 if (entry.hideInQuietMode) { 2007 return false; 2008 } 2009 if (AppUtils.isInstant(entry.info)) { 2010 return false; 2011 } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { 2012 return true; 2013 } else if (!hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM)) { 2014 return true; 2015 } else if (entry.hasLauncherEntry) { 2016 return true; 2017 } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM) && entry.isHomeApp) { 2018 return true; 2019 } 2020 return false; 2021 } 2022 2023 @Override 2024 public void refreshAppEntryOnRebuild(@NonNull AppEntry appEntry, boolean hideInQuietMode) { 2025 appEntry.hideInQuietMode = hideInQuietMode; 2026 } 2027 }; 2028 2029 /** 2030 * Displays a combined list with "downloaded" and "visible in launcher" apps only. 2031 */ 2032 public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT = new AppFilter() { 2033 2034 @Override 2035 public void init() { 2036 } 2037 2038 @Override 2039 public boolean filterApp(AppEntry entry) { 2040 return AppUtils.isInstant(entry.info) 2041 || FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(entry); 2042 } 2043 2044 }; 2045 2046 public static final AppFilter FILTER_THIRD_PARTY = new AppFilter() { 2047 @Override 2048 public void init() { 2049 } 2050 2051 @Override 2052 public boolean filterApp(AppEntry entry) { 2053 if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { 2054 return true; 2055 } else if (!hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM)) { 2056 return true; 2057 } 2058 return false; 2059 } 2060 }; 2061 2062 public static final AppFilter FILTER_DISABLED = new AppFilter() { 2063 @Override 2064 public void init() { 2065 } 2066 2067 @Override 2068 public boolean filterApp(AppEntry entry) { 2069 return !entry.info.enabled && !AppUtils.isInstant(entry.info); 2070 } 2071 }; 2072 2073 public static final AppFilter FILTER_INSTANT = new AppFilter() { 2074 @Override 2075 public void init() { 2076 } 2077 2078 @Override 2079 public boolean filterApp(AppEntry entry) { 2080 return AppUtils.isInstant(entry.info); 2081 } 2082 }; 2083 2084 public static final AppFilter FILTER_ALL_ENABLED = new AppFilter() { 2085 @Override 2086 public void init() { 2087 } 2088 2089 @Override 2090 public boolean filterApp(AppEntry entry) { 2091 return entry.info.enabled && !AppUtils.isInstant(entry.info); 2092 } 2093 }; 2094 2095 public static final AppFilter FILTER_ENABLED_NOT_QUIET = new AppFilter() { 2096 @Override 2097 public void init() { 2098 } 2099 2100 @Override 2101 public boolean filterApp(@NonNull AppEntry entry) { 2102 return entry.info.enabled && !AppUtils.isInstant(entry.info) 2103 && !entry.hideInQuietMode; 2104 } 2105 2106 @Override 2107 public void refreshAppEntryOnRebuild( 2108 @NonNull AppEntry appEntry, 2109 boolean hideInQuietMode) { 2110 appEntry.hideInQuietMode = hideInQuietMode; 2111 } 2112 }; 2113 2114 public static final AppFilter FILTER_EVERYTHING = new AppFilter() { 2115 @Override 2116 public void init() { 2117 } 2118 2119 @Override 2120 public boolean filterApp(AppEntry entry) { 2121 return true; 2122 } 2123 }; 2124 2125 public static final AppFilter FILTER_WITH_DOMAIN_URLS = new AppFilter() { 2126 @Override 2127 public void init() { 2128 } 2129 2130 @Override 2131 public boolean filterApp(AppEntry entry) { 2132 return !AppUtils.isInstant(entry.info) 2133 && hasFlag(entry.info.privateFlags, 2134 ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) 2135 && !entry.hideInQuietMode; 2136 } 2137 2138 @Override 2139 public void refreshAppEntryOnRebuild(@NonNull AppEntry appEntry, boolean hideInQuietMode) { 2140 appEntry.hideInQuietMode = hideInQuietMode; 2141 } 2142 }; 2143 2144 public static final AppFilter FILTER_NOT_HIDE = new AppFilter() { 2145 private String[] mHidePackageNames; 2146 2147 @Override 2148 public void init(Context context) { 2149 mHidePackageNames = context.getResources() 2150 .getStringArray(R.array.config_hideWhenDisabled_packageNames); 2151 } 2152 2153 @Override 2154 public void init() { 2155 } 2156 2157 @Override 2158 public boolean filterApp(AppEntry entry) { 2159 if (ArrayUtils.contains(mHidePackageNames, entry.info.packageName)) { 2160 if (!entry.info.enabled) { 2161 return false; 2162 } else if (entry.info.enabledSetting == 2163 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { 2164 return false; 2165 } 2166 } 2167 2168 return true; 2169 } 2170 }; 2171 2172 public static final AppFilter FILTER_GAMES = new AppFilter() { 2173 @Override 2174 public void init() { 2175 } 2176 2177 @Override 2178 public boolean filterApp(ApplicationsState.AppEntry info) { 2179 // TODO: Update for the new game category. 2180 boolean isGame; 2181 synchronized (info.info) { 2182 isGame = hasFlag(info.info.flags, ApplicationInfo.FLAG_IS_GAME) 2183 || info.info.category == ApplicationInfo.CATEGORY_GAME; 2184 } 2185 return isGame; 2186 } 2187 }; 2188 2189 public static class VolumeFilter implements AppFilter { 2190 private final String mVolumeUuid; 2191 VolumeFilter(String volumeUuid)2192 public VolumeFilter(String volumeUuid) { 2193 mVolumeUuid = volumeUuid; 2194 } 2195 2196 @Override init()2197 public void init() { 2198 } 2199 2200 @Override filterApp(AppEntry info)2201 public boolean filterApp(AppEntry info) { 2202 return Objects.equals(info.info.volumeUuid, mVolumeUuid); 2203 } 2204 } 2205 2206 public static class CompoundFilter implements AppFilter { 2207 private final AppFilter mFirstFilter; 2208 private final AppFilter mSecondFilter; 2209 CompoundFilter(AppFilter first, AppFilter second)2210 public CompoundFilter(AppFilter first, AppFilter second) { 2211 mFirstFilter = first; 2212 mSecondFilter = second; 2213 } 2214 2215 @Override init(Context context)2216 public void init(Context context) { 2217 mFirstFilter.init(context); 2218 mSecondFilter.init(context); 2219 } 2220 2221 @Override init()2222 public void init() { 2223 mFirstFilter.init(); 2224 mSecondFilter.init(); 2225 } 2226 2227 @Override filterApp(AppEntry info)2228 public boolean filterApp(AppEntry info) { 2229 return mFirstFilter.filterApp(info) && mSecondFilter.filterApp(info); 2230 } 2231 } 2232 2233 public static final AppFilter FILTER_AUDIO = new AppFilter() { 2234 @Override 2235 public void init() { 2236 } 2237 2238 @Override 2239 public boolean filterApp(AppEntry entry) { 2240 boolean isMusicApp; 2241 synchronized (entry) { 2242 isMusicApp = entry.info.category == ApplicationInfo.CATEGORY_AUDIO; 2243 } 2244 return isMusicApp; 2245 } 2246 }; 2247 2248 public static final AppFilter FILTER_MOVIES = new AppFilter() { 2249 @Override 2250 public void init() { 2251 } 2252 2253 @Override 2254 public boolean filterApp(AppEntry entry) { 2255 boolean isMovieApp; 2256 synchronized (entry) { 2257 isMovieApp = entry.info.category == ApplicationInfo.CATEGORY_VIDEO; 2258 } 2259 return isMovieApp; 2260 } 2261 }; 2262 2263 public static final AppFilter FILTER_PHOTOS = 2264 new AppFilter() { 2265 @Override 2266 public void init() { 2267 } 2268 2269 @Override 2270 public boolean filterApp(AppEntry entry) { 2271 boolean isPhotosApp; 2272 synchronized (entry) { 2273 isPhotosApp = entry.info.category == ApplicationInfo.CATEGORY_IMAGE; 2274 } 2275 return isPhotosApp; 2276 } 2277 }; 2278 2279 /* For the Storage Settings which shows category of app types. */ 2280 public static final AppFilter FILTER_OTHER_APPS = 2281 new AppFilter() { 2282 @Override 2283 public void init() { 2284 } 2285 2286 @Override 2287 public boolean filterApp(AppEntry entry) { 2288 boolean isCategorized; 2289 synchronized (entry) { 2290 isCategorized = 2291 FILTER_AUDIO.filterApp(entry) 2292 || FILTER_GAMES.filterApp(entry) 2293 || FILTER_MOVIES.filterApp(entry) 2294 || FILTER_PHOTOS.filterApp(entry); 2295 } 2296 return !isCategorized; 2297 } 2298 }; 2299 2300 /* For the Storage Settings which shows category of file types. */ 2301 public static final AppFilter FILTER_APPS_EXCEPT_GAMES = 2302 new AppFilter() { 2303 @Override 2304 public void init() { 2305 } 2306 2307 @Override 2308 public boolean filterApp(AppEntry entry) { 2309 boolean isCategorized; 2310 synchronized (entry) { 2311 isCategorized = FILTER_GAMES.filterApp(entry); 2312 } 2313 return !isCategorized; 2314 } 2315 }; 2316 } 2317