1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.tv.settings.device.apps; 18 19import android.app.Application; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.content.pm.ApplicationInfo; 25import android.content.pm.IPackageStatsObserver; 26import android.content.pm.PackageManager; 27import android.content.pm.PackageStats; 28import android.content.pm.PackageManager.NameNotFoundException; 29import android.graphics.drawable.Drawable; 30import android.net.Uri; 31import android.os.Handler; 32import android.os.HandlerThread; 33import android.os.Looper; 34import android.os.Message; 35import android.os.Process; 36import android.os.SystemClock; 37import android.os.UserHandle; 38import android.text.format.Formatter; 39import android.util.Log; 40 41import java.io.File; 42import java.text.Collator; 43import java.text.Normalizer; 44import java.text.Normalizer.Form; 45import java.util.ArrayList; 46import java.util.Collections; 47import java.util.Comparator; 48import java.util.HashMap; 49import java.util.List; 50import java.util.regex.Pattern; 51 52/** 53 * Keeps track of information about all installed applications, lazy-loading as 54 * needed. 55 */ 56public class ApplicationsState { 57 58 // TODO: Remove what we don't need! 59 60 private static final String TAG = "ApplicationsState"; 61 private static final boolean DEBUG = false; 62 private static final boolean DEBUG_LOCKING = false; 63 64 public static interface Callbacks { 65 public void onRunningStateChanged(boolean running); 66 67 public void onPackageListChanged(); 68 69 public void onRebuildComplete(); 70 71 public void onPackageIconChanged(); 72 73 public void onPackageSizeChanged(String packageName); 74 75 public void onAllSizesComputed(); 76 } 77 78 static final int SIZE_UNKNOWN = -1; 79 static final int SIZE_INVALID = -2; 80 81 static final Pattern REMOVE_DIACRITICALS_PATTERN = Pattern.compile( 82 "\\p{InCombiningDiacriticalMarks}+"); 83 84 public static String normalize(String str) { 85 String tmp = Normalizer.normalize(str, Form.NFD); 86 return REMOVE_DIACRITICALS_PATTERN.matcher(tmp).replaceAll("").toLowerCase(); 87 } 88 89 public static class AppEntry { 90 long cacheSize; 91 long codeSize; 92 long dataSize; 93 long externalCodeSize; 94 long externalDataSize; 95 96 // This is the part of externalDataSize that is in the cache 97 // section of external storage. Note that we don't just combine 98 // this with cacheSize because currently the platform can't 99 // automatically trim this data when needed, so it is something 100 // the user may need to manage. The externalDataSize also includes 101 // this value, since what this is here is really the part of 102 // externalDataSize that we can just consider to be "cache" files 103 // for purposes of cleaning them up in the app details UI. 104 long externalCacheSize; 105 106 final File apkFile; 107 final long id; 108 String label; 109 long size; 110 long internalSize; 111 long externalSize; 112 113 boolean mounted; 114 115 String getNormalizedLabel() { 116 if (normalizedLabel != null) { 117 return normalizedLabel; 118 } 119 normalizedLabel = normalize(label); 120 return normalizedLabel; 121 } 122 123 // Need to synchronize on 'this' for the following. 124 ApplicationInfo info; 125 String sizeStr; 126 boolean sizeStale; 127 long sizeLoadStart; 128 129 String normalizedLabel; 130 131 AppEntry(Context context, ApplicationInfo info, long id) { 132 apkFile = new File(info.sourceDir); 133 this.id = id; 134 this.info = info; 135 this.size = SIZE_UNKNOWN; 136 this.sizeStale = true; 137 ensureLabel(context); 138 } 139 140 void ensureLabel(Context context) { 141 if (this.label == null || !this.mounted) { 142 if (!this.apkFile.exists()) { 143 this.mounted = false; 144 this.label = info.packageName; 145 } else { 146 this.mounted = true; 147 CharSequence label = info.loadLabel(context.getPackageManager()); 148 this.label = label != null ? label.toString() : info.packageName; 149 } 150 } 151 } 152 153 String getVersion(Context context) { 154 try { 155 return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName; 156 } catch (NameNotFoundException e) { 157 return ""; 158 } 159 } 160 } 161 162 private final Context mContext; 163 private final PackageManager mPm; 164 private final int mRetrieveFlags; 165 private PackageIntentReceiver mPackageIntentReceiver; 166 167 private boolean mResumed; 168 169 // Information about all applications. Synchronize on mEntriesMap 170 // to protect access to these. 171 private final ArrayList<Session> mSessions = new ArrayList<Session>(); 172 private final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); 173 final HashMap<String, AppEntry> mEntriesMap = new HashMap<String, AppEntry>(); 174 final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>(); 175 private List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>(); 176 private long mCurId = 1; 177 private String mCurComputingSizePkg; 178 private boolean mSessionsChanged; 179 180 // Temporary for dispatching session callbacks. Only touched by main thread. 181 private final ArrayList<Session> mActiveSessions = new ArrayList<Session>(); 182 183 /** 184 * Receives notifications when applications are added/removed. 185 */ 186 private class PackageIntentReceiver extends BroadcastReceiver { 187 void registerReceiver() { 188 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 189 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 190 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 191 filter.addDataScheme("package"); 192 mContext.registerReceiver(this, filter); 193 // Register for events related to sdcard installation. 194 IntentFilter sdFilter = new IntentFilter(); 195 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); 196 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); 197 mContext.registerReceiver(this, sdFilter); 198 } 199 200 void unregisterReceiver() { 201 mContext.unregisterReceiver(this); 202 } 203 204 @Override 205 public void onReceive(Context context, Intent intent) { 206 String actionStr = intent.getAction(); 207 if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) { 208 Uri data = intent.getData(); 209 String pkgName = data.getEncodedSchemeSpecificPart(); 210 addPackage(pkgName); 211 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) { 212 Uri data = intent.getData(); 213 String pkgName = data.getEncodedSchemeSpecificPart(); 214 removePackage(pkgName); 215 } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) { 216 Uri data = intent.getData(); 217 String pkgName = data.getEncodedSchemeSpecificPart(); 218 invalidatePackage(pkgName); 219 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) 220 || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) { 221 // When applications become available or unavailable (perhaps 222 // because 223 // the SD card was inserted or ejected) we need to refresh the 224 // AppInfo with new label, icon and size information as 225 // appropriate 226 // given the newfound (un)availability of the application. 227 // A simple way to do that is to treat the refresh as a package 228 // removal followed by a package addition. 229 String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 230 if (pkgList == null || pkgList.length == 0) { 231 // Ignore 232 return; 233 } 234 boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr); 235 if (avail) { 236 for (String pkgName : pkgList) { 237 invalidatePackage(pkgName); 238 } 239 } 240 } 241 } 242 } 243 244 void rebuildActiveSessions() { 245 synchronized (mEntriesMap) { 246 if (!mSessionsChanged) { 247 return; 248 } 249 mActiveSessions.clear(); 250 for (int i = 0; i < mSessions.size(); i++) { 251 Session s = mSessions.get(i); 252 if (s.mResumed) { 253 mActiveSessions.add(s); 254 } 255 } 256 } 257 } 258 259 class MainHandler extends Handler { 260 static final int MSG_REBUILD_COMPLETE = 1; 261 static final int MSG_PACKAGE_LIST_CHANGED = 2; 262 static final int MSG_PACKAGE_ICON_CHANGED = 3; 263 static final int MSG_PACKAGE_SIZE_CHANGED = 4; 264 static final int MSG_ALL_SIZES_COMPUTED = 5; 265 static final int MSG_RUNNING_STATE_CHANGED = 6; 266 267 @Override 268 public void handleMessage(Message msg) { 269 rebuildActiveSessions(); 270 switch (msg.what) { 271 case MSG_REBUILD_COMPLETE: 272 { 273 Session s = (Session) msg.obj; 274 if (mActiveSessions.contains(s)) { 275 s.mCallbacks.onRebuildComplete(); 276 } 277 } 278 break; 279 case MSG_PACKAGE_LIST_CHANGED: 280 for (int i = 0; i < mActiveSessions.size(); i++) { 281 mActiveSessions.get(i).mCallbacks.onPackageListChanged(); 282 } 283 break; 284 case MSG_PACKAGE_ICON_CHANGED: 285 for (int i = 0; i < mActiveSessions.size(); i++) { 286 mActiveSessions.get(i).mCallbacks.onPackageIconChanged(); 287 } 288 break; 289 case MSG_PACKAGE_SIZE_CHANGED: 290 for (int i = 0; i < mActiveSessions.size(); i++) { 291 mActiveSessions.get(i).mCallbacks.onPackageSizeChanged((String) msg.obj); 292 } 293 break; 294 case MSG_ALL_SIZES_COMPUTED: 295 for (int i = 0; i < mActiveSessions.size(); i++) { 296 mActiveSessions.get(i).mCallbacks.onAllSizesComputed(); 297 } 298 break; 299 case MSG_RUNNING_STATE_CHANGED: 300 for (int i = 0; i < mActiveSessions.size(); i++) { 301 mActiveSessions.get(i).mCallbacks.onRunningStateChanged(msg.arg1 != 0); 302 } 303 break; 304 } 305 } 306 } 307 308 private final MainHandler mMainHandler = new MainHandler(); 309 310 // -------------------------------------------------------------- 311 312 private static final Object sLock = new Object(); 313 private static ApplicationsState sInstance; 314 315 static ApplicationsState getInstance(Context app) { 316 synchronized (sLock) { 317 if (sInstance == null) { 318 sInstance = new ApplicationsState(app); 319 } 320 return sInstance; 321 } 322 } 323 324 private ApplicationsState(Context app) { 325 mContext = app; 326 mPm = mContext.getPackageManager(); 327 mThread = new HandlerThread("ApplicationsState.Loader", Process.THREAD_PRIORITY_BACKGROUND); 328 mThread.start(); 329 mBackgroundHandler = new BackgroundHandler(mThread.getLooper()); 330 331 // Only the owner can see all apps. 332 if (UserHandle.myUserId() == 0) { 333 mRetrieveFlags = PackageManager.GET_UNINSTALLED_PACKAGES 334 | PackageManager.GET_DISABLED_COMPONENTS; 335 } else { 336 mRetrieveFlags = PackageManager.GET_DISABLED_COMPONENTS; 337 } 338 339 /** 340 * This is a trick to prevent the foreground thread from being delayed. 341 * The problem is that Dalvik monitors are initially spin locks, to keep 342 * them lightweight. This leads to unfair contention -- Even though the 343 * background thread only holds the lock for a short amount of time, if 344 * it keeps running and locking again it can prevent the main thread 345 * from acquiring its lock for a long time... sometimes even > 5 seconds 346 * (leading to an ANR). Dalvik will promote a monitor to a "real" lock 347 * if it detects enough contention on it. It doesn't figure this out 348 * fast enough for us here, though, so this little trick will force it 349 * to turn into a real lock immediately. 350 */ 351 synchronized (mEntriesMap) { 352 try { 353 mEntriesMap.wait(1); 354 } catch (InterruptedException e) { 355 } 356 } 357 } 358 359 public class Session { 360 private final Callbacks mCallbacks; 361 private boolean mResumed; 362 363 private Session(Callbacks callbacks) { 364 mCallbacks = callbacks; 365 } 366 367 public void resume() { 368 if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock..."); 369 synchronized (mEntriesMap) { 370 if (!mResumed) { 371 mResumed = true; 372 mSessionsChanged = true; 373 doResumeIfNeededLocked(); 374 } 375 } 376 if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock"); 377 } 378 379 public void pause() { 380 if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock..."); 381 synchronized (mEntriesMap) { 382 if (mResumed) { 383 mResumed = false; 384 mSessionsChanged = true; 385 mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this); 386 doPauseIfNeededLocked(); 387 } 388 if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock"); 389 } 390 } 391 392 public void release() { 393 pause(); 394 synchronized (mEntriesMap) { 395 mSessions.remove(this); 396 } 397 } 398 } 399 400 public Session newSession(Callbacks callbacks) { 401 Session s = new Session(callbacks); 402 synchronized (mEntriesMap) { 403 mSessions.add(s); 404 } 405 return s; 406 } 407 408 private void doResumeIfNeededLocked() { 409 if (mResumed) { 410 return; 411 } 412 mResumed = true; 413 if (mPackageIntentReceiver == null) { 414 mPackageIntentReceiver = new PackageIntentReceiver(); 415 mPackageIntentReceiver.registerReceiver(); 416 } 417 mApplications = mPm.getInstalledApplications(mRetrieveFlags); 418 if (mApplications == null) { 419 mApplications = new ArrayList<ApplicationInfo>(); 420 } 421 422 if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { 423 // If an interesting part of the configuration has changed, we 424 // should completely reload the app entries. 425 mEntriesMap.clear(); 426 mAppEntries.clear(); 427 } else { 428 for (int i = 0; i < mAppEntries.size(); i++) { 429 mAppEntries.get(i).sizeStale = true; 430 } 431 } 432 433 for (int i = 0; i < mApplications.size(); i++) { 434 final ApplicationInfo info = mApplications.get(i); 435 // Need to trim out any applications that are disabled by 436 // something different than the user. 437 if (!info.enabled && info.enabledSetting 438 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { 439 mApplications.remove(i); 440 i--; 441 continue; 442 } 443 final AppEntry entry = mEntriesMap.get(info.packageName); 444 if (entry != null) { 445 entry.info = info; 446 } 447 } 448 mCurComputingSizePkg = null; 449 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { 450 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); 451 } 452 } 453 454 private void doPauseIfNeededLocked() { 455 if (!mResumed) { 456 return; 457 } 458 for (int i = 0; i < mSessions.size(); i++) { 459 if (mSessions.get(i).mResumed) { 460 return; 461 } 462 } 463 mResumed = false; 464 if (mPackageIntentReceiver != null) { 465 mPackageIntentReceiver.unregisterReceiver(); 466 mPackageIntentReceiver = null; 467 } 468 } 469 470 AppEntry getEntry(String packageName) { 471 if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock..."); 472 synchronized (mEntriesMap) { 473 AppEntry entry = mEntriesMap.get(packageName); 474 if (entry == null) { 475 for (int i = 0; i < mApplications.size(); i++) { 476 ApplicationInfo info = mApplications.get(i); 477 if (packageName.equals(info.packageName)) { 478 entry = getEntryLocked(info); 479 break; 480 } 481 } 482 } 483 if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock"); 484 return entry; 485 } 486 } 487 488 void requestSize(String packageName) { 489 if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock..."); 490 synchronized (mEntriesMap) { 491 AppEntry entry = mEntriesMap.get(packageName); 492 if (entry != null) { 493 mPm.getPackageSizeInfo(packageName, mBackgroundHandler.mStatsObserver); 494 } 495 if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock"); 496 } 497 } 498 499 private int indexOfApplicationInfoLocked(String pkgName) { 500 for (int i = mApplications.size() - 1; i >= 0; i--) { 501 if (mApplications.get(i).packageName.equals(pkgName)) { 502 return i; 503 } 504 } 505 return -1; 506 } 507 508 private void addPackage(String pkgName) { 509 try { 510 synchronized (mEntriesMap) { 511 if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock"); 512 if (DEBUG) Log.i(TAG, "Adding package " + pkgName); 513 if (!mResumed) { 514 // If we are not resumed, we will do a full query the 515 // next time we resume, so there is no reason to do work 516 // here. 517 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed"); 518 return; 519 } 520 if (indexOfApplicationInfoLocked(pkgName) >= 0) { 521 if (DEBUG) Log.i(TAG, "Package already exists!"); 522 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists"); 523 return; 524 } 525 ApplicationInfo info = mPm.getApplicationInfo(pkgName, mRetrieveFlags); 526 mApplications.add(info); 527 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { 528 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); 529 } 530 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { 531 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); 532 } 533 if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock"); 534 } 535 } catch (NameNotFoundException e) { 536 } 537 } 538 539 void removePackage(String pkgName) { 540 synchronized (mEntriesMap) { 541 if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock"); 542 int idx = indexOfApplicationInfoLocked(pkgName); 543 if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx); 544 if (idx >= 0) { 545 AppEntry entry = mEntriesMap.get(pkgName); 546 if (DEBUG) Log.i(TAG, "removePackage: " + entry); 547 if (entry != null) { 548 mEntriesMap.remove(pkgName); 549 mAppEntries.remove(entry); 550 } 551 mApplications.remove(idx); 552 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { 553 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); 554 } 555 } 556 if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock"); 557 } 558 } 559 560 void invalidatePackage(String pkgName) { 561 removePackage(pkgName); 562 addPackage(pkgName); 563 } 564 565 private AppEntry getEntryLocked(ApplicationInfo info) { 566 AppEntry entry = mEntriesMap.get(info.packageName); 567 if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry); 568 if (entry == null) { 569 if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName); 570 entry = new AppEntry(mContext, info, mCurId++); 571 mEntriesMap.put(info.packageName, entry); 572 mAppEntries.add(entry); 573 } else if (entry.info != info) { 574 entry.info = info; 575 } 576 return entry; 577 } 578 579 // -------------------------------------------------------------- 580 581 private final HandlerThread mThread; 582 private final BackgroundHandler mBackgroundHandler; 583 584 private class BackgroundHandler extends Handler { 585 static final int MSG_REBUILD_LIST = 1; 586 static final int MSG_LOAD_ENTRIES = 2; 587 static final int MSG_LOAD_SIZES = 3; 588 589 boolean mRunning; 590 591 final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() { 592 public void onGetStatsCompleted(PackageStats stats, boolean succeeded) { 593 boolean sizeChanged = false; 594 synchronized (mEntriesMap) { 595 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock"); 596 AppEntry entry = mEntriesMap.get(stats.packageName); 597 if (entry != null) { 598 synchronized (entry) { 599 entry.sizeStale = false; 600 entry.sizeLoadStart = 0; 601 long externalCodeSize = stats.externalCodeSize + stats.externalObbSize; 602 long externalDataSize = stats.externalDataSize 603 + stats.externalMediaSize; 604 long newSize = externalCodeSize + externalDataSize 605 + getTotalInternalSize(stats); 606 if (entry.size != newSize || entry.cacheSize != stats.cacheSize 607 || entry.codeSize != stats.codeSize 608 || entry.dataSize != stats.dataSize 609 || entry.externalCodeSize != externalCodeSize 610 || entry.externalDataSize != externalDataSize 611 || entry.externalCacheSize != stats.externalCacheSize) { 612 entry.size = newSize; 613 entry.cacheSize = stats.cacheSize; 614 entry.codeSize = stats.codeSize; 615 entry.dataSize = stats.dataSize; 616 entry.externalCodeSize = externalCodeSize; 617 entry.externalDataSize = externalDataSize; 618 entry.externalCacheSize = stats.externalCacheSize; 619 entry.sizeStr = getSizeStr(entry.size); 620 entry.internalSize = getTotalInternalSize(stats); 621 entry.externalSize = getTotalExternalSize(stats); 622 if (DEBUG) 623 Log.i(TAG, "Set size of " + entry.label + " " + entry + ": " 624 + entry.sizeStr); 625 sizeChanged = true; 626 } 627 } 628 if (sizeChanged) { 629 Message msg = mMainHandler.obtainMessage( 630 MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName); 631 mMainHandler.sendMessage(msg); 632 } 633 } 634 if (mCurComputingSizePkg == null 635 || mCurComputingSizePkg.equals(stats.packageName)) { 636 mCurComputingSizePkg = null; 637 sendEmptyMessage(MSG_LOAD_SIZES); 638 } 639 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock"); 640 } 641 } 642 }; 643 644 BackgroundHandler(Looper looper) { 645 super(looper); 646 } 647 648 @Override 649 public void handleMessage(Message msg) { 650 651 switch (msg.what) { 652 case MSG_REBUILD_LIST: 653 break; 654 case MSG_LOAD_ENTRIES: 655 { 656 int numDone = 0; 657 synchronized (mEntriesMap) { 658 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock"); 659 for (int i = 0; i < mApplications.size() && numDone < 6; i++) { 660 if (!mRunning) { 661 mRunning = true; 662 Message m = mMainHandler.obtainMessage( 663 MainHandler.MSG_RUNNING_STATE_CHANGED, 1); 664 mMainHandler.sendMessage(m); 665 } 666 ApplicationInfo info = mApplications.get(i); 667 if (mEntriesMap.get(info.packageName) == null) { 668 numDone++; 669 getEntryLocked(info); 670 } 671 } 672 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock"); 673 } 674 675 if (numDone >= 6) { 676 sendEmptyMessage(MSG_LOAD_ENTRIES); 677 } else { 678 sendEmptyMessage(MSG_LOAD_SIZES); 679 } 680 } 681 break; 682 case MSG_LOAD_SIZES: 683 synchronized (mEntriesMap) { 684 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock"); 685 if (mCurComputingSizePkg != null) { 686 if (DEBUG_LOCKING) { 687 Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing"); 688 } 689 return; 690 } 691 692 long now = SystemClock.uptimeMillis(); 693 for (int i = 0; i < mAppEntries.size(); i++) { 694 AppEntry entry = mAppEntries.get(i); 695 if (entry.size == SIZE_UNKNOWN || entry.sizeStale) { 696 if (entry.sizeLoadStart == 0 697 || (entry.sizeLoadStart < (now - 20 * 1000))) { 698 if (!mRunning) { 699 mRunning = true; 700 Message m = mMainHandler.obtainMessage( 701 MainHandler.MSG_RUNNING_STATE_CHANGED, 1); 702 mMainHandler.sendMessage(m); 703 } 704 entry.sizeLoadStart = now; 705 mCurComputingSizePkg = entry.info.packageName; 706 mPm.getPackageSizeInfo(mCurComputingSizePkg, mStatsObserver); 707 } 708 if (DEBUG_LOCKING) { 709 Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing"); 710 } 711 return; 712 } 713 } 714 if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) { 715 mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED); 716 mRunning = false; 717 Message m = mMainHandler.obtainMessage( 718 MainHandler.MSG_RUNNING_STATE_CHANGED, 0); 719 mMainHandler.sendMessage(m); 720 } 721 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock"); 722 } 723 break; 724 } 725 } 726 727 private long getTotalInternalSize(PackageStats ps) { 728 if (ps != null) { 729 return ps.codeSize + ps.dataSize; 730 } 731 return SIZE_INVALID; 732 } 733 734 private long getTotalExternalSize(PackageStats ps) { 735 if (ps != null) { 736 // We also include the cache size here because for non-emulated 737 // we don't automtically clean cache files. 738 return ps.externalCodeSize + ps.externalDataSize + ps.externalCacheSize 739 + ps.externalMediaSize + ps.externalObbSize; 740 } 741 return SIZE_INVALID; 742 } 743 744 private String getSizeStr(long size) { 745 if (size >= 0) { 746 return Formatter.formatFileSize(mContext, size); 747 } 748 return null; 749 } 750 } 751} 752