1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.tv.settings.device.apps;
18
19import android.app.Application;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.IPackageStatsObserver;
26import android.content.pm.PackageManager;
27import android.content.pm.PackageStats;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.graphics.drawable.Drawable;
30import android.net.Uri;
31import android.os.Handler;
32import android.os.HandlerThread;
33import android.os.Looper;
34import android.os.Message;
35import android.os.Process;
36import android.os.SystemClock;
37import android.os.UserHandle;
38import android.text.format.Formatter;
39import android.util.Log;
40
41import java.io.File;
42import java.text.Collator;
43import java.text.Normalizer;
44import java.text.Normalizer.Form;
45import java.util.ArrayList;
46import java.util.Collections;
47import java.util.Comparator;
48import java.util.HashMap;
49import java.util.List;
50import java.util.regex.Pattern;
51
52/**
53 * Keeps track of information about all installed applications, lazy-loading as
54 * needed.
55 */
56public class ApplicationsState {
57
58    // TODO: Remove what we don't need!
59
60    private static final String TAG = "ApplicationsState";
61    private static final boolean DEBUG = false;
62    private static final boolean DEBUG_LOCKING = false;
63
64    public static interface Callbacks {
65        public void onRunningStateChanged(boolean running);
66
67        public void onPackageListChanged();
68
69        public void onRebuildComplete();
70
71        public void onPackageIconChanged();
72
73        public void onPackageSizeChanged(String packageName);
74
75        public void onAllSizesComputed();
76    }
77
78    static final int SIZE_UNKNOWN = -1;
79    static final int SIZE_INVALID = -2;
80
81    static final Pattern REMOVE_DIACRITICALS_PATTERN = Pattern.compile(
82            "\\p{InCombiningDiacriticalMarks}+");
83
84    public static String normalize(String str) {
85        String tmp = Normalizer.normalize(str, Form.NFD);
86        return REMOVE_DIACRITICALS_PATTERN.matcher(tmp).replaceAll("").toLowerCase();
87    }
88
89    public static class AppEntry {
90        long cacheSize;
91        long codeSize;
92        long dataSize;
93        long externalCodeSize;
94        long externalDataSize;
95
96        // This is the part of externalDataSize that is in the cache
97        // section of external storage. Note that we don't just combine
98        // this with cacheSize because currently the platform can't
99        // automatically trim this data when needed, so it is something
100        // the user may need to manage. The externalDataSize also includes
101        // this value, since what this is here is really the part of
102        // externalDataSize that we can just consider to be "cache" files
103        // for purposes of cleaning them up in the app details UI.
104        long externalCacheSize;
105
106        final File apkFile;
107        final long id;
108        String label;
109        long size;
110        long internalSize;
111        long externalSize;
112
113        boolean mounted;
114
115        String getNormalizedLabel() {
116            if (normalizedLabel != null) {
117                return normalizedLabel;
118            }
119            normalizedLabel = normalize(label);
120            return normalizedLabel;
121        }
122
123        // Need to synchronize on 'this' for the following.
124        ApplicationInfo info;
125        String sizeStr;
126        boolean sizeStale;
127        long sizeLoadStart;
128
129        String normalizedLabel;
130
131        AppEntry(Context context, ApplicationInfo info, long id) {
132            apkFile = new File(info.sourceDir);
133            this.id = id;
134            this.info = info;
135            this.size = SIZE_UNKNOWN;
136            this.sizeStale = true;
137            ensureLabel(context);
138        }
139
140        void ensureLabel(Context context) {
141            if (this.label == null || !this.mounted) {
142                if (!this.apkFile.exists()) {
143                    this.mounted = false;
144                    this.label = info.packageName;
145                } else {
146                    this.mounted = true;
147                    CharSequence label = info.loadLabel(context.getPackageManager());
148                    this.label = label != null ? label.toString() : info.packageName;
149                }
150            }
151        }
152
153        String getVersion(Context context) {
154            try {
155                return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName;
156            } catch (NameNotFoundException e) {
157                return "";
158            }
159        }
160    }
161
162    private final Context mContext;
163    private final PackageManager mPm;
164    private final int mRetrieveFlags;
165    private PackageIntentReceiver mPackageIntentReceiver;
166
167    private boolean mResumed;
168
169    // Information about all applications. Synchronize on mEntriesMap
170    // to protect access to these.
171    private final ArrayList<Session> mSessions = new ArrayList<Session>();
172    private final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges();
173    final HashMap<String, AppEntry> mEntriesMap = new HashMap<String, AppEntry>();
174    final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>();
175    private List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>();
176    private long mCurId = 1;
177    private String mCurComputingSizePkg;
178    private boolean mSessionsChanged;
179
180    // Temporary for dispatching session callbacks. Only touched by main thread.
181    private final ArrayList<Session> mActiveSessions = new ArrayList<Session>();
182
183    /**
184     * Receives notifications when applications are added/removed.
185     */
186    private class PackageIntentReceiver extends BroadcastReceiver {
187        void registerReceiver() {
188            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
189            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
190            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
191            filter.addDataScheme("package");
192            mContext.registerReceiver(this, filter);
193            // Register for events related to sdcard installation.
194            IntentFilter sdFilter = new IntentFilter();
195            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
196            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
197            mContext.registerReceiver(this, sdFilter);
198        }
199
200        void unregisterReceiver() {
201            mContext.unregisterReceiver(this);
202        }
203
204        @Override
205        public void onReceive(Context context, Intent intent) {
206            String actionStr = intent.getAction();
207            if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) {
208                Uri data = intent.getData();
209                String pkgName = data.getEncodedSchemeSpecificPart();
210                addPackage(pkgName);
211            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) {
212                Uri data = intent.getData();
213                String pkgName = data.getEncodedSchemeSpecificPart();
214                removePackage(pkgName);
215            } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) {
216                Uri data = intent.getData();
217                String pkgName = data.getEncodedSchemeSpecificPart();
218                invalidatePackage(pkgName);
219            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr)
220                    || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) {
221                // When applications become available or unavailable (perhaps
222                // because
223                // the SD card was inserted or ejected) we need to refresh the
224                // AppInfo with new label, icon and size information as
225                // appropriate
226                // given the newfound (un)availability of the application.
227                // A simple way to do that is to treat the refresh as a package
228                // removal followed by a package addition.
229                String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
230                if (pkgList == null || pkgList.length == 0) {
231                    // Ignore
232                    return;
233                }
234                boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr);
235                if (avail) {
236                    for (String pkgName : pkgList) {
237                        invalidatePackage(pkgName);
238                    }
239                }
240            }
241        }
242    }
243
244    void rebuildActiveSessions() {
245        synchronized (mEntriesMap) {
246            if (!mSessionsChanged) {
247                return;
248            }
249            mActiveSessions.clear();
250            for (int i = 0; i < mSessions.size(); i++) {
251                Session s = mSessions.get(i);
252                if (s.mResumed) {
253                    mActiveSessions.add(s);
254                }
255            }
256        }
257    }
258
259    class MainHandler extends Handler {
260        static final int MSG_REBUILD_COMPLETE = 1;
261        static final int MSG_PACKAGE_LIST_CHANGED = 2;
262        static final int MSG_PACKAGE_ICON_CHANGED = 3;
263        static final int MSG_PACKAGE_SIZE_CHANGED = 4;
264        static final int MSG_ALL_SIZES_COMPUTED = 5;
265        static final int MSG_RUNNING_STATE_CHANGED = 6;
266
267        @Override
268        public void handleMessage(Message msg) {
269            rebuildActiveSessions();
270            switch (msg.what) {
271                case MSG_REBUILD_COMPLETE:
272                    {
273                        Session s = (Session) msg.obj;
274                        if (mActiveSessions.contains(s)) {
275                            s.mCallbacks.onRebuildComplete();
276                        }
277                    }
278                    break;
279                case MSG_PACKAGE_LIST_CHANGED:
280                    for (int i = 0; i < mActiveSessions.size(); i++) {
281                        mActiveSessions.get(i).mCallbacks.onPackageListChanged();
282                    }
283                    break;
284                case MSG_PACKAGE_ICON_CHANGED:
285                    for (int i = 0; i < mActiveSessions.size(); i++) {
286                        mActiveSessions.get(i).mCallbacks.onPackageIconChanged();
287                    }
288                    break;
289                case MSG_PACKAGE_SIZE_CHANGED:
290                    for (int i = 0; i < mActiveSessions.size(); i++) {
291                        mActiveSessions.get(i).mCallbacks.onPackageSizeChanged((String) msg.obj);
292                    }
293                    break;
294                case MSG_ALL_SIZES_COMPUTED:
295                    for (int i = 0; i < mActiveSessions.size(); i++) {
296                        mActiveSessions.get(i).mCallbacks.onAllSizesComputed();
297                    }
298                    break;
299                case MSG_RUNNING_STATE_CHANGED:
300                    for (int i = 0; i < mActiveSessions.size(); i++) {
301                        mActiveSessions.get(i).mCallbacks.onRunningStateChanged(msg.arg1 != 0);
302                    }
303                    break;
304            }
305        }
306    }
307
308    private final MainHandler mMainHandler = new MainHandler();
309
310    // --------------------------------------------------------------
311
312    private static final Object sLock = new Object();
313    private static ApplicationsState sInstance;
314
315    static ApplicationsState getInstance(Context app) {
316        synchronized (sLock) {
317            if (sInstance == null) {
318                sInstance = new ApplicationsState(app);
319            }
320            return sInstance;
321        }
322    }
323
324    private ApplicationsState(Context app) {
325        mContext = app;
326        mPm = mContext.getPackageManager();
327        mThread = new HandlerThread("ApplicationsState.Loader", Process.THREAD_PRIORITY_BACKGROUND);
328        mThread.start();
329        mBackgroundHandler = new BackgroundHandler(mThread.getLooper());
330
331        // Only the owner can see all apps.
332        if (UserHandle.myUserId() == 0) {
333            mRetrieveFlags = PackageManager.GET_UNINSTALLED_PACKAGES
334                    | PackageManager.GET_DISABLED_COMPONENTS;
335        } else {
336            mRetrieveFlags = PackageManager.GET_DISABLED_COMPONENTS;
337        }
338
339        /**
340         * This is a trick to prevent the foreground thread from being delayed.
341         * The problem is that Dalvik monitors are initially spin locks, to keep
342         * them lightweight. This leads to unfair contention -- Even though the
343         * background thread only holds the lock for a short amount of time, if
344         * it keeps running and locking again it can prevent the main thread
345         * from acquiring its lock for a long time... sometimes even > 5 seconds
346         * (leading to an ANR). Dalvik will promote a monitor to a "real" lock
347         * if it detects enough contention on it. It doesn't figure this out
348         * fast enough for us here, though, so this little trick will force it
349         * to turn into a real lock immediately.
350         */
351        synchronized (mEntriesMap) {
352            try {
353                mEntriesMap.wait(1);
354            } catch (InterruptedException e) {
355            }
356        }
357    }
358
359    public class Session {
360        private final Callbacks mCallbacks;
361        private boolean mResumed;
362
363        private Session(Callbacks callbacks) {
364            mCallbacks = callbacks;
365        }
366
367        public void resume() {
368            if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
369            synchronized (mEntriesMap) {
370                if (!mResumed) {
371                    mResumed = true;
372                    mSessionsChanged = true;
373                    doResumeIfNeededLocked();
374                }
375            }
376            if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
377        }
378
379        public void pause() {
380            if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
381            synchronized (mEntriesMap) {
382                if (mResumed) {
383                    mResumed = false;
384                    mSessionsChanged = true;
385                    mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this);
386                    doPauseIfNeededLocked();
387                }
388                if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock");
389            }
390        }
391
392        public void release() {
393            pause();
394            synchronized (mEntriesMap) {
395                mSessions.remove(this);
396            }
397        }
398    }
399
400    public Session newSession(Callbacks callbacks) {
401        Session s = new Session(callbacks);
402        synchronized (mEntriesMap) {
403            mSessions.add(s);
404        }
405        return s;
406    }
407
408    private void doResumeIfNeededLocked() {
409        if (mResumed) {
410            return;
411        }
412        mResumed = true;
413        if (mPackageIntentReceiver == null) {
414            mPackageIntentReceiver = new PackageIntentReceiver();
415            mPackageIntentReceiver.registerReceiver();
416        }
417        mApplications = mPm.getInstalledApplications(mRetrieveFlags);
418        if (mApplications == null) {
419            mApplications = new ArrayList<ApplicationInfo>();
420        }
421
422        if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
423            // If an interesting part of the configuration has changed, we
424            // should completely reload the app entries.
425            mEntriesMap.clear();
426            mAppEntries.clear();
427        } else {
428            for (int i = 0; i < mAppEntries.size(); i++) {
429                mAppEntries.get(i).sizeStale = true;
430            }
431        }
432
433        for (int i = 0; i < mApplications.size(); i++) {
434            final ApplicationInfo info = mApplications.get(i);
435            // Need to trim out any applications that are disabled by
436            // something different than the user.
437            if (!info.enabled && info.enabledSetting
438                    != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
439                mApplications.remove(i);
440                i--;
441                continue;
442            }
443            final AppEntry entry = mEntriesMap.get(info.packageName);
444            if (entry != null) {
445                entry.info = info;
446            }
447        }
448        mCurComputingSizePkg = null;
449        if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
450            mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
451        }
452    }
453
454    private void doPauseIfNeededLocked() {
455        if (!mResumed) {
456            return;
457        }
458        for (int i = 0; i < mSessions.size(); i++) {
459            if (mSessions.get(i).mResumed) {
460                return;
461            }
462        }
463        mResumed = false;
464        if (mPackageIntentReceiver != null) {
465            mPackageIntentReceiver.unregisterReceiver();
466            mPackageIntentReceiver = null;
467        }
468    }
469
470    AppEntry getEntry(String packageName) {
471        if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
472        synchronized (mEntriesMap) {
473            AppEntry entry = mEntriesMap.get(packageName);
474            if (entry == null) {
475                for (int i = 0; i < mApplications.size(); i++) {
476                    ApplicationInfo info = mApplications.get(i);
477                    if (packageName.equals(info.packageName)) {
478                        entry = getEntryLocked(info);
479                        break;
480                    }
481                }
482            }
483            if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock");
484            return entry;
485        }
486    }
487
488    void requestSize(String packageName) {
489        if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock...");
490        synchronized (mEntriesMap) {
491            AppEntry entry = mEntriesMap.get(packageName);
492            if (entry != null) {
493                mPm.getPackageSizeInfo(packageName, mBackgroundHandler.mStatsObserver);
494            }
495            if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock");
496        }
497    }
498
499    private int indexOfApplicationInfoLocked(String pkgName) {
500        for (int i = mApplications.size() - 1; i >= 0; i--) {
501            if (mApplications.get(i).packageName.equals(pkgName)) {
502                return i;
503            }
504        }
505        return -1;
506    }
507
508    private void addPackage(String pkgName) {
509        try {
510            synchronized (mEntriesMap) {
511                if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock");
512                if (DEBUG) Log.i(TAG, "Adding package " + pkgName);
513                if (!mResumed) {
514                    // If we are not resumed, we will do a full query the
515                    // next time we resume, so there is no reason to do work
516                    // here.
517                    if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed");
518                    return;
519                }
520                if (indexOfApplicationInfoLocked(pkgName) >= 0) {
521                    if (DEBUG) Log.i(TAG, "Package already exists!");
522                    if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists");
523                    return;
524                }
525                ApplicationInfo info = mPm.getApplicationInfo(pkgName, mRetrieveFlags);
526                mApplications.add(info);
527                if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
528                    mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
529                }
530                if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
531                    mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
532                }
533                if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock");
534            }
535        } catch (NameNotFoundException e) {
536        }
537    }
538
539    void removePackage(String pkgName) {
540        synchronized (mEntriesMap) {
541            if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock");
542            int idx = indexOfApplicationInfoLocked(pkgName);
543            if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx);
544            if (idx >= 0) {
545                AppEntry entry = mEntriesMap.get(pkgName);
546                if (DEBUG) Log.i(TAG, "removePackage: " + entry);
547                if (entry != null) {
548                    mEntriesMap.remove(pkgName);
549                    mAppEntries.remove(entry);
550                }
551                mApplications.remove(idx);
552                if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
553                    mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
554                }
555            }
556            if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock");
557        }
558    }
559
560    void invalidatePackage(String pkgName) {
561        removePackage(pkgName);
562        addPackage(pkgName);
563    }
564
565    private AppEntry getEntryLocked(ApplicationInfo info) {
566        AppEntry entry = mEntriesMap.get(info.packageName);
567        if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
568        if (entry == null) {
569            if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName);
570            entry = new AppEntry(mContext, info, mCurId++);
571            mEntriesMap.put(info.packageName, entry);
572            mAppEntries.add(entry);
573        } else if (entry.info != info) {
574            entry.info = info;
575        }
576        return entry;
577    }
578
579    // --------------------------------------------------------------
580
581    private final HandlerThread mThread;
582    private final BackgroundHandler mBackgroundHandler;
583
584    private class BackgroundHandler extends Handler {
585        static final int MSG_REBUILD_LIST = 1;
586        static final int MSG_LOAD_ENTRIES = 2;
587        static final int MSG_LOAD_SIZES = 3;
588
589        boolean mRunning;
590
591        final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
592            public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
593                boolean sizeChanged = false;
594                synchronized (mEntriesMap) {
595                    if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock");
596                    AppEntry entry = mEntriesMap.get(stats.packageName);
597                    if (entry != null) {
598                        synchronized (entry) {
599                            entry.sizeStale = false;
600                            entry.sizeLoadStart = 0;
601                            long externalCodeSize = stats.externalCodeSize + stats.externalObbSize;
602                            long externalDataSize = stats.externalDataSize
603                                    + stats.externalMediaSize;
604                            long newSize = externalCodeSize + externalDataSize
605                                    + getTotalInternalSize(stats);
606                            if (entry.size != newSize || entry.cacheSize != stats.cacheSize
607                                    || entry.codeSize != stats.codeSize
608                                    || entry.dataSize != stats.dataSize
609                                    || entry.externalCodeSize != externalCodeSize
610                                    || entry.externalDataSize != externalDataSize
611                                    || entry.externalCacheSize != stats.externalCacheSize) {
612                                entry.size = newSize;
613                                entry.cacheSize = stats.cacheSize;
614                                entry.codeSize = stats.codeSize;
615                                entry.dataSize = stats.dataSize;
616                                entry.externalCodeSize = externalCodeSize;
617                                entry.externalDataSize = externalDataSize;
618                                entry.externalCacheSize = stats.externalCacheSize;
619                                entry.sizeStr = getSizeStr(entry.size);
620                                entry.internalSize = getTotalInternalSize(stats);
621                                entry.externalSize = getTotalExternalSize(stats);
622                                if (DEBUG)
623                                    Log.i(TAG, "Set size of " + entry.label + " " + entry + ": "
624                                            + entry.sizeStr);
625                                sizeChanged = true;
626                            }
627                        }
628                        if (sizeChanged) {
629                            Message msg = mMainHandler.obtainMessage(
630                                    MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName);
631                            mMainHandler.sendMessage(msg);
632                        }
633                    }
634                    if (mCurComputingSizePkg == null
635                            || mCurComputingSizePkg.equals(stats.packageName)) {
636                        mCurComputingSizePkg = null;
637                        sendEmptyMessage(MSG_LOAD_SIZES);
638                    }
639                    if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock");
640                }
641            }
642        };
643
644        BackgroundHandler(Looper looper) {
645            super(looper);
646        }
647
648        @Override
649        public void handleMessage(Message msg) {
650
651            switch (msg.what) {
652                case MSG_REBUILD_LIST:
653                    break;
654                case MSG_LOAD_ENTRIES:
655                    {
656                        int numDone = 0;
657                        synchronized (mEntriesMap) {
658                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock");
659                            for (int i = 0; i < mApplications.size() && numDone < 6; i++) {
660                                if (!mRunning) {
661                                    mRunning = true;
662                                    Message m = mMainHandler.obtainMessage(
663                                            MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
664                                    mMainHandler.sendMessage(m);
665                                }
666                                ApplicationInfo info = mApplications.get(i);
667                                if (mEntriesMap.get(info.packageName) == null) {
668                                    numDone++;
669                                    getEntryLocked(info);
670                                }
671                            }
672                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock");
673                        }
674
675                        if (numDone >= 6) {
676                            sendEmptyMessage(MSG_LOAD_ENTRIES);
677                        } else {
678                            sendEmptyMessage(MSG_LOAD_SIZES);
679                        }
680                    }
681                    break;
682                case MSG_LOAD_SIZES:
683                    synchronized (mEntriesMap) {
684                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock");
685                        if (mCurComputingSizePkg != null) {
686                            if (DEBUG_LOCKING) {
687                                Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing");
688                            }
689                            return;
690                        }
691
692                        long now = SystemClock.uptimeMillis();
693                        for (int i = 0; i < mAppEntries.size(); i++) {
694                            AppEntry entry = mAppEntries.get(i);
695                            if (entry.size == SIZE_UNKNOWN || entry.sizeStale) {
696                                if (entry.sizeLoadStart == 0
697                                        || (entry.sizeLoadStart < (now - 20 * 1000))) {
698                                    if (!mRunning) {
699                                        mRunning = true;
700                                        Message m = mMainHandler.obtainMessage(
701                                                MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
702                                        mMainHandler.sendMessage(m);
703                                    }
704                                    entry.sizeLoadStart = now;
705                                    mCurComputingSizePkg = entry.info.packageName;
706                                    mPm.getPackageSizeInfo(mCurComputingSizePkg, mStatsObserver);
707                                }
708                                if (DEBUG_LOCKING) {
709                                    Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing");
710                                }
711                                return;
712                            }
713                        }
714                        if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) {
715                            mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED);
716                            mRunning = false;
717                            Message m = mMainHandler.obtainMessage(
718                                    MainHandler.MSG_RUNNING_STATE_CHANGED, 0);
719                            mMainHandler.sendMessage(m);
720                        }
721                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock");
722                    }
723                    break;
724            }
725        }
726
727        private long getTotalInternalSize(PackageStats ps) {
728            if (ps != null) {
729                return ps.codeSize + ps.dataSize;
730            }
731            return SIZE_INVALID;
732        }
733
734        private long getTotalExternalSize(PackageStats ps) {
735            if (ps != null) {
736                // We also include the cache size here because for non-emulated
737                // we don't automtically clean cache files.
738                return ps.externalCodeSize + ps.externalDataSize + ps.externalCacheSize
739                        + ps.externalMediaSize + ps.externalObbSize;
740            }
741            return SIZE_INVALID;
742        }
743
744        private String getSizeStr(long size) {
745            if (size >= 0) {
746                return Formatter.formatFileSize(mContext, size);
747            }
748            return null;
749        }
750    }
751}
752