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