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