• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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