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