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