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