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