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