1 package com.android.settings.applications; 2 3 import android.app.Application; 4 import android.content.BroadcastReceiver; 5 import android.content.Context; 6 import android.content.Intent; 7 import android.content.IntentFilter; 8 import android.content.pm.ApplicationInfo; 9 import android.content.pm.IPackageStatsObserver; 10 import android.content.pm.PackageManager; 11 import android.content.pm.PackageStats; 12 import android.content.pm.PackageManager.NameNotFoundException; 13 import android.graphics.drawable.Drawable; 14 import android.net.Uri; 15 import android.os.Handler; 16 import android.os.HandlerThread; 17 import android.os.Looper; 18 import android.os.Message; 19 import android.os.Process; 20 import android.os.SystemClock; 21 import android.os.UserHandle; 22 import android.text.format.Formatter; 23 import android.util.Log; 24 25 import java.io.File; 26 import java.text.Collator; 27 import java.text.Normalizer; 28 import java.text.Normalizer.Form; 29 import java.util.ArrayList; 30 import java.util.Collections; 31 import java.util.Comparator; 32 import java.util.HashMap; 33 import java.util.List; 34 import java.util.regex.Pattern; 35 36 /** 37 * Keeps track of information about all installed applications, lazy-loading 38 * as needed. 39 */ 40 public class ApplicationsState { 41 static final String TAG = "ApplicationsState"; 42 static final boolean DEBUG = false; 43 static final boolean DEBUG_LOCKING = false; 44 45 public static interface Callbacks { 46 public void onRunningStateChanged(boolean running); 47 public void onPackageListChanged(); 48 public void onRebuildComplete(ArrayList<AppEntry> apps); 49 public void onPackageIconChanged(); 50 public void onPackageSizeChanged(String packageName); 51 public void onAllSizesComputed(); 52 } 53 54 public static interface AppFilter { 55 public void init(); 56 public boolean filterApp(ApplicationInfo info); 57 } 58 59 static final int SIZE_UNKNOWN = -1; 60 static final int SIZE_INVALID = -2; 61 62 static final Pattern REMOVE_DIACRITICALS_PATTERN 63 = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); 64 65 public static String normalize(String str) { 66 String tmp = Normalizer.normalize(str, Form.NFD); 67 return REMOVE_DIACRITICALS_PATTERN.matcher(tmp) 68 .replaceAll("").toLowerCase(); 69 } 70 71 public static class SizeInfo { 72 long cacheSize; 73 long codeSize; 74 long dataSize; 75 long externalCodeSize; 76 long externalDataSize; 77 78 // This is the part of externalDataSize that is in the cache 79 // section of external storage. Note that we don't just combine 80 // this with cacheSize because currently the platform can't 81 // automatically trim this data when needed, so it is something 82 // the user may need to manage. The externalDataSize also includes 83 // this value, since what this is here is really the part of 84 // externalDataSize that we can just consider to be "cache" files 85 // for purposes of cleaning them up in the app details UI. 86 long externalCacheSize; 87 } 88 89 public static class AppEntry extends SizeInfo { 90 final File apkFile; 91 final long id; 92 String label; 93 long size; 94 long internalSize; 95 long externalSize; 96 97 boolean mounted; 98 99 String getNormalizedLabel() { 100 if (normalizedLabel != null) { 101 return normalizedLabel; 102 } 103 normalizedLabel = normalize(label); 104 return normalizedLabel; 105 } 106 107 // Need to synchronize on 'this' for the following. 108 ApplicationInfo info; 109 Drawable icon; 110 String sizeStr; 111 String internalSizeStr; 112 String externalSizeStr; 113 boolean sizeStale; 114 long sizeLoadStart; 115 116 String normalizedLabel; 117 118 AppEntry(Context context, ApplicationInfo info, long id) { 119 apkFile = new File(info.sourceDir); 120 this.id = id; 121 this.info = info; 122 this.size = SIZE_UNKNOWN; 123 this.sizeStale = true; 124 ensureLabel(context); 125 } 126 127 void ensureLabel(Context context) { 128 if (this.label == null || !this.mounted) { 129 if (!this.apkFile.exists()) { 130 this.mounted = false; 131 this.label = info.packageName; 132 } else { 133 this.mounted = true; 134 CharSequence label = info.loadLabel(context.getPackageManager()); 135 this.label = label != null ? label.toString() : info.packageName; 136 } 137 } 138 } 139 140 boolean ensureIconLocked(Context context, PackageManager pm) { 141 if (this.icon == null) { 142 if (this.apkFile.exists()) { 143 this.icon = this.info.loadIcon(pm); 144 return true; 145 } else { 146 this.mounted = false; 147 this.icon = context.getResources().getDrawable( 148 com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon); 149 } 150 } else if (!this.mounted) { 151 // If the app wasn't mounted but is now mounted, reload 152 // its icon. 153 if (this.apkFile.exists()) { 154 this.mounted = true; 155 this.icon = this.info.loadIcon(pm); 156 return true; 157 } 158 } 159 return false; 160 } 161 } 162 163 public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() { 164 private final Collator sCollator = Collator.getInstance(); 165 @Override 166 public int compare(AppEntry object1, AppEntry object2) { 167 final boolean normal1 = object1.info.enabled 168 && (object1.info.flags&ApplicationInfo.FLAG_INSTALLED) != 0; 169 final boolean normal2 = object2.info.enabled 170 && (object2.info.flags&ApplicationInfo.FLAG_INSTALLED) != 0; 171 if (normal1 != normal2) { 172 return normal1 ? -1 : 1; 173 } 174 return sCollator.compare(object1.label, object2.label); 175 } 176 }; 177 178 public static final Comparator<AppEntry> SIZE_COMPARATOR 179 = new Comparator<AppEntry>() { 180 private final Collator sCollator = Collator.getInstance(); 181 @Override 182 public int compare(AppEntry object1, AppEntry object2) { 183 if (object1.size < object2.size) return 1; 184 if (object1.size > object2.size) return -1; 185 return sCollator.compare(object1.label, object2.label); 186 } 187 }; 188 189 public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR 190 = new Comparator<AppEntry>() { 191 private final Collator sCollator = Collator.getInstance(); 192 @Override 193 public int compare(AppEntry object1, AppEntry object2) { 194 if (object1.internalSize < object2.internalSize) return 1; 195 if (object1.internalSize > object2.internalSize) return -1; 196 return sCollator.compare(object1.label, object2.label); 197 } 198 }; 199 200 public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR 201 = new Comparator<AppEntry>() { 202 private final Collator sCollator = Collator.getInstance(); 203 @Override 204 public int compare(AppEntry object1, AppEntry object2) { 205 if (object1.externalSize < object2.externalSize) return 1; 206 if (object1.externalSize > object2.externalSize) return -1; 207 return sCollator.compare(object1.label, object2.label); 208 } 209 }; 210 211 public static final AppFilter THIRD_PARTY_FILTER = new AppFilter() { 212 public void init() { 213 } 214 215 @Override 216 public boolean filterApp(ApplicationInfo info) { 217 if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { 218 return true; 219 } else if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { 220 return true; 221 } 222 return false; 223 } 224 }; 225 226 public static final AppFilter ON_SD_CARD_FILTER = new AppFilter() { 227 final CanBeOnSdCardChecker mCanBeOnSdCardChecker 228 = new CanBeOnSdCardChecker(); 229 230 public void init() { 231 mCanBeOnSdCardChecker.init(); 232 } 233 234 @Override 235 public boolean filterApp(ApplicationInfo info) { 236 return mCanBeOnSdCardChecker.check(info); 237 } 238 }; 239 240 public static final AppFilter DISABLED_FILTER = new AppFilter() { 241 public void init() { 242 } 243 244 @Override 245 public boolean filterApp(ApplicationInfo info) { 246 if (!info.enabled) { 247 return true; 248 } 249 return false; 250 } 251 }; 252 253 public static final AppFilter ALL_ENABLED_FILTER = new AppFilter() { 254 public void init() { 255 } 256 257 @Override 258 public boolean filterApp(ApplicationInfo info) { 259 if (info.enabled) { 260 return true; 261 } 262 return false; 263 } 264 }; 265 266 final Context mContext; 267 final PackageManager mPm; 268 final int mRetrieveFlags; 269 PackageIntentReceiver mPackageIntentReceiver; 270 271 boolean mResumed; 272 boolean mHaveDisabledApps; 273 274 // Information about all applications. Synchronize on mEntriesMap 275 // to protect access to these. 276 final ArrayList<Session> mSessions = new ArrayList<Session>(); 277 final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>(); 278 final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); 279 final HashMap<String, AppEntry> mEntriesMap = new HashMap<String, AppEntry>(); 280 final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>(); 281 List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>(); 282 long mCurId = 1; 283 String mCurComputingSizePkg; 284 boolean mSessionsChanged; 285 286 // Temporary for dispatching session callbacks. Only touched by main thread. 287 final ArrayList<Session> mActiveSessions = new ArrayList<Session>(); 288 289 /** 290 * Receives notifications when applications are added/removed. 291 */ 292 private class PackageIntentReceiver extends BroadcastReceiver { 293 void registerReceiver() { 294 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 295 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 296 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 297 filter.addDataScheme("package"); 298 mContext.registerReceiver(this, filter); 299 // Register for events related to sdcard installation. 300 IntentFilter sdFilter = new IntentFilter(); 301 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); 302 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); 303 mContext.registerReceiver(this, sdFilter); 304 } 305 void unregisterReceiver() { 306 mContext.unregisterReceiver(this); 307 } 308 @Override 309 public void onReceive(Context context, Intent intent) { 310 String actionStr = intent.getAction(); 311 if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) { 312 Uri data = intent.getData(); 313 String pkgName = data.getEncodedSchemeSpecificPart(); 314 addPackage(pkgName); 315 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) { 316 Uri data = intent.getData(); 317 String pkgName = data.getEncodedSchemeSpecificPart(); 318 removePackage(pkgName); 319 } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) { 320 Uri data = intent.getData(); 321 String pkgName = data.getEncodedSchemeSpecificPart(); 322 invalidatePackage(pkgName); 323 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) || 324 Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) { 325 // When applications become available or unavailable (perhaps because 326 // the SD card was inserted or ejected) we need to refresh the 327 // AppInfo with new label, icon and size information as appropriate 328 // given the newfound (un)availability of the application. 329 // A simple way to do that is to treat the refresh as a package 330 // removal followed by a package addition. 331 String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 332 if (pkgList == null || pkgList.length == 0) { 333 // Ignore 334 return; 335 } 336 boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr); 337 if (avail) { 338 for (String pkgName : pkgList) { 339 invalidatePackage(pkgName); 340 } 341 } 342 } 343 } 344 } 345 346 void rebuildActiveSessions() { 347 synchronized (mEntriesMap) { 348 if (!mSessionsChanged) { 349 return; 350 } 351 mActiveSessions.clear(); 352 for (int i=0; i<mSessions.size(); i++) { 353 Session s = mSessions.get(i); 354 if (s.mResumed) { 355 mActiveSessions.add(s); 356 } 357 } 358 } 359 } 360 361 class MainHandler extends Handler { 362 static final int MSG_REBUILD_COMPLETE = 1; 363 static final int MSG_PACKAGE_LIST_CHANGED = 2; 364 static final int MSG_PACKAGE_ICON_CHANGED = 3; 365 static final int MSG_PACKAGE_SIZE_CHANGED = 4; 366 static final int MSG_ALL_SIZES_COMPUTED = 5; 367 static final int MSG_RUNNING_STATE_CHANGED = 6; 368 369 @Override 370 public void handleMessage(Message msg) { 371 rebuildActiveSessions(); 372 switch (msg.what) { 373 case MSG_REBUILD_COMPLETE: { 374 Session s = (Session)msg.obj; 375 if (mActiveSessions.contains(s)) { 376 s.mCallbacks.onRebuildComplete(s.mLastAppList); 377 } 378 } break; 379 case MSG_PACKAGE_LIST_CHANGED: { 380 for (int i=0; i<mActiveSessions.size(); i++) { 381 mActiveSessions.get(i).mCallbacks.onPackageListChanged(); 382 } 383 } break; 384 case MSG_PACKAGE_ICON_CHANGED: { 385 for (int i=0; i<mActiveSessions.size(); i++) { 386 mActiveSessions.get(i).mCallbacks.onPackageIconChanged(); 387 } 388 } break; 389 case MSG_PACKAGE_SIZE_CHANGED: { 390 for (int i=0; i<mActiveSessions.size(); i++) { 391 mActiveSessions.get(i).mCallbacks.onPackageSizeChanged( 392 (String)msg.obj); 393 } 394 } break; 395 case MSG_ALL_SIZES_COMPUTED: { 396 for (int i=0; i<mActiveSessions.size(); i++) { 397 mActiveSessions.get(i).mCallbacks.onAllSizesComputed(); 398 } 399 } break; 400 case MSG_RUNNING_STATE_CHANGED: { 401 for (int i=0; i<mActiveSessions.size(); i++) { 402 mActiveSessions.get(i).mCallbacks.onRunningStateChanged( 403 msg.arg1 != 0); 404 } 405 } break; 406 } 407 } 408 } 409 410 final MainHandler mMainHandler = new MainHandler(); 411 412 // -------------------------------------------------------------- 413 414 static final Object sLock = new Object(); 415 static ApplicationsState sInstance; 416 417 static ApplicationsState getInstance(Application app) { 418 synchronized (sLock) { 419 if (sInstance == null) { 420 sInstance = new ApplicationsState(app); 421 } 422 return sInstance; 423 } 424 } 425 426 private ApplicationsState(Application app) { 427 mContext = app; 428 mPm = mContext.getPackageManager(); 429 mThread = new HandlerThread("ApplicationsState.Loader", 430 Process.THREAD_PRIORITY_BACKGROUND); 431 mThread.start(); 432 mBackgroundHandler = new BackgroundHandler(mThread.getLooper()); 433 434 // Only the owner can see all apps. 435 if (UserHandle.myUserId() == 0) { 436 mRetrieveFlags = PackageManager.GET_UNINSTALLED_PACKAGES | 437 PackageManager.GET_DISABLED_COMPONENTS | 438 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS; 439 } else { 440 mRetrieveFlags = PackageManager.GET_DISABLED_COMPONENTS | 441 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS; 442 } 443 444 /** 445 * This is a trick to prevent the foreground thread from being delayed. 446 * The problem is that Dalvik monitors are initially spin locks, to keep 447 * them lightweight. This leads to unfair contention -- Even though the 448 * background thread only holds the lock for a short amount of time, if 449 * it keeps running and locking again it can prevent the main thread from 450 * acquiring its lock for a long time... sometimes even > 5 seconds 451 * (leading to an ANR). 452 * 453 * Dalvik will promote a monitor to a "real" lock if it detects enough 454 * contention on it. It doesn't figure this out fast enough for us 455 * here, though, so this little trick will force it to turn into a real 456 * lock immediately. 457 */ 458 synchronized (mEntriesMap) { 459 try { 460 mEntriesMap.wait(1); 461 } catch (InterruptedException e) { 462 } 463 } 464 } 465 466 public class Session { 467 final Callbacks mCallbacks; 468 boolean mResumed; 469 470 // Rebuilding of app list. Synchronized on mRebuildSync. 471 final Object mRebuildSync = new Object(); 472 boolean mRebuildRequested; 473 boolean mRebuildAsync; 474 AppFilter mRebuildFilter; 475 Comparator<AppEntry> mRebuildComparator; 476 ArrayList<AppEntry> mRebuildResult; 477 ArrayList<AppEntry> mLastAppList; 478 479 Session(Callbacks callbacks) { 480 mCallbacks = callbacks; 481 } 482 483 public void resume() { 484 if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock..."); 485 synchronized (mEntriesMap) { 486 if (!mResumed) { 487 mResumed = true; 488 mSessionsChanged = true; 489 doResumeIfNeededLocked(); 490 } 491 } 492 if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock"); 493 } 494 495 public void pause() { 496 if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock..."); 497 synchronized (mEntriesMap) { 498 if (mResumed) { 499 mResumed = false; 500 mSessionsChanged = true; 501 mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this); 502 doPauseIfNeededLocked(); 503 } 504 if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock"); 505 } 506 } 507 508 // Creates a new list of app entries with the given filter and comparator. 509 ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) { 510 synchronized (mRebuildSync) { 511 synchronized (mEntriesMap) { 512 mRebuildingSessions.add(this); 513 mRebuildRequested = true; 514 mRebuildAsync = false; 515 mRebuildFilter = filter; 516 mRebuildComparator = comparator; 517 mRebuildResult = null; 518 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) { 519 Message msg = mBackgroundHandler.obtainMessage( 520 BackgroundHandler.MSG_REBUILD_LIST); 521 mBackgroundHandler.sendMessage(msg); 522 } 523 } 524 525 // We will wait for .25s for the list to be built. 526 long waitend = SystemClock.uptimeMillis()+250; 527 528 while (mRebuildResult == null) { 529 long now = SystemClock.uptimeMillis(); 530 if (now >= waitend) { 531 break; 532 } 533 try { 534 mRebuildSync.wait(waitend - now); 535 } catch (InterruptedException e) { 536 } 537 } 538 539 mRebuildAsync = true; 540 541 return mRebuildResult; 542 } 543 } 544 545 void handleRebuildList() { 546 AppFilter filter; 547 Comparator<AppEntry> comparator; 548 synchronized (mRebuildSync) { 549 if (!mRebuildRequested) { 550 return; 551 } 552 553 filter = mRebuildFilter; 554 comparator = mRebuildComparator; 555 mRebuildRequested = false; 556 mRebuildFilter = null; 557 mRebuildComparator = null; 558 } 559 560 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); 561 562 if (filter != null) { 563 filter.init(); 564 } 565 566 List<ApplicationInfo> apps; 567 synchronized (mEntriesMap) { 568 apps = new ArrayList<ApplicationInfo>(mApplications); 569 } 570 571 ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>(); 572 if (DEBUG) Log.i(TAG, "Rebuilding..."); 573 for (int i=0; i<apps.size(); i++) { 574 ApplicationInfo info = apps.get(i); 575 if (filter == null || filter.filterApp(info)) { 576 synchronized (mEntriesMap) { 577 if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock"); 578 AppEntry entry = getEntryLocked(info); 579 entry.ensureLabel(mContext); 580 if (DEBUG) Log.i(TAG, "Using " + info.packageName + ": " + entry); 581 filteredApps.add(entry); 582 if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock"); 583 } 584 } 585 } 586 587 Collections.sort(filteredApps, comparator); 588 589 synchronized (mRebuildSync) { 590 if (!mRebuildRequested) { 591 mLastAppList = filteredApps; 592 if (!mRebuildAsync) { 593 mRebuildResult = filteredApps; 594 mRebuildSync.notifyAll(); 595 } else { 596 if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) { 597 Message msg = mMainHandler.obtainMessage( 598 MainHandler.MSG_REBUILD_COMPLETE, this); 599 mMainHandler.sendMessage(msg); 600 } 601 } 602 } 603 } 604 605 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 606 } 607 608 public void release() { 609 pause(); 610 synchronized (mEntriesMap) { 611 mSessions.remove(this); 612 } 613 } 614 } 615 616 public Session newSession(Callbacks callbacks) { 617 Session s = new Session(callbacks); 618 synchronized (mEntriesMap) { 619 mSessions.add(s); 620 } 621 return s; 622 } 623 624 void doResumeIfNeededLocked() { 625 if (mResumed) { 626 return; 627 } 628 mResumed = true; 629 if (mPackageIntentReceiver == null) { 630 mPackageIntentReceiver = new PackageIntentReceiver(); 631 mPackageIntentReceiver.registerReceiver(); 632 } 633 mApplications = mPm.getInstalledApplications(mRetrieveFlags); 634 if (mApplications == null) { 635 mApplications = new ArrayList<ApplicationInfo>(); 636 } 637 638 if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { 639 // If an interesting part of the configuration has changed, we 640 // should completely reload the app entries. 641 mEntriesMap.clear(); 642 mAppEntries.clear(); 643 } else { 644 for (int i=0; i<mAppEntries.size(); i++) { 645 mAppEntries.get(i).sizeStale = true; 646 } 647 } 648 649 mHaveDisabledApps = false; 650 for (int i=0; i<mApplications.size(); i++) { 651 final ApplicationInfo info = mApplications.get(i); 652 // Need to trim out any applications that are disabled by 653 // something different than the user. 654 if (!info.enabled) { 655 if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { 656 mApplications.remove(i); 657 i--; 658 continue; 659 } 660 mHaveDisabledApps = true; 661 } 662 final AppEntry entry = mEntriesMap.get(info.packageName); 663 if (entry != null) { 664 entry.info = info; 665 } 666 } 667 mCurComputingSizePkg = null; 668 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { 669 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); 670 } 671 } 672 673 public boolean haveDisabledApps() { 674 return mHaveDisabledApps; 675 } 676 677 void doPauseIfNeededLocked() { 678 if (!mResumed) { 679 return; 680 } 681 for (int i=0; i<mSessions.size(); i++) { 682 if (mSessions.get(i).mResumed) { 683 return; 684 } 685 } 686 mResumed = false; 687 if (mPackageIntentReceiver != null) { 688 mPackageIntentReceiver.unregisterReceiver(); 689 mPackageIntentReceiver = null; 690 } 691 } 692 693 AppEntry getEntry(String packageName) { 694 if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock..."); 695 synchronized (mEntriesMap) { 696 AppEntry entry = mEntriesMap.get(packageName); 697 if (entry == null) { 698 for (int i=0; i<mApplications.size(); i++) { 699 ApplicationInfo info = mApplications.get(i); 700 if (packageName.equals(info.packageName)) { 701 entry = getEntryLocked(info); 702 break; 703 } 704 } 705 } 706 if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock"); 707 return entry; 708 } 709 } 710 711 void ensureIcon(AppEntry entry) { 712 if (entry.icon != null) { 713 return; 714 } 715 synchronized (entry) { 716 entry.ensureIconLocked(mContext, mPm); 717 } 718 } 719 720 void requestSize(String packageName) { 721 if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock..."); 722 synchronized (mEntriesMap) { 723 AppEntry entry = mEntriesMap.get(packageName); 724 if (entry != null) { 725 mPm.getPackageSizeInfo(packageName, mBackgroundHandler.mStatsObserver); 726 } 727 if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock"); 728 } 729 } 730 731 long sumCacheSizes() { 732 long sum = 0; 733 if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock..."); 734 synchronized (mEntriesMap) { 735 if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock"); 736 for (int i=mAppEntries.size()-1; i>=0; i--) { 737 sum += mAppEntries.get(i).cacheSize; 738 } 739 if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock"); 740 } 741 return sum; 742 } 743 744 int indexOfApplicationInfoLocked(String pkgName) { 745 for (int i=mApplications.size()-1; i>=0; i--) { 746 if (mApplications.get(i).packageName.equals(pkgName)) { 747 return i; 748 } 749 } 750 return -1; 751 } 752 753 void addPackage(String pkgName) { 754 try { 755 synchronized (mEntriesMap) { 756 if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock"); 757 if (DEBUG) Log.i(TAG, "Adding package " + pkgName); 758 if (!mResumed) { 759 // If we are not resumed, we will do a full query the 760 // next time we resume, so there is no reason to do work 761 // here. 762 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed"); 763 return; 764 } 765 if (indexOfApplicationInfoLocked(pkgName) >= 0) { 766 if (DEBUG) Log.i(TAG, "Package already exists!"); 767 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists"); 768 return; 769 } 770 ApplicationInfo info = mPm.getApplicationInfo(pkgName, mRetrieveFlags); 771 if (!info.enabled) { 772 if (info.enabledSetting 773 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { 774 return; 775 } 776 mHaveDisabledApps = true; 777 } 778 mApplications.add(info); 779 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { 780 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); 781 } 782 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { 783 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); 784 } 785 if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock"); 786 } 787 } catch (NameNotFoundException e) { 788 } 789 } 790 791 void removePackage(String pkgName) { 792 synchronized (mEntriesMap) { 793 if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock"); 794 int idx = indexOfApplicationInfoLocked(pkgName); 795 if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx); 796 if (idx >= 0) { 797 AppEntry entry = mEntriesMap.get(pkgName); 798 if (DEBUG) Log.i(TAG, "removePackage: " + entry); 799 if (entry != null) { 800 mEntriesMap.remove(pkgName); 801 mAppEntries.remove(entry); 802 } 803 ApplicationInfo info = mApplications.get(idx); 804 mApplications.remove(idx); 805 if (!info.enabled) { 806 mHaveDisabledApps = false; 807 for (int i=0; i<mApplications.size(); i++) { 808 if (!mApplications.get(i).enabled) { 809 mHaveDisabledApps = true; 810 break; 811 } 812 } 813 } 814 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { 815 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); 816 } 817 } 818 if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock"); 819 } 820 } 821 822 void invalidatePackage(String pkgName) { 823 removePackage(pkgName); 824 addPackage(pkgName); 825 } 826 827 AppEntry getEntryLocked(ApplicationInfo info) { 828 AppEntry entry = mEntriesMap.get(info.packageName); 829 if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry); 830 if (entry == null) { 831 if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName); 832 entry = new AppEntry(mContext, info, mCurId++); 833 mEntriesMap.put(info.packageName, entry); 834 mAppEntries.add(entry); 835 } else if (entry.info != info) { 836 entry.info = info; 837 } 838 return entry; 839 } 840 841 // -------------------------------------------------------------- 842 843 private long getTotalInternalSize(PackageStats ps) { 844 if (ps != null) { 845 return ps.codeSize + ps.dataSize; 846 } 847 return SIZE_INVALID; 848 } 849 850 private long getTotalExternalSize(PackageStats ps) { 851 if (ps != null) { 852 // We also include the cache size here because for non-emulated 853 // we don't automtically clean cache files. 854 return ps.externalCodeSize + ps.externalDataSize 855 + ps.externalCacheSize 856 + ps.externalMediaSize + ps.externalObbSize; 857 } 858 return SIZE_INVALID; 859 } 860 861 private String getSizeStr(long size) { 862 if (size >= 0) { 863 return Formatter.formatFileSize(mContext, size); 864 } 865 return null; 866 } 867 868 final HandlerThread mThread; 869 final BackgroundHandler mBackgroundHandler; 870 class BackgroundHandler extends Handler { 871 static final int MSG_REBUILD_LIST = 1; 872 static final int MSG_LOAD_ENTRIES = 2; 873 static final int MSG_LOAD_ICONS = 3; 874 static final int MSG_LOAD_SIZES = 4; 875 876 boolean mRunning; 877 878 final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() { 879 public void onGetStatsCompleted(PackageStats stats, boolean succeeded) { 880 boolean sizeChanged = false; 881 synchronized (mEntriesMap) { 882 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock"); 883 AppEntry entry = mEntriesMap.get(stats.packageName); 884 if (entry != null) { 885 synchronized (entry) { 886 entry.sizeStale = false; 887 entry.sizeLoadStart = 0; 888 long externalCodeSize = stats.externalCodeSize 889 + stats.externalObbSize; 890 long externalDataSize = stats.externalDataSize 891 + stats.externalMediaSize; 892 long newSize = externalCodeSize + externalDataSize 893 + getTotalInternalSize(stats); 894 if (entry.size != newSize || 895 entry.cacheSize != stats.cacheSize || 896 entry.codeSize != stats.codeSize || 897 entry.dataSize != stats.dataSize || 898 entry.externalCodeSize != externalCodeSize || 899 entry.externalDataSize != externalDataSize || 900 entry.externalCacheSize != stats.externalCacheSize) { 901 entry.size = newSize; 902 entry.cacheSize = stats.cacheSize; 903 entry.codeSize = stats.codeSize; 904 entry.dataSize = stats.dataSize; 905 entry.externalCodeSize = externalCodeSize; 906 entry.externalDataSize = externalDataSize; 907 entry.externalCacheSize = stats.externalCacheSize; 908 entry.sizeStr = getSizeStr(entry.size); 909 entry.internalSize = getTotalInternalSize(stats); 910 entry.internalSizeStr = getSizeStr(entry.internalSize); 911 entry.externalSize = getTotalExternalSize(stats); 912 entry.externalSizeStr = getSizeStr(entry.externalSize); 913 if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry 914 + ": " + entry.sizeStr); 915 sizeChanged = true; 916 } 917 } 918 if (sizeChanged) { 919 Message msg = mMainHandler.obtainMessage( 920 MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName); 921 mMainHandler.sendMessage(msg); 922 } 923 } 924 if (mCurComputingSizePkg == null 925 || mCurComputingSizePkg.equals(stats.packageName)) { 926 mCurComputingSizePkg = null; 927 sendEmptyMessage(MSG_LOAD_SIZES); 928 } 929 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock"); 930 } 931 } 932 }; 933 934 BackgroundHandler(Looper looper) { 935 super(looper); 936 } 937 938 @Override 939 public void handleMessage(Message msg) { 940 // Always try rebuilding list first thing, if needed. 941 ArrayList<Session> rebuildingSessions = null; 942 synchronized (mEntriesMap) { 943 if (mRebuildingSessions.size() > 0) { 944 rebuildingSessions = new ArrayList<Session>(mRebuildingSessions); 945 mRebuildingSessions.clear(); 946 } 947 } 948 if (rebuildingSessions != null) { 949 for (int i=0; i<rebuildingSessions.size(); i++) { 950 rebuildingSessions.get(i).handleRebuildList(); 951 } 952 } 953 954 switch (msg.what) { 955 case MSG_REBUILD_LIST: { 956 } break; 957 case MSG_LOAD_ENTRIES: { 958 int numDone = 0; 959 synchronized (mEntriesMap) { 960 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock"); 961 for (int i=0; i<mApplications.size() && numDone<6; i++) { 962 if (!mRunning) { 963 mRunning = true; 964 Message m = mMainHandler.obtainMessage( 965 MainHandler.MSG_RUNNING_STATE_CHANGED, 1); 966 mMainHandler.sendMessage(m); 967 } 968 ApplicationInfo info = mApplications.get(i); 969 if (mEntriesMap.get(info.packageName) == null) { 970 numDone++; 971 getEntryLocked(info); 972 } 973 } 974 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock"); 975 } 976 977 if (numDone >= 6) { 978 sendEmptyMessage(MSG_LOAD_ENTRIES); 979 } else { 980 sendEmptyMessage(MSG_LOAD_ICONS); 981 } 982 } break; 983 case MSG_LOAD_ICONS: { 984 int numDone = 0; 985 synchronized (mEntriesMap) { 986 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock"); 987 for (int i=0; i<mAppEntries.size() && numDone<2; i++) { 988 AppEntry entry = mAppEntries.get(i); 989 if (entry.icon == null || !entry.mounted) { 990 synchronized (entry) { 991 if (entry.ensureIconLocked(mContext, mPm)) { 992 if (!mRunning) { 993 mRunning = true; 994 Message m = mMainHandler.obtainMessage( 995 MainHandler.MSG_RUNNING_STATE_CHANGED, 1); 996 mMainHandler.sendMessage(m); 997 } 998 numDone++; 999 } 1000 } 1001 } 1002 } 1003 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock"); 1004 } 1005 if (numDone > 0) { 1006 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) { 1007 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED); 1008 } 1009 } 1010 if (numDone >= 2) { 1011 sendEmptyMessage(MSG_LOAD_ICONS); 1012 } else { 1013 sendEmptyMessage(MSG_LOAD_SIZES); 1014 } 1015 } break; 1016 case MSG_LOAD_SIZES: { 1017 synchronized (mEntriesMap) { 1018 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock"); 1019 if (mCurComputingSizePkg != null) { 1020 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing"); 1021 return; 1022 } 1023 1024 long now = SystemClock.uptimeMillis(); 1025 for (int i=0; i<mAppEntries.size(); i++) { 1026 AppEntry entry = mAppEntries.get(i); 1027 if (entry.size == SIZE_UNKNOWN || entry.sizeStale) { 1028 if (entry.sizeLoadStart == 0 || 1029 (entry.sizeLoadStart < (now-20*1000))) { 1030 if (!mRunning) { 1031 mRunning = true; 1032 Message m = mMainHandler.obtainMessage( 1033 MainHandler.MSG_RUNNING_STATE_CHANGED, 1); 1034 mMainHandler.sendMessage(m); 1035 } 1036 entry.sizeLoadStart = now; 1037 mCurComputingSizePkg = entry.info.packageName; 1038 mPm.getPackageSizeInfo(mCurComputingSizePkg, mStatsObserver); 1039 } 1040 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing"); 1041 return; 1042 } 1043 } 1044 if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) { 1045 mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED); 1046 mRunning = false; 1047 Message m = mMainHandler.obtainMessage( 1048 MainHandler.MSG_RUNNING_STATE_CHANGED, 0); 1049 mMainHandler.sendMessage(m); 1050 } 1051 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock"); 1052 } 1053 } break; 1054 } 1055 } 1056 1057 } 1058 } 1059