• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getInstance(Application app)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 
ApplicationsState(Application app)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 
getBackgroundLooper()217     public Looper getBackgroundLooper() {
218         return mThread.getLooper();
219     }
220 
newSession(Callbacks callbacks)221     public Session newSession(Callbacks callbacks) {
222         return newSession(callbacks, null);
223     }
224 
newSession(Callbacks callbacks, Lifecycle lifecycle)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 
doResumeIfNeededLocked()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
clearEntries()304     void clearEntries() {
305         for (int i = 0; i < mEntriesMap.size(); i++) {
306             mEntriesMap.valueAt(i).clear();
307         }
308         mAppEntries.clear();
309     }
310 
haveDisabledApps()311     public boolean haveDisabledApps() {
312         return mHaveDisabledApps;
313     }
haveInstantApps()314     public boolean haveInstantApps() {
315         return mHaveInstantApps;
316     }
317 
doPauseIfNeededLocked()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 
doPauseLocked()330     void doPauseLocked() {
331         mResumed = false;
332         if (mPackageIntentReceiver != null) {
333             mPackageIntentReceiver.unregisterReceiver();
334             mPackageIntentReceiver = null;
335         }
336     }
337 
getEntry(String packageName, int userId)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 
getAppInfoLocked(String pkg, int userId)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 
ensureIcon(AppEntry entry)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 
requestSize(String packageName, int userId)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 
sumCacheSizes()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 
indexOfApplicationInfoLocked(String pkgName, int userId)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 
addPackage(String pkgName, int userId)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 
removePackage(String pkgName, int userId)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 
invalidatePackage(String pkgName, int userId)530     public void invalidatePackage(String pkgName, int userId) {
531         removePackage(pkgName, userId);
532         addPackage(pkgName, userId);
533     }
534 
addUser(int userId)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 
removeUser(int userId)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 
getEntryLocked(ApplicationInfo info)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 
getTotalInternalSize(PackageStats ps)587     private long getTotalInternalSize(PackageStats ps) {
588         if (ps != null) {
589             return ps.codeSize + ps.dataSize;
590         }
591         return SIZE_INVALID;
592     }
593 
getTotalExternalSize(PackageStats ps)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 
getSizeStr(long size)605     private String getSizeStr(long size) {
606         if (size >= 0) {
607             return Formatter.formatFileSize(mContext, size);
608         }
609         return null;
610     }
611 
rebuildActiveSessions()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 
normalize(String str)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 
Session(Callbacks callbacks, Lifecycle lifecycle)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 
getSessionFlags()661         public @SessionFlags int getSessionFlags() {
662             return mFlags;
663         }
664 
setSessionFlags(@essionFlags int flags)665         public void setSessionFlags(@SessionFlags int flags) {
666             mFlags = flags;
667         }
668 
669         @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
onResume()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)
onPause()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 
getAllApps()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.
rebuild(AppFilter filter, Comparator<AppEntry> comparator)703         public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) {
704             return rebuild(filter, comparator, true);
705         }
706 
rebuild(AppFilter filter, Comparator<AppEntry> comparator, boolean foreground)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 
handleRebuildList()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)
onDestroy()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 
MainHandler(Looper looper)824         public MainHandler(Looper looper) {
825             super(looper);
826         }
827 
828         @Override
handleMessage(Message msg)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 
BackgroundHandler(Looper looper)890         BackgroundHandler(Looper looper) {
891             super(looper);
892         }
893 
894         @Override
handleMessage(Message msg)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 
getCombinedSessionFlags(List<Session> sessions)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 {
registerReceiver()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         }
unregisterReceiver()1250         void unregisterReceiver() {
1251             mContext.unregisterReceiver(this);
1252         }
1253         @Override
onReceive(Context context, Intent intent)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 {
onRunningStateChanged(boolean running)1304         void onRunningStateChanged(boolean running);
onPackageListChanged()1305         void onPackageListChanged();
onRebuildComplete(ArrayList<AppEntry> apps)1306         void onRebuildComplete(ArrayList<AppEntry> apps);
onPackageIconChanged()1307         void onPackageIconChanged();
onPackageSizeChanged(String packageName)1308         void onPackageSizeChanged(String packageName);
onAllSizesComputed()1309         void onAllSizesComputed();
onLauncherInfoChanged()1310         void onLauncherInfoChanged();
onLoadEntriesCompleted()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 
getNormalizedLabel()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)
AppEntry(Context context, ApplicationInfo info, long id)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 
ensureLabel(Context context)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 
ensureIconLocked(Context context, IconDrawableFactory drawableFactory)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 
getVersion(Context context)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 
hasFlag(int flags, int flag)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 {
init()1490         void init();
init(Context context)1491         default void init(Context context) {
1492             init();
1493         }
filterApp(AppEntry info)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 
VolumeFilter(String volumeUuid)1700         public VolumeFilter(String volumeUuid) {
1701             mVolumeUuid = volumeUuid;
1702         }
1703 
1704         @Override
init()1705         public void init() {
1706         }
1707 
1708         @Override
filterApp(AppEntry info)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 
CompoundFilter(AppFilter first, AppFilter second)1718         public CompoundFilter(AppFilter first, AppFilter second) {
1719             mFirstFilter = first;
1720             mSecondFilter = second;
1721         }
1722 
1723         @Override
init(Context context)1724         public void init(Context context) {
1725             mFirstFilter.init(context);
1726             mSecondFilter.init(context);
1727         }
1728 
1729         @Override
init()1730         public void init() {
1731             mFirstFilter.init();
1732             mSecondFilter.init();
1733         }
1734 
1735         @Override
filterApp(AppEntry info)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