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