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