Home | History | Annotate | Download | only in applications
      1 /*
      2  * Copyright (C) 2015 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 
     17 package com.android.settingslib.applications;
     18 
     19 import android.annotation.IntDef;
     20 import android.app.ActivityManager;
     21 import android.app.AppGlobals;
     22 import android.app.Application;
     23 import android.app.usage.StorageStats;
     24 import android.app.usage.StorageStatsManager;
     25 import android.arch.lifecycle.Lifecycle;
     26 import android.arch.lifecycle.LifecycleObserver;
     27 import android.arch.lifecycle.OnLifecycleEvent;
     28 import android.content.BroadcastReceiver;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.content.IntentFilter;
     32 import android.content.pm.ApplicationInfo;
     33 import android.content.pm.IPackageManager;
     34 import android.content.pm.IPackageStatsObserver;
     35 import android.content.pm.PackageManager;
     36 import android.content.pm.PackageManager.NameNotFoundException;
     37 import android.content.pm.PackageStats;
     38 import android.content.pm.ParceledListSlice;
     39 import android.content.pm.ResolveInfo;
     40 import android.content.pm.UserInfo;
     41 import android.graphics.drawable.Drawable;
     42 import android.net.Uri;
     43 import android.os.Handler;
     44 import android.os.HandlerThread;
     45 import android.os.Looper;
     46 import android.os.Message;
     47 import android.os.Process;
     48 import android.os.RemoteException;
     49 import android.os.SystemClock;
     50 import android.os.UserHandle;
     51 import android.os.UserManager;
     52 import android.support.annotation.VisibleForTesting;
     53 import android.text.format.Formatter;
     54 import android.util.IconDrawableFactory;
     55 import android.util.Log;
     56 import android.util.SparseArray;
     57 
     58 import com.android.internal.R;
     59 import com.android.internal.util.ArrayUtils;
     60 
     61 import java.io.File;
     62 import java.io.IOException;
     63 import java.lang.annotation.Retention;
     64 import java.lang.annotation.RetentionPolicy;
     65 import java.text.Collator;
     66 import java.text.Normalizer;
     67 import java.text.Normalizer.Form;
     68 import java.util.ArrayList;
     69 import java.util.Collections;
     70 import java.util.Comparator;
     71 import java.util.HashMap;
     72 import java.util.List;
     73 import java.util.Objects;
     74 import java.util.UUID;
     75 import java.util.regex.Pattern;
     76 
     77 /**
     78  * Keeps track of information about all installed applications, lazy-loading
     79  * as needed.
     80  */
     81 public class ApplicationsState {
     82     static final String TAG = "ApplicationsState";
     83     static final boolean DEBUG = false;
     84     static final boolean DEBUG_LOCKING = false;
     85 
     86     public static final int SIZE_UNKNOWN = -1;
     87     public static final int SIZE_INVALID = -2;
     88 
     89     static final Pattern REMOVE_DIACRITICALS_PATTERN
     90             = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
     91 
     92     static final Object sLock = new Object();
     93     static ApplicationsState sInstance;
     94 
     95     public static ApplicationsState getInstance(Application app) {
     96         synchronized (sLock) {
     97             if (sInstance == null) {
     98                 sInstance = new ApplicationsState(app);
     99             }
    100             return sInstance;
    101         }
    102     }
    103 
    104     final Context mContext;
    105     final PackageManager mPm;
    106     final IconDrawableFactory mDrawableFactory;
    107     final IPackageManager mIpm;
    108     final UserManager mUm;
    109     final StorageStatsManager mStats;
    110     final int mAdminRetrieveFlags;
    111     final int mRetrieveFlags;
    112     PackageIntentReceiver mPackageIntentReceiver;
    113 
    114     boolean mResumed;
    115     boolean mHaveDisabledApps;
    116     boolean mHaveInstantApps;
    117 
    118     // Information about all applications.  Synchronize on mEntriesMap
    119     // to protect access to these.
    120     final ArrayList<Session> mSessions = new ArrayList<Session>();
    121     final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>();
    122     final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges();
    123     // Map: userid => (Map: package name => AppEntry)
    124     final SparseArray<HashMap<String, AppEntry>> mEntriesMap =
    125             new SparseArray<HashMap<String, AppEntry>>();
    126     final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>();
    127     List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>();
    128     long mCurId = 1;
    129     UUID mCurComputingSizeUuid;
    130     String mCurComputingSizePkg;
    131     int mCurComputingSizeUserId;
    132     boolean mSessionsChanged;
    133 
    134     // Temporary for dispatching session callbacks.  Only touched by main thread.
    135     final ArrayList<Session> mActiveSessions = new ArrayList<Session>();
    136 
    137     final HandlerThread mThread;
    138     final BackgroundHandler mBackgroundHandler;
    139     final MainHandler mMainHandler = new MainHandler(Looper.getMainLooper());
    140 
    141     /** Requests that the home app is loaded. */
    142     public static final int FLAG_SESSION_REQUEST_HOME_APP = 1 << 0;
    143 
    144     /** Requests that icons are loaded. */
    145     public static final int FLAG_SESSION_REQUEST_ICONS = 1 << 1;
    146 
    147     /** Requests that sizes are loaded. */
    148     public static final int FLAG_SESSION_REQUEST_SIZES = 1 << 2;
    149 
    150     /** Requests that launcher intents are resolved. */
    151     public static final int FLAG_SESSION_REQUEST_LAUNCHER = 1 << 3;
    152 
    153     /** Requests that leanback launcher intents are resolved. */
    154     public static final int FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER = 1 << 4;
    155 
    156     /**
    157      * Flags to configure the session to request various types of info.
    158      */
    159     @IntDef(prefix = { "FLAG_SESSION_" }, value = {
    160             FLAG_SESSION_REQUEST_HOME_APP,
    161             FLAG_SESSION_REQUEST_ICONS,
    162             FLAG_SESSION_REQUEST_SIZES,
    163             FLAG_SESSION_REQUEST_LAUNCHER,
    164             FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER
    165     })
    166     @Retention(RetentionPolicy.SOURCE)
    167     public @interface SessionFlags {}
    168 
    169     public static final @SessionFlags int DEFAULT_SESSION_FLAGS =
    170             FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS |
    171             FLAG_SESSION_REQUEST_SIZES | FLAG_SESSION_REQUEST_LAUNCHER;
    172 
    173     private ApplicationsState(Application app) {
    174         mContext = app;
    175         mPm = mContext.getPackageManager();
    176         mDrawableFactory = IconDrawableFactory.newInstance(mContext);
    177         mIpm = AppGlobals.getPackageManager();
    178         mUm = mContext.getSystemService(UserManager.class);
    179         mStats = mContext.getSystemService(StorageStatsManager.class);
    180         for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) {
    181             mEntriesMap.put(userId, new HashMap<String, AppEntry>());
    182         }
    183         mThread = new HandlerThread("ApplicationsState.Loader",
    184                 Process.THREAD_PRIORITY_BACKGROUND);
    185         mThread.start();
    186         mBackgroundHandler = new BackgroundHandler(mThread.getLooper());
    187 
    188         // Only the owner can see all apps.
    189         mAdminRetrieveFlags = PackageManager.MATCH_ANY_USER |
    190                 PackageManager.MATCH_DISABLED_COMPONENTS |
    191                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
    192         mRetrieveFlags = PackageManager.MATCH_DISABLED_COMPONENTS |
    193                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
    194 
    195         /**
    196          * This is a trick to prevent the foreground thread from being delayed.
    197          * The problem is that Dalvik monitors are initially spin locks, to keep
    198          * them lightweight.  This leads to unfair contention -- Even though the
    199          * background thread only holds the lock for a short amount of time, if
    200          * it keeps running and locking again it can prevent the main thread from
    201          * acquiring its lock for a long time...  sometimes even > 5 seconds
    202          * (leading to an ANR).
    203          *
    204          * Dalvik will promote a monitor to a "real" lock if it detects enough
    205          * contention on it.  It doesn't figure this out fast enough for us
    206          * here, though, so this little trick will force it to turn into a real
    207          * lock immediately.
    208          */
    209         synchronized (mEntriesMap) {
    210             try {
    211                 mEntriesMap.wait(1);
    212             } catch (InterruptedException e) {
    213             }
    214         }
    215     }
    216 
    217     public Looper getBackgroundLooper() {
    218         return mThread.getLooper();
    219     }
    220 
    221     public Session newSession(Callbacks callbacks) {
    222         return newSession(callbacks, null);
    223     }
    224 
    225     public Session newSession(Callbacks callbacks, Lifecycle lifecycle) {
    226         Session s = new Session(callbacks, lifecycle);
    227         synchronized (mEntriesMap) {
    228             mSessions.add(s);
    229         }
    230         return s;
    231     }
    232 
    233     void doResumeIfNeededLocked() {
    234         if (mResumed) {
    235             return;
    236         }
    237         mResumed = true;
    238         if (mPackageIntentReceiver == null) {
    239             mPackageIntentReceiver = new PackageIntentReceiver();
    240             mPackageIntentReceiver.registerReceiver();
    241         }
    242         mApplications = new ArrayList<ApplicationInfo>();
    243         for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) {
    244             try {
    245                 // If this user is new, it needs a map created.
    246                 if (mEntriesMap.indexOfKey(user.id) < 0) {
    247                     mEntriesMap.put(user.id, new HashMap<String, AppEntry>());
    248                 }
    249                 @SuppressWarnings("unchecked")
    250                 ParceledListSlice<ApplicationInfo> list =
    251                         mIpm.getInstalledApplications(
    252                                 user.isAdmin() ? mAdminRetrieveFlags : mRetrieveFlags,
    253                                 user.id);
    254                 mApplications.addAll(list.getList());
    255             } catch (RemoteException e) {
    256             }
    257         }
    258 
    259         if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
    260             // If an interesting part of the configuration has changed, we
    261             // should completely reload the app entries.
    262             clearEntries();
    263         } else {
    264             for (int i=0; i<mAppEntries.size(); i++) {
    265                 mAppEntries.get(i).sizeStale = true;
    266             }
    267         }
    268 
    269         mHaveDisabledApps = false;
    270         mHaveInstantApps = false;
    271         for (int i=0; i<mApplications.size(); i++) {
    272             final ApplicationInfo info = mApplications.get(i);
    273             // Need to trim out any applications that are disabled by
    274             // something different than the user.
    275             if (!info.enabled) {
    276                 if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
    277                     mApplications.remove(i);
    278                     i--;
    279                     continue;
    280                 }
    281                 mHaveDisabledApps = true;
    282             }
    283             if (!mHaveInstantApps && AppUtils.isInstant(info)) {
    284                 mHaveInstantApps = true;
    285             }
    286 
    287             int userId = UserHandle.getUserId(info.uid);
    288             final AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
    289             if (entry != null) {
    290                 entry.info = info;
    291             }
    292         }
    293         if (mAppEntries.size() > mApplications.size()) {
    294             // There are less apps now, some must have been uninstalled.
    295             clearEntries();
    296         }
    297         mCurComputingSizePkg = null;
    298         if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
    299             mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
    300         }
    301     }
    302 
    303     @VisibleForTesting
    304     void clearEntries() {
    305         for (int i = 0; i < mEntriesMap.size(); i++) {
    306             mEntriesMap.valueAt(i).clear();
    307         }
    308         mAppEntries.clear();
    309     }
    310 
    311     public boolean haveDisabledApps() {
    312         return mHaveDisabledApps;
    313     }
    314     public boolean haveInstantApps() {
    315         return mHaveInstantApps;
    316     }
    317 
    318     void doPauseIfNeededLocked() {
    319         if (!mResumed) {
    320             return;
    321         }
    322         for (int i=0; i<mSessions.size(); i++) {
    323             if (mSessions.get(i).mResumed) {
    324                 return;
    325             }
    326         }
    327         doPauseLocked();
    328     }
    329 
    330     void doPauseLocked() {
    331         mResumed = false;
    332         if (mPackageIntentReceiver != null) {
    333             mPackageIntentReceiver.unregisterReceiver();
    334             mPackageIntentReceiver = null;
    335         }
    336     }
    337 
    338     public AppEntry getEntry(String packageName, int userId) {
    339         if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
    340         synchronized (mEntriesMap) {
    341             AppEntry entry = mEntriesMap.get(userId).get(packageName);
    342             if (entry == null) {
    343                 ApplicationInfo info = getAppInfoLocked(packageName, userId);
    344                 if (info == null) {
    345                     try {
    346                         info = mIpm.getApplicationInfo(packageName, 0, userId);
    347                     } catch (RemoteException e) {
    348                         Log.w(TAG, "getEntry couldn't reach PackageManager", e);
    349                         return null;
    350                     }
    351                 }
    352                 if (info != null) {
    353                     entry = getEntryLocked(info);
    354                 }
    355             }
    356             if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock");
    357             return entry;
    358         }
    359     }
    360 
    361     private ApplicationInfo getAppInfoLocked(String pkg, int userId) {
    362         for (int i = 0; i < mApplications.size(); i++) {
    363             ApplicationInfo info = mApplications.get(i);
    364             if (pkg.equals(info.packageName)
    365                     && userId == UserHandle.getUserId(info.uid)) {
    366                 return info;
    367             }
    368         }
    369         return null;
    370     }
    371 
    372     public void ensureIcon(AppEntry entry) {
    373         if (entry.icon != null) {
    374             return;
    375         }
    376         synchronized (entry) {
    377             entry.ensureIconLocked(mContext, mDrawableFactory);
    378         }
    379     }
    380 
    381     public void requestSize(String packageName, int userId) {
    382         if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock...");
    383         synchronized (mEntriesMap) {
    384             AppEntry entry = mEntriesMap.get(userId).get(packageName);
    385             if (entry != null && hasFlag(entry.info.flags, ApplicationInfo.FLAG_INSTALLED)) {
    386                 mBackgroundHandler.post(
    387                         () -> {
    388                             try {
    389                                 final StorageStats stats =
    390                                         mStats.queryStatsForPackage(
    391                                                 entry.info.storageUuid,
    392                                                 packageName,
    393                                                 UserHandle.of(userId));
    394                                 final long cacheQuota =
    395                                         mStats.getCacheQuotaBytes(
    396                                                 entry.info.storageUuid.toString(), entry.info.uid);
    397                                 final PackageStats legacy = new PackageStats(packageName, userId);
    398                                 legacy.codeSize = stats.getCodeBytes();
    399                                 legacy.dataSize = stats.getDataBytes();
    400                                 legacy.cacheSize = Math.min(stats.getCacheBytes(), cacheQuota);
    401                                 try {
    402                                     mBackgroundHandler.mStatsObserver.onGetStatsCompleted(
    403                                             legacy, true);
    404                                 } catch (RemoteException ignored) {
    405                                 }
    406                             } catch (NameNotFoundException | IOException e) {
    407                                 Log.w(TAG, "Failed to query stats: " + e);
    408                                 try {
    409                                     mBackgroundHandler.mStatsObserver.onGetStatsCompleted(
    410                                             null, false);
    411                                 } catch (RemoteException ignored) {
    412                                 }
    413                             }
    414                         });
    415             }
    416             if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock");
    417         }
    418     }
    419 
    420     long sumCacheSizes() {
    421         long sum = 0;
    422         if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock...");
    423         synchronized (mEntriesMap) {
    424             if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock");
    425             for (int i=mAppEntries.size()-1; i>=0; i--) {
    426                 sum += mAppEntries.get(i).cacheSize;
    427             }
    428             if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock");
    429         }
    430         return sum;
    431     }
    432 
    433     int indexOfApplicationInfoLocked(String pkgName, int userId) {
    434         for (int i=mApplications.size()-1; i>=0; i--) {
    435             ApplicationInfo appInfo = mApplications.get(i);
    436             if (appInfo.packageName.equals(pkgName)
    437                     && UserHandle.getUserId(appInfo.uid) == userId) {
    438                 return i;
    439             }
    440         }
    441         return -1;
    442     }
    443 
    444     void addPackage(String pkgName, int userId) {
    445         try {
    446             synchronized (mEntriesMap) {
    447                 if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock");
    448                 if (DEBUG) Log.i(TAG, "Adding package " + pkgName);
    449                 if (!mResumed) {
    450                     // If we are not resumed, we will do a full query the
    451                     // next time we resume, so there is no reason to do work
    452                     // here.
    453                     if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed");
    454                     return;
    455                 }
    456                 if (indexOfApplicationInfoLocked(pkgName, userId) >= 0) {
    457                     if (DEBUG) Log.i(TAG, "Package already exists!");
    458                     if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists");
    459                     return;
    460                 }
    461                 ApplicationInfo info = mIpm.getApplicationInfo(pkgName,
    462                         mUm.isUserAdmin(userId) ? mAdminRetrieveFlags : mRetrieveFlags,
    463                         userId);
    464                 if (info == null) {
    465                     return;
    466                 }
    467                 if (!info.enabled) {
    468                     if (info.enabledSetting
    469                             != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
    470                         return;
    471                     }
    472                     mHaveDisabledApps = true;
    473                 }
    474                 if (AppUtils.isInstant(info)) {
    475                     mHaveInstantApps = true;
    476                 }
    477                 mApplications.add(info);
    478                 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
    479                     mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
    480                 }
    481                 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
    482                     mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
    483                 }
    484                 if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock");
    485             }
    486         } catch (RemoteException e) {
    487         }
    488     }
    489 
    490     public void removePackage(String pkgName, int userId) {
    491         synchronized (mEntriesMap) {
    492             if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock");
    493             int idx = indexOfApplicationInfoLocked(pkgName, userId);
    494             if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx);
    495             if (idx >= 0) {
    496                 AppEntry entry = mEntriesMap.get(userId).get(pkgName);
    497                 if (DEBUG) Log.i(TAG, "removePackage: " + entry);
    498                 if (entry != null) {
    499                     mEntriesMap.get(userId).remove(pkgName);
    500                     mAppEntries.remove(entry);
    501                 }
    502                 ApplicationInfo info = mApplications.get(idx);
    503                 mApplications.remove(idx);
    504                 if (!info.enabled) {
    505                     mHaveDisabledApps = false;
    506                     for (ApplicationInfo otherInfo : mApplications) {
    507                         if (!otherInfo.enabled) {
    508                             mHaveDisabledApps = true;
    509                             break;
    510                         }
    511                     }
    512                 }
    513                 if (AppUtils.isInstant(info)) {
    514                     mHaveInstantApps = false;
    515                     for (ApplicationInfo otherInfo : mApplications) {
    516                         if (AppUtils.isInstant(otherInfo)) {
    517                             mHaveInstantApps = true;
    518                             break;
    519                         }
    520                     }
    521                 }
    522                 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
    523                     mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
    524                 }
    525             }
    526             if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock");
    527         }
    528     }
    529 
    530     public void invalidatePackage(String pkgName, int userId) {
    531         removePackage(pkgName, userId);
    532         addPackage(pkgName, userId);
    533     }
    534 
    535     private void addUser(int userId) {
    536         final int profileIds[] = mUm.getProfileIdsWithDisabled(UserHandle.myUserId());
    537         if (ArrayUtils.contains(profileIds, userId)) {
    538             synchronized (mEntriesMap) {
    539                 mEntriesMap.put(userId, new HashMap<String, AppEntry>());
    540                 if (mResumed) {
    541                     // If resumed, Manually pause, then cause a resume to repopulate the app list.
    542                     // This is the simplest way to reload the packages so that the new user
    543                     // is included.  Otherwise the list will be repopulated on next resume.
    544                     doPauseLocked();
    545                     doResumeIfNeededLocked();
    546                 }
    547                 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
    548                     mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
    549                 }
    550             }
    551         }
    552     }
    553 
    554     private void removeUser(int userId) {
    555         synchronized (mEntriesMap) {
    556             HashMap<String, AppEntry> userMap = mEntriesMap.get(userId);
    557             if (userMap != null) {
    558                 for (AppEntry appEntry : userMap.values()) {
    559                     mAppEntries.remove(appEntry);
    560                     mApplications.remove(appEntry.info);
    561                 }
    562                 mEntriesMap.remove(userId);
    563                 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
    564                     mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
    565                 }
    566             }
    567         }
    568     }
    569 
    570     private AppEntry getEntryLocked(ApplicationInfo info) {
    571         int userId = UserHandle.getUserId(info.uid);
    572         AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
    573         if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
    574         if (entry == null) {
    575             if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName);
    576             entry = new AppEntry(mContext, info, mCurId++);
    577             mEntriesMap.get(userId).put(info.packageName, entry);
    578             mAppEntries.add(entry);
    579         } else if (entry.info != info) {
    580             entry.info = info;
    581         }
    582         return entry;
    583     }
    584 
    585     // --------------------------------------------------------------
    586 
    587     private long getTotalInternalSize(PackageStats ps) {
    588         if (ps != null) {
    589             return ps.codeSize + ps.dataSize;
    590         }
    591         return SIZE_INVALID;
    592     }
    593 
    594     private long getTotalExternalSize(PackageStats ps) {
    595         if (ps != null) {
    596             // We also include the cache size here because for non-emulated
    597             // we don't automtically clean cache files.
    598             return ps.externalCodeSize + ps.externalDataSize
    599                     + ps.externalCacheSize
    600                     + ps.externalMediaSize + ps.externalObbSize;
    601         }
    602         return SIZE_INVALID;
    603     }
    604 
    605     private String getSizeStr(long size) {
    606         if (size >= 0) {
    607             return Formatter.formatFileSize(mContext, size);
    608         }
    609         return null;
    610     }
    611 
    612     void rebuildActiveSessions() {
    613         synchronized (mEntriesMap) {
    614             if (!mSessionsChanged) {
    615                 return;
    616             }
    617             mActiveSessions.clear();
    618             for (int i=0; i<mSessions.size(); i++) {
    619                 Session s = mSessions.get(i);
    620                 if (s.mResumed) {
    621                     mActiveSessions.add(s);
    622                 }
    623             }
    624         }
    625     }
    626 
    627     public static String normalize(String str) {
    628         String tmp = Normalizer.normalize(str, Form.NFD);
    629         return REMOVE_DIACRITICALS_PATTERN.matcher(tmp)
    630                 .replaceAll("").toLowerCase();
    631     }
    632 
    633     public class Session implements LifecycleObserver {
    634 
    635         final Callbacks mCallbacks;
    636         boolean mResumed;
    637 
    638         // Rebuilding of app list.  Synchronized on mRebuildSync.
    639         final Object mRebuildSync = new Object();
    640         boolean mRebuildRequested;
    641         boolean mRebuildAsync;
    642         AppFilter mRebuildFilter;
    643         Comparator<AppEntry> mRebuildComparator;
    644         ArrayList<AppEntry> mRebuildResult;
    645         ArrayList<AppEntry> mLastAppList;
    646         boolean mRebuildForeground;
    647 
    648         private final boolean mHasLifecycle;
    649         @SessionFlags private int mFlags = DEFAULT_SESSION_FLAGS;
    650 
    651         Session(Callbacks callbacks, Lifecycle lifecycle) {
    652             mCallbacks = callbacks;
    653             if (lifecycle != null) {
    654                 lifecycle.addObserver(this);
    655                 mHasLifecycle = true;
    656             } else {
    657                 mHasLifecycle = false;
    658             }
    659         }
    660 
    661         public @SessionFlags int getSessionFlags() {
    662             return mFlags;
    663         }
    664 
    665         public void setSessionFlags(@SessionFlags int flags) {
    666             mFlags = flags;
    667         }
    668 
    669         @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    670         public void onResume() {
    671             if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
    672             synchronized (mEntriesMap) {
    673                 if (!mResumed) {
    674                     mResumed = true;
    675                     mSessionsChanged = true;
    676                     doResumeIfNeededLocked();
    677                 }
    678             }
    679             if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
    680         }
    681 
    682         @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    683         public void onPause() {
    684             if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
    685             synchronized (mEntriesMap) {
    686                 if (mResumed) {
    687                     mResumed = false;
    688                     mSessionsChanged = true;
    689                     mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this);
    690                     doPauseIfNeededLocked();
    691                 }
    692                 if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock");
    693             }
    694         }
    695 
    696         public ArrayList<AppEntry> getAllApps() {
    697             synchronized (mEntriesMap) {
    698                 return new ArrayList<>(mAppEntries);
    699             }
    700         }
    701 
    702         // Creates a new list of app entries with the given filter and comparator.
    703         public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) {
    704             return rebuild(filter, comparator, true);
    705         }
    706 
    707         public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator,
    708                 boolean foreground) {
    709             synchronized (mRebuildSync) {
    710                 synchronized (mRebuildingSessions) {
    711                     mRebuildingSessions.add(this);
    712                     mRebuildRequested = true;
    713                     mRebuildAsync = true;
    714                     mRebuildFilter = filter;
    715                     mRebuildComparator = comparator;
    716                     mRebuildForeground = foreground;
    717                     mRebuildResult = null;
    718                     if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) {
    719                         Message msg = mBackgroundHandler.obtainMessage(
    720                                 BackgroundHandler.MSG_REBUILD_LIST);
    721                         mBackgroundHandler.sendMessage(msg);
    722                     }
    723                 }
    724 
    725                 return null;
    726             }
    727         }
    728 
    729         void handleRebuildList() {
    730             AppFilter filter;
    731             Comparator<AppEntry> comparator;
    732             synchronized (mRebuildSync) {
    733                 if (!mRebuildRequested) {
    734                     return;
    735                 }
    736 
    737                 filter = mRebuildFilter;
    738                 comparator = mRebuildComparator;
    739                 mRebuildRequested = false;
    740                 mRebuildFilter = null;
    741                 mRebuildComparator = null;
    742                 if (mRebuildForeground) {
    743                     Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
    744                     mRebuildForeground = false;
    745                 }
    746             }
    747 
    748             if (filter != null) {
    749                 filter.init(mContext);
    750             }
    751 
    752             List<AppEntry> apps;
    753             synchronized (mEntriesMap) {
    754                 apps = new ArrayList<>(mAppEntries);
    755             }
    756 
    757             ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>();
    758             if (DEBUG) Log.i(TAG, "Rebuilding...");
    759             for (int i=0; i<apps.size(); i++) {
    760                 AppEntry entry = apps.get(i);
    761                 if (entry != null && (filter == null || filter.filterApp(entry))) {
    762                     synchronized (mEntriesMap) {
    763                         if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock");
    764                         if (comparator != null) {
    765                             // Only need the label if we are going to be sorting.
    766                             entry.ensureLabel(mContext);
    767                         }
    768                         if (DEBUG) Log.i(TAG, "Using " + entry.info.packageName + ": " + entry);
    769                         filteredApps.add(entry);
    770                         if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock");
    771                     }
    772                 }
    773             }
    774 
    775             if (comparator != null) {
    776                 synchronized (mEntriesMap) {
    777                     // Locking to ensure that the background handler does not mutate
    778                     // the size of AppEntries used for ordering while sorting.
    779                     Collections.sort(filteredApps, comparator);
    780                 }
    781             }
    782 
    783             synchronized (mRebuildSync) {
    784                 if (!mRebuildRequested) {
    785                     mLastAppList = filteredApps;
    786                     if (!mRebuildAsync) {
    787                         mRebuildResult = filteredApps;
    788                         mRebuildSync.notifyAll();
    789                     } else {
    790                         if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) {
    791                             Message msg = mMainHandler.obtainMessage(
    792                                     MainHandler.MSG_REBUILD_COMPLETE, this);
    793                             mMainHandler.sendMessage(msg);
    794                         }
    795                     }
    796                 }
    797             }
    798 
    799             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    800         }
    801 
    802         @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    803         public void onDestroy() {
    804             if (!mHasLifecycle) {
    805                 // TODO: Legacy, remove this later once all usages are switched to Lifecycle
    806                 onPause();
    807             }
    808             synchronized (mEntriesMap) {
    809                 mSessions.remove(this);
    810             }
    811         }
    812     }
    813 
    814     class MainHandler extends Handler {
    815         static final int MSG_REBUILD_COMPLETE = 1;
    816         static final int MSG_PACKAGE_LIST_CHANGED = 2;
    817         static final int MSG_PACKAGE_ICON_CHANGED = 3;
    818         static final int MSG_PACKAGE_SIZE_CHANGED = 4;
    819         static final int MSG_ALL_SIZES_COMPUTED = 5;
    820         static final int MSG_RUNNING_STATE_CHANGED = 6;
    821         static final int MSG_LAUNCHER_INFO_CHANGED = 7;
    822         static final int MSG_LOAD_ENTRIES_COMPLETE = 8;
    823 
    824         public MainHandler(Looper looper) {
    825             super(looper);
    826         }
    827 
    828         @Override
    829         public void handleMessage(Message msg) {
    830             rebuildActiveSessions();
    831             switch (msg.what) {
    832                 case MSG_REBUILD_COMPLETE: {
    833                     Session s = (Session)msg.obj;
    834                     if (mActiveSessions.contains(s)) {
    835                         s.mCallbacks.onRebuildComplete(s.mLastAppList);
    836                     }
    837                 } break;
    838                 case MSG_PACKAGE_LIST_CHANGED: {
    839                     for (int i=0; i<mActiveSessions.size(); i++) {
    840                         mActiveSessions.get(i).mCallbacks.onPackageListChanged();
    841                     }
    842                 } break;
    843                 case MSG_PACKAGE_ICON_CHANGED: {
    844                     for (int i=0; i<mActiveSessions.size(); i++) {
    845                         mActiveSessions.get(i).mCallbacks.onPackageIconChanged();
    846                     }
    847                 } break;
    848                 case MSG_PACKAGE_SIZE_CHANGED: {
    849                     for (int i=0; i<mActiveSessions.size(); i++) {
    850                         mActiveSessions.get(i).mCallbacks.onPackageSizeChanged(
    851                                 (String)msg.obj);
    852                     }
    853                 } break;
    854                 case MSG_ALL_SIZES_COMPUTED: {
    855                     for (int i=0; i<mActiveSessions.size(); i++) {
    856                         mActiveSessions.get(i).mCallbacks.onAllSizesComputed();
    857                     }
    858                 } break;
    859                 case MSG_RUNNING_STATE_CHANGED: {
    860                     for (int i=0; i<mActiveSessions.size(); i++) {
    861                         mActiveSessions.get(i).mCallbacks.onRunningStateChanged(
    862                                 msg.arg1 != 0);
    863                     }
    864                 } break;
    865                 case MSG_LAUNCHER_INFO_CHANGED: {
    866                     for (int i=0; i<mActiveSessions.size(); i++) {
    867                         mActiveSessions.get(i).mCallbacks.onLauncherInfoChanged();
    868                     }
    869                 } break;
    870                 case MSG_LOAD_ENTRIES_COMPLETE: {
    871                     for (int i=0; i<mActiveSessions.size(); i++) {
    872                         mActiveSessions.get(i).mCallbacks.onLoadEntriesCompleted();
    873                     }
    874                 } break;
    875             }
    876         }
    877     }
    878 
    879     private class BackgroundHandler extends Handler {
    880         static final int MSG_REBUILD_LIST = 1;
    881         static final int MSG_LOAD_ENTRIES = 2;
    882         static final int MSG_LOAD_HOME_APP = 3;
    883         static final int MSG_LOAD_LAUNCHER = 4;
    884         static final int MSG_LOAD_LEANBACK_LAUNCHER = 5;
    885         static final int MSG_LOAD_ICONS = 6;
    886         static final int MSG_LOAD_SIZES = 7;
    887 
    888         boolean mRunning;
    889 
    890         BackgroundHandler(Looper looper) {
    891             super(looper);
    892         }
    893 
    894         @Override
    895         public void handleMessage(Message msg) {
    896             // Always try rebuilding list first thing, if needed.
    897             ArrayList<Session> rebuildingSessions = null;
    898             synchronized (mRebuildingSessions) {
    899                 if (mRebuildingSessions.size() > 0) {
    900                     rebuildingSessions = new ArrayList<Session>(mRebuildingSessions);
    901                     mRebuildingSessions.clear();
    902                 }
    903             }
    904             if (rebuildingSessions != null) {
    905                 for (int i=0; i<rebuildingSessions.size(); i++) {
    906                     rebuildingSessions.get(i).handleRebuildList();
    907                 }
    908             }
    909 
    910             int flags = getCombinedSessionFlags(mSessions);
    911 
    912             switch (msg.what) {
    913                 case MSG_REBUILD_LIST: {
    914                 } break;
    915                 case MSG_LOAD_ENTRIES: {
    916                     int numDone = 0;
    917                     synchronized (mEntriesMap) {
    918                         if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock");
    919                         for (int i = 0; i < mApplications.size() && numDone < 6; i++) {
    920                             if (!mRunning) {
    921                                 mRunning = true;
    922                                 Message m = mMainHandler.obtainMessage(
    923                                         MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
    924                                 mMainHandler.sendMessage(m);
    925                             }
    926                             ApplicationInfo info = mApplications.get(i);
    927                             int userId = UserHandle.getUserId(info.uid);
    928                             if (mEntriesMap.get(userId).get(info.packageName) == null) {
    929                                 numDone++;
    930                                 getEntryLocked(info);
    931                             }
    932                             if (userId != 0 && mEntriesMap.indexOfKey(0) >= 0) {
    933                                 // If this app is for a profile and we are on the owner, remove
    934                                 // the owner entry if it isn't installed.  This will prevent
    935                                 // duplicates of work only apps showing up as 'not installed
    936                                 // for this user'.
    937                                 // Note: This depends on us traversing the users in order, which
    938                                 // happens because of the way we generate the list in
    939                                 // doResumeIfNeededLocked.
    940                                 AppEntry entry = mEntriesMap.get(0).get(info.packageName);
    941                                 if (entry != null && !hasFlag(entry.info.flags,
    942                                         ApplicationInfo.FLAG_INSTALLED)) {
    943                                     mEntriesMap.get(0).remove(info.packageName);
    944                                     mAppEntries.remove(entry);
    945                                 }
    946                             }
    947                         }
    948                         if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock");
    949                     }
    950 
    951                     if (numDone >= 6) {
    952                         sendEmptyMessage(MSG_LOAD_ENTRIES);
    953                     } else {
    954                         if (!mMainHandler.hasMessages(MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) {
    955                             mMainHandler.sendEmptyMessage(MainHandler.MSG_LOAD_ENTRIES_COMPLETE);
    956                         }
    957                         sendEmptyMessage(MSG_LOAD_HOME_APP);
    958                     }
    959                 } break;
    960                 case MSG_LOAD_HOME_APP: {
    961                     if (hasFlag(flags, FLAG_SESSION_REQUEST_HOME_APP)) {
    962                         final List<ResolveInfo> homeActivities = new ArrayList<>();
    963                         mPm.getHomeActivities(homeActivities);
    964                         synchronized (mEntriesMap) {
    965                             final int entryCount = mEntriesMap.size();
    966                             for (int i = 0; i < entryCount; i++) {
    967                                 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP acquired lock");
    968                                 final HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(
    969                                         i);
    970                                 for (ResolveInfo activity : homeActivities) {
    971                                     String packageName = activity.activityInfo.packageName;
    972                                     AppEntry entry = userEntries.get(packageName);
    973                                     if (entry != null) {
    974                                         entry.isHomeApp = true;
    975                                     }
    976                                 }
    977                                 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP releasing lock");
    978                             }
    979                         }
    980                     }
    981                     sendEmptyMessage(MSG_LOAD_LAUNCHER);
    982                 } break;
    983                 case MSG_LOAD_LAUNCHER:
    984                 case MSG_LOAD_LEANBACK_LAUNCHER: {
    985                     if ((msg.what == MSG_LOAD_LAUNCHER &&
    986                             hasFlag(flags, FLAG_SESSION_REQUEST_LAUNCHER))
    987                             || (msg.what == MSG_LOAD_LEANBACK_LAUNCHER &&
    988                             hasFlag(flags, FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER))) {
    989 
    990                         Intent launchIntent = new Intent(Intent.ACTION_MAIN, null);
    991                         launchIntent.addCategory(msg.what == MSG_LOAD_LAUNCHER
    992                                 ? Intent.CATEGORY_LAUNCHER : Intent.CATEGORY_LEANBACK_LAUNCHER);
    993                         for (int i = 0; i < mEntriesMap.size(); i++) {
    994                             int userId = mEntriesMap.keyAt(i);
    995                             // If we do not specify MATCH_DIRECT_BOOT_AWARE or
    996                             // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags
    997                             // according to the user's lock state. When the user is locked,
    998                             // components
    999                             // with ComponentInfo#directBootAware == false will be filtered. We should
   1000                             // explicitly include both direct boot aware and unaware components here.
   1001                             List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser(
   1002                                     launchIntent,
   1003                                     PackageManager.MATCH_DISABLED_COMPONENTS
   1004                                             | PackageManager.MATCH_DIRECT_BOOT_AWARE
   1005                                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
   1006                                     userId
   1007                             );
   1008                             synchronized (mEntriesMap) {
   1009                                 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock");
   1010                                 HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
   1011                                 final int N = intents.size();
   1012                                 for (int j = 0; j < N; j++) {
   1013                                     ResolveInfo resolveInfo = intents.get(j);
   1014                                     String packageName = resolveInfo.activityInfo.packageName;
   1015                                     AppEntry entry = userEntries.get(packageName);
   1016                                     if (entry != null) {
   1017                                         entry.hasLauncherEntry = true;
   1018                                         entry.launcherEntryEnabled |=
   1019                                                 resolveInfo.activityInfo.enabled;
   1020                                     } else {
   1021                                         Log.w(TAG, "Cannot find pkg: " + packageName
   1022                                                 + " on user " + userId);
   1023                                     }
   1024                                 }
   1025                                 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock");
   1026                             }
   1027                         }
   1028 
   1029                         if (!mMainHandler.hasMessages(MainHandler.MSG_LAUNCHER_INFO_CHANGED)) {
   1030                             mMainHandler.sendEmptyMessage(MainHandler.MSG_LAUNCHER_INFO_CHANGED);
   1031                         }
   1032                     }
   1033                     if (msg.what == MSG_LOAD_LAUNCHER) {
   1034                         sendEmptyMessage(MSG_LOAD_LEANBACK_LAUNCHER);
   1035                     } else {
   1036                         sendEmptyMessage(MSG_LOAD_ICONS);
   1037                     }
   1038                 } break;
   1039                 case MSG_LOAD_ICONS: {
   1040                     if (hasFlag(flags, FLAG_SESSION_REQUEST_ICONS)) {
   1041                         int numDone = 0;
   1042                         synchronized (mEntriesMap) {
   1043                             if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock");
   1044                             for (int i = 0; i < mAppEntries.size() && numDone < 2; i++) {
   1045                                 AppEntry entry = mAppEntries.get(i);
   1046                                 if (entry.icon == null || !entry.mounted) {
   1047                                     synchronized (entry) {
   1048                                         if (entry.ensureIconLocked(mContext, mDrawableFactory)) {
   1049                                             if (!mRunning) {
   1050                                                 mRunning = true;
   1051                                                 Message m = mMainHandler.obtainMessage(
   1052                                                         MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
   1053                                                 mMainHandler.sendMessage(m);
   1054                                             }
   1055                                             numDone++;
   1056                                         }
   1057                                     }
   1058                                 }
   1059                             }
   1060                             if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock");
   1061                         }
   1062                         if (numDone > 0) {
   1063                             if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) {
   1064                                 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED);
   1065                             }
   1066                         }
   1067                         if (numDone >= 2) {
   1068                             sendEmptyMessage(MSG_LOAD_ICONS);
   1069                             break;
   1070                         }
   1071                     }
   1072                     sendEmptyMessage(MSG_LOAD_SIZES);
   1073                 } break;
   1074                 case MSG_LOAD_SIZES: {
   1075                     if (hasFlag(flags, FLAG_SESSION_REQUEST_SIZES)) {
   1076                         synchronized (mEntriesMap) {
   1077                             if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock");
   1078                             if (mCurComputingSizePkg != null) {
   1079                                 if (DEBUG_LOCKING) Log.v(TAG,
   1080                                         "MSG_LOAD_SIZES releasing: currently computing");
   1081                                 return;
   1082                             }
   1083 
   1084                             long now = SystemClock.uptimeMillis();
   1085                             for (int i = 0; i < mAppEntries.size(); i++) {
   1086                                 AppEntry entry = mAppEntries.get(i);
   1087                                 if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_INSTALLED)
   1088                                         && (entry.size == SIZE_UNKNOWN || entry.sizeStale)) {
   1089                                     if (entry.sizeLoadStart == 0 ||
   1090                                             (entry.sizeLoadStart < (now - 20 * 1000))) {
   1091                                         if (!mRunning) {
   1092                                             mRunning = true;
   1093                                             Message m = mMainHandler.obtainMessage(
   1094                                                     MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
   1095                                             mMainHandler.sendMessage(m);
   1096                                         }
   1097                                         entry.sizeLoadStart = now;
   1098                                         mCurComputingSizeUuid = entry.info.storageUuid;
   1099                                         mCurComputingSizePkg = entry.info.packageName;
   1100                                         mCurComputingSizeUserId = UserHandle.getUserId(
   1101                                                 entry.info.uid);
   1102 
   1103                                         mBackgroundHandler.post(() -> {
   1104                                             try {
   1105                                                 final StorageStats stats =
   1106                                                         mStats.queryStatsForPackage(
   1107                                                                 mCurComputingSizeUuid,
   1108                                                                 mCurComputingSizePkg,
   1109                                                                 UserHandle.of(
   1110                                                                         mCurComputingSizeUserId));
   1111                                                 final PackageStats legacy = new PackageStats(
   1112                                                         mCurComputingSizePkg,
   1113                                                         mCurComputingSizeUserId);
   1114                                                 legacy.codeSize = stats.getCodeBytes();
   1115                                                 legacy.dataSize = stats.getDataBytes();
   1116                                                 legacy.cacheSize = stats.getCacheBytes();
   1117                                                 try {
   1118                                                     mStatsObserver.onGetStatsCompleted(legacy,
   1119                                                             true);
   1120                                                 } catch (RemoteException ignored) {
   1121                                                 }
   1122                                             } catch (NameNotFoundException | IOException e) {
   1123                                                 Log.w(TAG, "Failed to query stats: " + e);
   1124                                                 try {
   1125                                                     mStatsObserver.onGetStatsCompleted(null, false);
   1126                                                 } catch (RemoteException ignored) {
   1127                                                 }
   1128                                             }
   1129 
   1130                                         });
   1131                                     }
   1132                                     if (DEBUG_LOCKING) Log.v(TAG,
   1133                                             "MSG_LOAD_SIZES releasing: now computing");
   1134                                     return;
   1135                                 }
   1136                             }
   1137                             if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) {
   1138                                 mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED);
   1139                                 mRunning = false;
   1140                                 Message m = mMainHandler.obtainMessage(
   1141                                         MainHandler.MSG_RUNNING_STATE_CHANGED, 0);
   1142                                 mMainHandler.sendMessage(m);
   1143                             }
   1144                             if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock");
   1145                         }
   1146                     }
   1147                 } break;
   1148             }
   1149         }
   1150 
   1151         private @SessionFlags int getCombinedSessionFlags(List<Session> sessions) {
   1152             synchronized (mEntriesMap) {
   1153                 int flags = 0;
   1154                 for (Session session : sessions) {
   1155                     flags |= session.mFlags;
   1156                 }
   1157                 return flags;
   1158             }
   1159         }
   1160 
   1161         final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
   1162             public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
   1163                 if (!succeeded) {
   1164                     // There is no meaningful information in stats if the call failed.
   1165                     return;
   1166                 }
   1167 
   1168                 boolean sizeChanged = false;
   1169                 synchronized (mEntriesMap) {
   1170                     if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock");
   1171                     HashMap<String, AppEntry> userMap = mEntriesMap.get(stats.userHandle);
   1172                     if (userMap == null) {
   1173                         // The user must have been removed.
   1174                         return;
   1175                     }
   1176                     AppEntry entry = userMap.get(stats.packageName);
   1177                     if (entry != null) {
   1178                         synchronized (entry) {
   1179                             entry.sizeStale = false;
   1180                             entry.sizeLoadStart = 0;
   1181                             long externalCodeSize = stats.externalCodeSize
   1182                                     + stats.externalObbSize;
   1183                             long externalDataSize = stats.externalDataSize
   1184                                     + stats.externalMediaSize;
   1185                             long newSize = externalCodeSize + externalDataSize
   1186                                     + getTotalInternalSize(stats);
   1187                             if (entry.size != newSize ||
   1188                                     entry.cacheSize != stats.cacheSize ||
   1189                                     entry.codeSize != stats.codeSize ||
   1190                                     entry.dataSize != stats.dataSize ||
   1191                                     entry.externalCodeSize != externalCodeSize ||
   1192                                     entry.externalDataSize != externalDataSize ||
   1193                                     entry.externalCacheSize != stats.externalCacheSize) {
   1194                                 entry.size = newSize;
   1195                                 entry.cacheSize = stats.cacheSize;
   1196                                 entry.codeSize = stats.codeSize;
   1197                                 entry.dataSize = stats.dataSize;
   1198                                 entry.externalCodeSize = externalCodeSize;
   1199                                 entry.externalDataSize = externalDataSize;
   1200                                 entry.externalCacheSize = stats.externalCacheSize;
   1201                                 entry.sizeStr = getSizeStr(entry.size);
   1202                                 entry.internalSize = getTotalInternalSize(stats);
   1203                                 entry.internalSizeStr = getSizeStr(entry.internalSize);
   1204                                 entry.externalSize = getTotalExternalSize(stats);
   1205                                 entry.externalSizeStr = getSizeStr(entry.externalSize);
   1206                                 if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry
   1207                                         + ": " + entry.sizeStr);
   1208                                 sizeChanged = true;
   1209                             }
   1210                         }
   1211                         if (sizeChanged) {
   1212                             Message msg = mMainHandler.obtainMessage(
   1213                                     MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName);
   1214                             mMainHandler.sendMessage(msg);
   1215                         }
   1216                     }
   1217                     if (mCurComputingSizePkg != null
   1218                             && (mCurComputingSizePkg.equals(stats.packageName)
   1219                             && mCurComputingSizeUserId == stats.userHandle)) {
   1220                         mCurComputingSizePkg = null;
   1221                         sendEmptyMessage(MSG_LOAD_SIZES);
   1222                     }
   1223                     if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock");
   1224                 }
   1225             }
   1226         };
   1227     }
   1228 
   1229     /**
   1230      * Receives notifications when applications are added/removed.
   1231      */
   1232     private class PackageIntentReceiver extends BroadcastReceiver {
   1233         void registerReceiver() {
   1234             IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
   1235             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
   1236             filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
   1237             filter.addDataScheme("package");
   1238             mContext.registerReceiver(this, filter);
   1239             // Register for events related to sdcard installation.
   1240             IntentFilter sdFilter = new IntentFilter();
   1241             sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
   1242             sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
   1243             mContext.registerReceiver(this, sdFilter);
   1244             // Register for events related to user creation/deletion.
   1245             IntentFilter userFilter = new IntentFilter();
   1246             userFilter.addAction(Intent.ACTION_USER_ADDED);
   1247             userFilter.addAction(Intent.ACTION_USER_REMOVED);
   1248             mContext.registerReceiver(this, userFilter);
   1249         }
   1250         void unregisterReceiver() {
   1251             mContext.unregisterReceiver(this);
   1252         }
   1253         @Override
   1254         public void onReceive(Context context, Intent intent) {
   1255             String actionStr = intent.getAction();
   1256             if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) {
   1257                 Uri data = intent.getData();
   1258                 String pkgName = data.getEncodedSchemeSpecificPart();
   1259                 for (int i = 0; i < mEntriesMap.size(); i++) {
   1260                     addPackage(pkgName, mEntriesMap.keyAt(i));
   1261                 }
   1262             } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) {
   1263                 Uri data = intent.getData();
   1264                 String pkgName = data.getEncodedSchemeSpecificPart();
   1265                 for (int i = 0; i < mEntriesMap.size(); i++) {
   1266                     removePackage(pkgName, mEntriesMap.keyAt(i));
   1267                 }
   1268             } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) {
   1269                 Uri data = intent.getData();
   1270                 String pkgName = data.getEncodedSchemeSpecificPart();
   1271                 for (int i = 0; i < mEntriesMap.size(); i++) {
   1272                     invalidatePackage(pkgName, mEntriesMap.keyAt(i));
   1273                 }
   1274             } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) ||
   1275                     Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) {
   1276                 // When applications become available or unavailable (perhaps because
   1277                 // the SD card was inserted or ejected) we need to refresh the
   1278                 // AppInfo with new label, icon and size information as appropriate
   1279                 // given the newfound (un)availability of the application.
   1280                 // A simple way to do that is to treat the refresh as a package
   1281                 // removal followed by a package addition.
   1282                 String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
   1283                 if (pkgList == null || pkgList.length == 0) {
   1284                     // Ignore
   1285                     return;
   1286                 }
   1287                 boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr);
   1288                 if (avail) {
   1289                     for (String pkgName : pkgList) {
   1290                         for (int i = 0; i < mEntriesMap.size(); i++) {
   1291                             invalidatePackage(pkgName, mEntriesMap.keyAt(i));
   1292                         }
   1293                     }
   1294                 }
   1295             } else if (Intent.ACTION_USER_ADDED.equals(actionStr)) {
   1296                 addUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
   1297             } else if (Intent.ACTION_USER_REMOVED.equals(actionStr)) {
   1298                 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
   1299             }
   1300         }
   1301     }
   1302 
   1303     public interface Callbacks {
   1304         void onRunningStateChanged(boolean running);
   1305         void onPackageListChanged();
   1306         void onRebuildComplete(ArrayList<AppEntry> apps);
   1307         void onPackageIconChanged();
   1308         void onPackageSizeChanged(String packageName);
   1309         void onAllSizesComputed();
   1310         void onLauncherInfoChanged();
   1311         void onLoadEntriesCompleted();
   1312     }
   1313 
   1314     public static class SizeInfo {
   1315         public long cacheSize;
   1316         public long codeSize;
   1317         public long dataSize;
   1318         public long externalCodeSize;
   1319         public long externalDataSize;
   1320 
   1321         // This is the part of externalDataSize that is in the cache
   1322         // section of external storage.  Note that we don't just combine
   1323         // this with cacheSize because currently the platform can't
   1324         // automatically trim this data when needed, so it is something
   1325         // the user may need to manage.  The externalDataSize also includes
   1326         // this value, since what this is here is really the part of
   1327         // externalDataSize that we can just consider to be "cache" files
   1328         // for purposes of cleaning them up in the app details UI.
   1329         public long externalCacheSize;
   1330     }
   1331 
   1332     public static class AppEntry extends SizeInfo {
   1333         public final File apkFile;
   1334         public final long id;
   1335         public String label;
   1336         public long size;
   1337         public long internalSize;
   1338         public long externalSize;
   1339 
   1340         public boolean mounted;
   1341 
   1342         /**
   1343          * Setting this to {@code true} prevents the entry to be filtered by
   1344          * {@link #FILTER_DOWNLOADED_AND_LAUNCHER}.
   1345          */
   1346         public boolean hasLauncherEntry;
   1347 
   1348         /**
   1349          * Whether the component that has a launcher intent filter is enabled.
   1350          */
   1351         public boolean launcherEntryEnabled;
   1352 
   1353         /**
   1354          * Whether or not it's a Home app.
   1355          */
   1356         public boolean isHomeApp;
   1357 
   1358         public String getNormalizedLabel() {
   1359             if (normalizedLabel != null) {
   1360                 return normalizedLabel;
   1361             }
   1362             normalizedLabel = normalize(label);
   1363             return normalizedLabel;
   1364         }
   1365 
   1366         // Need to synchronize on 'this' for the following.
   1367         public ApplicationInfo info;
   1368         public Drawable icon;
   1369         public String sizeStr;
   1370         public String internalSizeStr;
   1371         public String externalSizeStr;
   1372         public boolean sizeStale;
   1373         public long sizeLoadStart;
   1374 
   1375         public String normalizedLabel;
   1376 
   1377         // A location where extra info can be placed to be used by custom filters.
   1378         public Object extraInfo;
   1379 
   1380         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
   1381         public AppEntry(Context context, ApplicationInfo info, long id) {
   1382             apkFile = new File(info.sourceDir);
   1383             this.id = id;
   1384             this.info = info;
   1385             this.size = SIZE_UNKNOWN;
   1386             this.sizeStale = true;
   1387             ensureLabel(context);
   1388         }
   1389 
   1390         public void ensureLabel(Context context) {
   1391             if (this.label == null || !this.mounted) {
   1392                 if (!this.apkFile.exists()) {
   1393                     this.mounted = false;
   1394                     this.label = info.packageName;
   1395                 } else {
   1396                     this.mounted = true;
   1397                     CharSequence label = info.loadLabel(context.getPackageManager());
   1398                     this.label = label != null ? label.toString() : info.packageName;
   1399                 }
   1400             }
   1401         }
   1402 
   1403         boolean ensureIconLocked(Context context, IconDrawableFactory drawableFactory) {
   1404             if (this.icon == null) {
   1405                 if (this.apkFile.exists()) {
   1406                     this.icon = drawableFactory.getBadgedIcon(info);
   1407                     return true;
   1408                 } else {
   1409                     this.mounted = false;
   1410                     this.icon = context.getDrawable(R.drawable.sym_app_on_sd_unavailable_icon);
   1411                 }
   1412             } else if (!this.mounted) {
   1413                 // If the app wasn't mounted but is now mounted, reload
   1414                 // its icon.
   1415                 if (this.apkFile.exists()) {
   1416                     this.mounted = true;
   1417                     this.icon = drawableFactory.getBadgedIcon(info);
   1418                     return true;
   1419                 }
   1420             }
   1421             return false;
   1422         }
   1423 
   1424         public String getVersion(Context context) {
   1425             try {
   1426                 return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName;
   1427             } catch (PackageManager.NameNotFoundException e) {
   1428                 return "";
   1429             }
   1430         }
   1431     }
   1432 
   1433     private static boolean hasFlag(int flags, int flag) {
   1434         return (flags & flag) != 0;
   1435     }
   1436 
   1437     /**
   1438      * Compare by label, then package name, then uid.
   1439      */
   1440     public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
   1441         private final Collator sCollator = Collator.getInstance();
   1442         @Override
   1443         public int compare(AppEntry object1, AppEntry object2) {
   1444             int compareResult = sCollator.compare(object1.label, object2.label);
   1445             if (compareResult != 0) {
   1446                 return compareResult;
   1447             }
   1448             if (object1.info != null && object2.info != null) {
   1449                 compareResult =
   1450                     sCollator.compare(object1.info.packageName, object2.info.packageName);
   1451                 if (compareResult != 0) {
   1452                     return compareResult;
   1453                 }
   1454             }
   1455             return object1.info.uid - object2.info.uid;
   1456         }
   1457     };
   1458 
   1459     public static final Comparator<AppEntry> SIZE_COMPARATOR
   1460             = new Comparator<AppEntry>() {
   1461         @Override
   1462         public int compare(AppEntry object1, AppEntry object2) {
   1463             if (object1.size < object2.size) return 1;
   1464             if (object1.size > object2.size) return -1;
   1465             return ALPHA_COMPARATOR.compare(object1, object2);
   1466         }
   1467     };
   1468 
   1469     public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR
   1470             = new Comparator<AppEntry>() {
   1471         @Override
   1472         public int compare(AppEntry object1, AppEntry object2) {
   1473             if (object1.internalSize < object2.internalSize) return 1;
   1474             if (object1.internalSize > object2.internalSize) return -1;
   1475             return ALPHA_COMPARATOR.compare(object1, object2);
   1476         }
   1477     };
   1478 
   1479     public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR
   1480             = new Comparator<AppEntry>() {
   1481         @Override
   1482         public int compare(AppEntry object1, AppEntry object2) {
   1483             if (object1.externalSize < object2.externalSize) return 1;
   1484             if (object1.externalSize > object2.externalSize) return -1;
   1485             return ALPHA_COMPARATOR.compare(object1, object2);
   1486         }
   1487     };
   1488 
   1489     public interface AppFilter {
   1490         void init();
   1491         default void init(Context context) {
   1492             init();
   1493         }
   1494         boolean filterApp(AppEntry info);
   1495     }
   1496 
   1497     public static final AppFilter FILTER_PERSONAL = new AppFilter() {
   1498         private int mCurrentUser;
   1499 
   1500         @Override
   1501         public void init() {
   1502             mCurrentUser = ActivityManager.getCurrentUser();
   1503         }
   1504 
   1505         @Override
   1506         public boolean filterApp(AppEntry entry) {
   1507             return UserHandle.getUserId(entry.info.uid) == mCurrentUser;
   1508         }
   1509     };
   1510 
   1511     public static final AppFilter FILTER_WITHOUT_DISABLED_UNTIL_USED = new AppFilter() {
   1512         @Override
   1513         public void init() {
   1514             // do nothing
   1515         }
   1516 
   1517         @Override
   1518         public boolean filterApp(AppEntry entry) {
   1519             return entry.info.enabledSetting
   1520                     != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
   1521         }
   1522     };
   1523 
   1524     public static final AppFilter FILTER_WORK = new AppFilter() {
   1525         private int mCurrentUser;
   1526 
   1527         @Override
   1528         public void init() {
   1529             mCurrentUser = ActivityManager.getCurrentUser();
   1530         }
   1531 
   1532         @Override
   1533         public boolean filterApp(AppEntry entry) {
   1534             return UserHandle.getUserId(entry.info.uid) != mCurrentUser;
   1535         }
   1536     };
   1537 
   1538     /**
   1539      * Displays a combined list with "downloaded" and "visible in launcher" apps only.
   1540      */
   1541     public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER = new AppFilter() {
   1542         @Override
   1543         public void init() {
   1544         }
   1545 
   1546         @Override
   1547         public boolean filterApp(AppEntry entry) {
   1548             if (AppUtils.isInstant(entry.info)) {
   1549                 return false;
   1550             } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) {
   1551                 return true;
   1552             } else if (!hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM)) {
   1553                 return true;
   1554             } else if (entry.hasLauncherEntry) {
   1555                 return true;
   1556             } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM) && entry.isHomeApp) {
   1557                 return true;
   1558             }
   1559             return false;
   1560         }
   1561     };
   1562 
   1563     /**
   1564      * Displays a combined list with "downloaded" and "visible in launcher" apps only.
   1565      */
   1566     public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT = new AppFilter() {
   1567 
   1568         @Override
   1569         public void init() {
   1570         }
   1571 
   1572         @Override
   1573         public boolean filterApp(AppEntry entry) {
   1574             return AppUtils.isInstant(entry.info)
   1575                     || FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(entry);
   1576         }
   1577 
   1578     };
   1579 
   1580     public static final AppFilter FILTER_THIRD_PARTY = new AppFilter() {
   1581         @Override
   1582         public void init() {
   1583         }
   1584 
   1585         @Override
   1586         public boolean filterApp(AppEntry entry) {
   1587             if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) {
   1588                 return true;
   1589             } else if (!hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM)) {
   1590                 return true;
   1591             }
   1592             return false;
   1593         }
   1594     };
   1595 
   1596     public static final AppFilter FILTER_DISABLED = new AppFilter() {
   1597         @Override
   1598         public void init() {
   1599         }
   1600 
   1601         @Override
   1602         public boolean filterApp(AppEntry entry) {
   1603             return !entry.info.enabled && !AppUtils.isInstant(entry.info);
   1604         }
   1605     };
   1606 
   1607     public static final AppFilter FILTER_INSTANT = new AppFilter() {
   1608         @Override
   1609         public void init() {
   1610         }
   1611 
   1612         @Override
   1613         public boolean filterApp(AppEntry entry) {
   1614             return AppUtils.isInstant(entry.info);
   1615         }
   1616     };
   1617 
   1618     public static final AppFilter FILTER_ALL_ENABLED = new AppFilter() {
   1619         @Override
   1620         public void init() {
   1621         }
   1622 
   1623         @Override
   1624         public boolean filterApp(AppEntry entry) {
   1625             return entry.info.enabled && !AppUtils.isInstant(entry.info);
   1626         }
   1627     };
   1628 
   1629     public static final AppFilter FILTER_EVERYTHING = new AppFilter() {
   1630         @Override
   1631         public void init() {
   1632         }
   1633 
   1634         @Override
   1635         public boolean filterApp(AppEntry entry) {
   1636             return true;
   1637         }
   1638     };
   1639 
   1640     public static final AppFilter FILTER_WITH_DOMAIN_URLS = new AppFilter() {
   1641         @Override
   1642         public void init() {
   1643         }
   1644 
   1645         @Override
   1646         public boolean filterApp(AppEntry entry) {
   1647             return !AppUtils.isInstant(entry.info)
   1648                 && hasFlag(entry.info.privateFlags, ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS);
   1649         }
   1650     };
   1651 
   1652     public static final AppFilter FILTER_NOT_HIDE = new AppFilter() {
   1653         private String[] mHidePackageNames;
   1654 
   1655         @Override
   1656         public void init(Context context) {
   1657             mHidePackageNames = context.getResources()
   1658                 .getStringArray(R.array.config_hideWhenDisabled_packageNames);
   1659         }
   1660 
   1661         @Override
   1662         public void init() {
   1663         }
   1664 
   1665         @Override
   1666         public boolean filterApp(AppEntry entry) {
   1667             if (ArrayUtils.contains(mHidePackageNames, entry.info.packageName)) {
   1668                 if (!entry.info.enabled) {
   1669                     return false;
   1670                 } else if (entry.info.enabledSetting ==
   1671                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
   1672                     return false;
   1673                 }
   1674             }
   1675 
   1676             return true;
   1677         }
   1678     };
   1679 
   1680     public static final AppFilter FILTER_GAMES = new AppFilter() {
   1681         @Override
   1682         public void init() {
   1683         }
   1684 
   1685         @Override
   1686         public boolean filterApp(ApplicationsState.AppEntry info) {
   1687             // TODO: Update for the new game category.
   1688             boolean isGame;
   1689             synchronized (info.info) {
   1690                 isGame = hasFlag(info.info.flags, ApplicationInfo.FLAG_IS_GAME)
   1691                         || info.info.category == ApplicationInfo.CATEGORY_GAME;
   1692             }
   1693             return isGame;
   1694         }
   1695     };
   1696 
   1697     public static class VolumeFilter implements AppFilter {
   1698         private final String mVolumeUuid;
   1699 
   1700         public VolumeFilter(String volumeUuid) {
   1701             mVolumeUuid = volumeUuid;
   1702         }
   1703 
   1704         @Override
   1705         public void init() {
   1706         }
   1707 
   1708         @Override
   1709         public boolean filterApp(AppEntry info) {
   1710             return Objects.equals(info.info.volumeUuid, mVolumeUuid);
   1711         }
   1712     }
   1713 
   1714     public static class CompoundFilter implements AppFilter {
   1715         private final AppFilter mFirstFilter;
   1716         private final AppFilter mSecondFilter;
   1717 
   1718         public CompoundFilter(AppFilter first, AppFilter second) {
   1719             mFirstFilter = first;
   1720             mSecondFilter = second;
   1721         }
   1722 
   1723         @Override
   1724         public void init(Context context) {
   1725             mFirstFilter.init(context);
   1726             mSecondFilter.init(context);
   1727         }
   1728 
   1729         @Override
   1730         public void init() {
   1731             mFirstFilter.init();
   1732             mSecondFilter.init();
   1733         }
   1734 
   1735         @Override
   1736         public boolean filterApp(AppEntry info) {
   1737             return mFirstFilter.filterApp(info) && mSecondFilter.filterApp(info);
   1738         }
   1739     }
   1740 
   1741     public static final AppFilter FILTER_AUDIO = new AppFilter() {
   1742         @Override
   1743         public void init() {
   1744         }
   1745 
   1746         @Override
   1747         public boolean filterApp(AppEntry entry) {
   1748             boolean isMusicApp;
   1749             synchronized(entry) {
   1750                 isMusicApp = entry.info.category == ApplicationInfo.CATEGORY_AUDIO;
   1751             }
   1752             return isMusicApp;
   1753         }
   1754     };
   1755 
   1756     public static final AppFilter FILTER_MOVIES = new AppFilter() {
   1757         @Override
   1758         public void init() {
   1759         }
   1760 
   1761         @Override
   1762         public boolean filterApp(AppEntry entry) {
   1763             boolean isMovieApp;
   1764             synchronized(entry) {
   1765                 isMovieApp = entry.info.category == ApplicationInfo.CATEGORY_VIDEO;
   1766             }
   1767             return isMovieApp;
   1768         }
   1769     };
   1770 
   1771     public static final AppFilter FILTER_PHOTOS =
   1772             new AppFilter() {
   1773                 @Override
   1774                 public void init() {}
   1775 
   1776                 @Override
   1777                 public boolean filterApp(AppEntry entry) {
   1778                     boolean isPhotosApp;
   1779                     synchronized (entry) {
   1780                         isPhotosApp = entry.info.category == ApplicationInfo.CATEGORY_IMAGE;
   1781                     }
   1782                     return isPhotosApp;
   1783                 }
   1784             };
   1785 
   1786     public static final AppFilter FILTER_OTHER_APPS =
   1787             new AppFilter() {
   1788                 @Override
   1789                 public void init() {}
   1790 
   1791                 @Override
   1792                 public boolean filterApp(AppEntry entry) {
   1793                     boolean isCategorized;
   1794                     synchronized (entry) {
   1795                         isCategorized =
   1796                                 FILTER_AUDIO.filterApp(entry)
   1797                                         || FILTER_GAMES.filterApp(entry)
   1798                                         || FILTER_MOVIES.filterApp(entry)
   1799                                         || FILTER_PHOTOS.filterApp(entry);
   1800                     }
   1801                     return !isCategorized;
   1802                 }
   1803             };
   1804 }
   1805