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