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