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