OSDN Git Service

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