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