1package com.android.settings.applications;
2
3import android.app.Application;
4import android.content.BroadcastReceiver;
5import android.content.Context;
6import android.content.Intent;
7import android.content.IntentFilter;
8import android.content.pm.ApplicationInfo;
9import android.content.pm.IPackageStatsObserver;
10import android.content.pm.PackageManager;
11import android.content.pm.PackageStats;
12import android.content.pm.PackageManager.NameNotFoundException;
13import android.graphics.drawable.Drawable;
14import android.net.Uri;
15import android.os.Handler;
16import android.os.HandlerThread;
17import android.os.Looper;
18import android.os.Message;
19import android.os.Process;
20import android.os.SystemClock;
21import android.os.UserHandle;
22import android.text.format.Formatter;
23import android.util.Log;
24
25import java.io.File;
26import java.text.Collator;
27import java.text.Normalizer;
28import java.text.Normalizer.Form;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.Comparator;
32import java.util.HashMap;
33import java.util.List;
34import java.util.regex.Pattern;
35
36/**
37 * Keeps track of information about all installed applications, lazy-loading
38 * as needed.
39 */
40public 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 {
46        public void onRunningStateChanged(boolean running);
47        public void onPackageListChanged();
48        public void onRebuildComplete(ArrayList<AppEntry> apps);
49        public void onPackageIconChanged();
50        public void onPackageSizeChanged(String packageName);
51        public void onAllSizesComputed();
52    }
53
54    public static interface AppFilter {
55        public void init();
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
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
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
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
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
140        boolean ensureIconLocked(Context context, PackageManager pm) {
141            if (this.icon == null) {
142                if (this.apkFile.exists()) {
143                    this.icon = this.info.loadIcon(pm);
144                    return true;
145                } else {
146                    this.mounted = false;
147                    this.icon = context.getResources().getDrawable(
148                            com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
149                }
150            } else if (!this.mounted) {
151                // If the app wasn't mounted but is now mounted, reload
152                // its icon.
153                if (this.apkFile.exists()) {
154                    this.mounted = true;
155                    this.icon = this.info.loadIcon(pm);
156                    return true;
157                }
158            }
159            return false;
160        }
161    }
162
163    public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
164        private final Collator sCollator = Collator.getInstance();
165        @Override
166        public int compare(AppEntry object1, AppEntry object2) {
167            final boolean normal1 = object1.info.enabled
168                    && (object1.info.flags&ApplicationInfo.FLAG_INSTALLED) != 0;
169            final boolean normal2 = object2.info.enabled
170                    && (object2.info.flags&ApplicationInfo.FLAG_INSTALLED) != 0;
171            if (normal1 != normal2) {
172                return normal1 ? -1 : 1;
173            }
174            return sCollator.compare(object1.label, object2.label);
175        }
176    };
177
178    public static final Comparator<AppEntry> SIZE_COMPARATOR
179            = new Comparator<AppEntry>() {
180        private final Collator sCollator = Collator.getInstance();
181        @Override
182        public int compare(AppEntry object1, AppEntry object2) {
183            if (object1.size < object2.size) return 1;
184            if (object1.size > object2.size) return -1;
185            return sCollator.compare(object1.label, object2.label);
186        }
187    };
188
189    public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR
190            = new Comparator<AppEntry>() {
191        private final Collator sCollator = Collator.getInstance();
192        @Override
193        public int compare(AppEntry object1, AppEntry object2) {
194            if (object1.internalSize < object2.internalSize) return 1;
195            if (object1.internalSize > object2.internalSize) return -1;
196            return sCollator.compare(object1.label, object2.label);
197        }
198    };
199
200    public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR
201            = new Comparator<AppEntry>() {
202        private final Collator sCollator = Collator.getInstance();
203        @Override
204        public int compare(AppEntry object1, AppEntry object2) {
205            if (object1.externalSize < object2.externalSize) return 1;
206            if (object1.externalSize > object2.externalSize) return -1;
207            return sCollator.compare(object1.label, object2.label);
208        }
209    };
210
211    public static final AppFilter THIRD_PARTY_FILTER = new AppFilter() {
212        public void init() {
213        }
214
215        @Override
216        public boolean filterApp(ApplicationInfo info) {
217            if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
218                return true;
219            } else if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
220                return true;
221            }
222            return false;
223        }
224    };
225
226    public static final AppFilter ON_SD_CARD_FILTER = new AppFilter() {
227        final CanBeOnSdCardChecker mCanBeOnSdCardChecker
228                = new CanBeOnSdCardChecker();
229
230        public void init() {
231            mCanBeOnSdCardChecker.init();
232        }
233
234        @Override
235        public boolean filterApp(ApplicationInfo info) {
236            return mCanBeOnSdCardChecker.check(info);
237        }
238    };
239
240    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 {
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         }
305         void unregisterReceiver() {
306             mContext.unregisterReceiver(this);
307         }
308         @Override
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
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
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
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
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
479        Session(Callbacks callbacks) {
480            mCallbacks = callbacks;
481        }
482
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
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.
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
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
608        public void release() {
609            pause();
610            synchronized (mEntriesMap) {
611                mSessions.remove(this);
612            }
613        }
614    }
615
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
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
673    public boolean haveDisabledApps() {
674        return mHaveDisabledApps;
675    }
676
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
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
711    void ensureIcon(AppEntry entry) {
712        if (entry.icon != null) {
713            return;
714        }
715        synchronized (entry) {
716            entry.ensureIconLocked(mContext, mPm);
717        }
718    }
719
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
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
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
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
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
822    void invalidatePackage(String pkgName) {
823        removePackage(pkgName);
824        addPackage(pkgName);
825    }
826
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
843    private long getTotalInternalSize(PackageStats ps) {
844        if (ps != null) {
845            return ps.codeSize + ps.dataSize;
846        }
847        return SIZE_INVALID;
848    }
849
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
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
934        BackgroundHandler(Looper looper) {
935            super(looper);
936        }
937
938        @Override
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