2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settingslib.applications;
19 import android.app.ActivityManager;
20 import android.app.AppGlobals;
21 import android.app.Application;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.IPackageManager;
28 import android.content.pm.IPackageStatsObserver;
29 import android.content.pm.PackageManager;
30 import android.content.pm.PackageStats;
31 import android.content.pm.ParceledListSlice;
32 import android.content.pm.ResolveInfo;
33 import android.content.pm.UserInfo;
34 import android.graphics.drawable.Drawable;
35 import android.net.Uri;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.Looper;
39 import android.os.Message;
40 import android.os.Process;
41 import android.os.RemoteException;
42 import android.os.SystemClock;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.text.format.Formatter;
46 import android.util.Log;
47 import android.util.SparseArray;
49 import com.android.internal.util.ArrayUtils;
50 import com.android.settingslib.R;
53 import java.text.Collator;
54 import java.text.Normalizer;
55 import java.text.Normalizer.Form;
56 import java.util.ArrayList;
57 import java.util.Collections;
58 import java.util.Comparator;
59 import java.util.HashMap;
60 import java.util.List;
61 import java.util.Objects;
62 import java.util.regex.Pattern;
65 * Keeps track of information about all installed applications, lazy-loading
68 public class ApplicationsState {
69 static final String TAG = "ApplicationsState";
70 static final boolean DEBUG = false;
71 static final boolean DEBUG_LOCKING = false;
73 public static final int SIZE_UNKNOWN = -1;
74 public static final int SIZE_INVALID = -2;
76 static final Pattern REMOVE_DIACRITICALS_PATTERN
77 = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
79 static final Object sLock = new Object();
80 static ApplicationsState sInstance;
82 public static ApplicationsState getInstance(Application app) {
83 synchronized (sLock) {
84 if (sInstance == null) {
85 sInstance = new ApplicationsState(app);
91 final Context mContext;
92 final PackageManager mPm;
93 final IPackageManager mIpm;
94 final UserManager mUm;
95 final int mAdminRetrieveFlags;
96 final int mRetrieveFlags;
97 PackageIntentReceiver mPackageIntentReceiver;
100 boolean mHaveDisabledApps;
102 // Information about all applications. Synchronize on mEntriesMap
103 // to protect access to these.
104 final ArrayList<Session> mSessions = new ArrayList<Session>();
105 final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>();
106 final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges();
107 // Map: userid => (Map: package name => AppEntry)
108 final SparseArray<HashMap<String, AppEntry>> mEntriesMap =
109 new SparseArray<HashMap<String, AppEntry>>();
110 final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>();
111 List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>();
113 String mCurComputingSizePkg;
114 int mCurComputingSizeUserId;
115 boolean mSessionsChanged;
117 // Temporary for dispatching session callbacks. Only touched by main thread.
118 final ArrayList<Session> mActiveSessions = new ArrayList<Session>();
120 final HandlerThread mThread;
121 final BackgroundHandler mBackgroundHandler;
122 final MainHandler mMainHandler = new MainHandler(Looper.getMainLooper());
124 private ApplicationsState(Application app) {
126 mPm = mContext.getPackageManager();
127 mIpm = AppGlobals.getPackageManager();
128 mUm = (UserManager) app.getSystemService(Context.USER_SERVICE);
129 for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) {
130 mEntriesMap.put(userId, new HashMap<String, AppEntry>());
132 mThread = new HandlerThread("ApplicationsState.Loader",
133 Process.THREAD_PRIORITY_BACKGROUND);
135 mBackgroundHandler = new BackgroundHandler(mThread.getLooper());
137 // Only the owner can see all apps.
138 mAdminRetrieveFlags = PackageManager.GET_UNINSTALLED_PACKAGES |
139 PackageManager.GET_DISABLED_COMPONENTS |
140 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS;
141 mRetrieveFlags = PackageManager.GET_DISABLED_COMPONENTS |
142 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS;
145 * This is a trick to prevent the foreground thread from being delayed.
146 * The problem is that Dalvik monitors are initially spin locks, to keep
147 * them lightweight. This leads to unfair contention -- Even though the
148 * background thread only holds the lock for a short amount of time, if
149 * it keeps running and locking again it can prevent the main thread from
150 * acquiring its lock for a long time... sometimes even > 5 seconds
151 * (leading to an ANR).
153 * Dalvik will promote a monitor to a "real" lock if it detects enough
154 * contention on it. It doesn't figure this out fast enough for us
155 * here, though, so this little trick will force it to turn into a real
158 synchronized (mEntriesMap) {
161 } catch (InterruptedException e) {
166 public Looper getBackgroundLooper() {
167 return mThread.getLooper();
170 public Session newSession(Callbacks callbacks) {
171 Session s = new Session(callbacks);
172 synchronized (mEntriesMap) {
178 void doResumeIfNeededLocked() {
183 if (mPackageIntentReceiver == null) {
184 mPackageIntentReceiver = new PackageIntentReceiver();
185 mPackageIntentReceiver.registerReceiver();
187 mApplications = new ArrayList<ApplicationInfo>();
188 for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) {
190 // If this user is new, it needs a map created.
191 if (mEntriesMap.indexOfKey(user.id) < 0) {
192 mEntriesMap.put(user.id, new HashMap<String, AppEntry>());
194 @SuppressWarnings("unchecked")
195 ParceledListSlice<ApplicationInfo> list =
196 mIpm.getInstalledApplications(
197 user.isAdmin() ? mAdminRetrieveFlags : mRetrieveFlags,
199 mApplications.addAll(list.getList());
200 } catch (RemoteException e) {
204 if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
205 // If an interesting part of the configuration has changed, we
206 // should completely reload the app entries.
209 for (int i=0; i<mAppEntries.size(); i++) {
210 mAppEntries.get(i).sizeStale = true;
214 mHaveDisabledApps = false;
215 for (int i=0; i<mApplications.size(); i++) {
216 final ApplicationInfo info = mApplications.get(i);
217 // Need to trim out any applications that are disabled by
218 // something different than the user.
220 if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
221 mApplications.remove(i);
225 mHaveDisabledApps = true;
227 int userId = UserHandle.getUserId(info.uid);
228 final AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
233 if (mAppEntries.size() > mApplications.size()) {
234 // There are less apps now, some must have been uninstalled.
237 mCurComputingSizePkg = null;
238 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
239 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
243 private void clearEntries() {
244 for (int i = 0; i < mEntriesMap.size(); i++) {
245 mEntriesMap.valueAt(i).clear();
250 public boolean haveDisabledApps() {
251 return mHaveDisabledApps;
254 void doPauseIfNeededLocked() {
258 for (int i=0; i<mSessions.size(); i++) {
259 if (mSessions.get(i).mResumed) {
266 void doPauseLocked() {
268 if (mPackageIntentReceiver != null) {
269 mPackageIntentReceiver.unregisterReceiver();
270 mPackageIntentReceiver = null;
274 public AppEntry getEntry(String packageName, int userId) {
275 if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
276 synchronized (mEntriesMap) {
277 AppEntry entry = mEntriesMap.get(userId).get(packageName);
279 ApplicationInfo info = getAppInfoLocked(packageName, userId);
282 info = mIpm.getApplicationInfo(packageName, 0, userId);
283 } catch (RemoteException e) {
284 Log.w(TAG, "getEntry couldn't reach PackageManager", e);
289 entry = getEntryLocked(info);
292 if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock");
297 private ApplicationInfo getAppInfoLocked(String pkg, int userId) {
298 for (int i = 0; i < mApplications.size(); i++) {
299 ApplicationInfo info = mApplications.get(i);
300 if (pkg.equals(info.packageName)
301 && userId == UserHandle.getUserId(info.uid)) {
308 public void ensureIcon(AppEntry entry) {
309 if (entry.icon != null) {
312 synchronized (entry) {
313 entry.ensureIconLocked(mContext, mPm);
317 public void requestSize(String packageName, int userId) {
318 if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock...");
319 synchronized (mEntriesMap) {
320 AppEntry entry = mEntriesMap.get(userId).get(packageName);
322 mPm.getPackageSizeInfoAsUser(packageName, userId, mBackgroundHandler.mStatsObserver);
324 if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock");
328 long sumCacheSizes() {
330 if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock...");
331 synchronized (mEntriesMap) {
332 if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock");
333 for (int i=mAppEntries.size()-1; i>=0; i--) {
334 sum += mAppEntries.get(i).cacheSize;
336 if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock");
341 int indexOfApplicationInfoLocked(String pkgName, int userId) {
342 for (int i=mApplications.size()-1; i>=0; i--) {
343 ApplicationInfo appInfo = mApplications.get(i);
344 if (appInfo.packageName.equals(pkgName)
345 && UserHandle.getUserId(appInfo.uid) == userId) {
352 void addPackage(String pkgName, int userId) {
354 synchronized (mEntriesMap) {
355 if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock");
356 if (DEBUG) Log.i(TAG, "Adding package " + pkgName);
358 // If we are not resumed, we will do a full query the
359 // next time we resume, so there is no reason to do work
361 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed");
364 if (indexOfApplicationInfoLocked(pkgName, userId) >= 0) {
365 if (DEBUG) Log.i(TAG, "Package already exists!");
366 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists");
369 ApplicationInfo info = mIpm.getApplicationInfo(pkgName,
370 mUm.isUserAdmin(userId) ? mAdminRetrieveFlags : mRetrieveFlags,
376 if (info.enabledSetting
377 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
380 mHaveDisabledApps = true;
382 mApplications.add(info);
383 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
384 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
386 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
387 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
389 if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock");
391 } catch (RemoteException e) {
395 public void removePackage(String pkgName, int userId) {
396 synchronized (mEntriesMap) {
397 if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock");
398 int idx = indexOfApplicationInfoLocked(pkgName, userId);
399 if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx);
401 AppEntry entry = mEntriesMap.get(userId).get(pkgName);
402 if (DEBUG) Log.i(TAG, "removePackage: " + entry);
404 mEntriesMap.get(userId).remove(pkgName);
405 mAppEntries.remove(entry);
407 ApplicationInfo info = mApplications.get(idx);
408 mApplications.remove(idx);
410 mHaveDisabledApps = false;
411 for (int i=0; i<mApplications.size(); i++) {
412 if (!mApplications.get(i).enabled) {
413 mHaveDisabledApps = true;
418 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
419 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
422 if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock");
426 public void invalidatePackage(String pkgName, int userId) {
427 removePackage(pkgName, userId);
428 addPackage(pkgName, userId);
431 private void addUser(int userId) {
432 final int profileIds[] = mUm.getProfileIdsWithDisabled(UserHandle.myUserId());
433 if (ArrayUtils.contains(profileIds, userId)) {
434 synchronized (mEntriesMap) {
435 mEntriesMap.put(userId, new HashMap<String, AppEntry>());
437 // If resumed, Manually pause, then cause a resume to repopulate the app list.
438 // This is the simplest way to reload the packages so that the new user
439 // is included. Otherwise the list will be repopulated on next resume.
441 doResumeIfNeededLocked();
443 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
444 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
450 private void removeUser(int userId) {
451 synchronized (mEntriesMap) {
452 HashMap<String, AppEntry> userMap = mEntriesMap.get(userId);
453 if (userMap != null) {
454 for (AppEntry appEntry : userMap.values()) {
455 mAppEntries.remove(appEntry);
456 mApplications.remove(appEntry.info);
458 mEntriesMap.remove(userId);
459 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
460 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
466 private AppEntry getEntryLocked(ApplicationInfo info) {
467 int userId = UserHandle.getUserId(info.uid);
468 AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
469 if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
471 if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName);
472 entry = new AppEntry(mContext, info, mCurId++);
473 mEntriesMap.get(userId).put(info.packageName, entry);
474 mAppEntries.add(entry);
475 } else if (entry.info != info) {
481 // --------------------------------------------------------------
483 private long getTotalInternalSize(PackageStats ps) {
485 return ps.codeSize + ps.dataSize;
490 private long getTotalExternalSize(PackageStats ps) {
492 // We also include the cache size here because for non-emulated
493 // we don't automtically clean cache files.
494 return ps.externalCodeSize + ps.externalDataSize
495 + ps.externalCacheSize
496 + ps.externalMediaSize + ps.externalObbSize;
501 private String getSizeStr(long size) {
503 return Formatter.formatFileSize(mContext, size);
508 void rebuildActiveSessions() {
509 synchronized (mEntriesMap) {
510 if (!mSessionsChanged) {
513 mActiveSessions.clear();
514 for (int i=0; i<mSessions.size(); i++) {
515 Session s = mSessions.get(i);
517 mActiveSessions.add(s);
523 public static String normalize(String str) {
524 String tmp = Normalizer.normalize(str, Form.NFD);
525 return REMOVE_DIACRITICALS_PATTERN.matcher(tmp)
526 .replaceAll("").toLowerCase();
529 public class Session {
530 final Callbacks mCallbacks;
533 // Rebuilding of app list. Synchronized on mRebuildSync.
534 final Object mRebuildSync = new Object();
535 boolean mRebuildRequested;
536 boolean mRebuildAsync;
537 AppFilter mRebuildFilter;
538 Comparator<AppEntry> mRebuildComparator;
539 ArrayList<AppEntry> mRebuildResult;
540 ArrayList<AppEntry> mLastAppList;
541 boolean mRebuildForeground;
543 Session(Callbacks callbacks) {
544 mCallbacks = callbacks;
547 public void resume() {
548 if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
549 synchronized (mEntriesMap) {
552 mSessionsChanged = true;
553 doResumeIfNeededLocked();
556 if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
559 public void pause() {
560 if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
561 synchronized (mEntriesMap) {
564 mSessionsChanged = true;
565 mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this);
566 doPauseIfNeededLocked();
568 if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock");
572 public ArrayList<AppEntry> getAllApps() {
573 synchronized (mEntriesMap) {
574 return new ArrayList<>(mAppEntries);
578 // Creates a new list of app entries with the given filter and comparator.
579 public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) {
580 return rebuild(filter, comparator, true);
583 public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator,
584 boolean foreground) {
585 synchronized (mRebuildSync) {
586 synchronized (mRebuildingSessions) {
587 mRebuildingSessions.add(this);
588 mRebuildRequested = true;
589 mRebuildAsync = true;
590 mRebuildFilter = filter;
591 mRebuildComparator = comparator;
592 mRebuildForeground = foreground;
593 mRebuildResult = null;
594 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) {
595 Message msg = mBackgroundHandler.obtainMessage(
596 BackgroundHandler.MSG_REBUILD_LIST);
597 mBackgroundHandler.sendMessage(msg);
605 void handleRebuildList() {
607 Comparator<AppEntry> comparator;
608 synchronized (mRebuildSync) {
609 if (!mRebuildRequested) {
613 filter = mRebuildFilter;
614 comparator = mRebuildComparator;
615 mRebuildRequested = false;
616 mRebuildFilter = null;
617 mRebuildComparator = null;
618 if (mRebuildForeground) {
619 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
620 mRebuildForeground = false;
624 if (filter != null) {
625 filter.init(mContext);
629 synchronized (mEntriesMap) {
630 apps = new ArrayList<>(mAppEntries);
633 ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>();
634 if (DEBUG) Log.i(TAG, "Rebuilding...");
635 for (int i=0; i<apps.size(); i++) {
636 AppEntry entry = apps.get(i);
637 if (entry != null && (filter == null || filter.filterApp(entry))) {
638 synchronized (mEntriesMap) {
639 if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock");
640 if (comparator != null) {
641 // Only need the label if we are going to be sorting.
642 entry.ensureLabel(mContext);
644 if (DEBUG) Log.i(TAG, "Using " + entry.info.packageName + ": " + entry);
645 filteredApps.add(entry);
646 if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock");
651 if (comparator != null) {
652 Collections.sort(filteredApps, comparator);
655 synchronized (mRebuildSync) {
656 if (!mRebuildRequested) {
657 mLastAppList = filteredApps;
658 if (!mRebuildAsync) {
659 mRebuildResult = filteredApps;
660 mRebuildSync.notifyAll();
662 if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) {
663 Message msg = mMainHandler.obtainMessage(
664 MainHandler.MSG_REBUILD_COMPLETE, this);
665 mMainHandler.sendMessage(msg);
671 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
674 public void release() {
676 synchronized (mEntriesMap) {
677 mSessions.remove(this);
682 class MainHandler extends Handler {
683 static final int MSG_REBUILD_COMPLETE = 1;
684 static final int MSG_PACKAGE_LIST_CHANGED = 2;
685 static final int MSG_PACKAGE_ICON_CHANGED = 3;
686 static final int MSG_PACKAGE_SIZE_CHANGED = 4;
687 static final int MSG_ALL_SIZES_COMPUTED = 5;
688 static final int MSG_RUNNING_STATE_CHANGED = 6;
689 static final int MSG_LAUNCHER_INFO_CHANGED = 7;
690 static final int MSG_LOAD_ENTRIES_COMPLETE = 8;
692 public MainHandler(Looper looper) {
697 public void handleMessage(Message msg) {
698 rebuildActiveSessions();
700 case MSG_REBUILD_COMPLETE: {
701 Session s = (Session)msg.obj;
702 if (mActiveSessions.contains(s)) {
703 s.mCallbacks.onRebuildComplete(s.mLastAppList);
706 case MSG_PACKAGE_LIST_CHANGED: {
707 for (int i=0; i<mActiveSessions.size(); i++) {
708 mActiveSessions.get(i).mCallbacks.onPackageListChanged();
711 case MSG_PACKAGE_ICON_CHANGED: {
712 for (int i=0; i<mActiveSessions.size(); i++) {
713 mActiveSessions.get(i).mCallbacks.onPackageIconChanged();
716 case MSG_PACKAGE_SIZE_CHANGED: {
717 for (int i=0; i<mActiveSessions.size(); i++) {
718 mActiveSessions.get(i).mCallbacks.onPackageSizeChanged(
722 case MSG_ALL_SIZES_COMPUTED: {
723 for (int i=0; i<mActiveSessions.size(); i++) {
724 mActiveSessions.get(i).mCallbacks.onAllSizesComputed();
727 case MSG_RUNNING_STATE_CHANGED: {
728 for (int i=0; i<mActiveSessions.size(); i++) {
729 mActiveSessions.get(i).mCallbacks.onRunningStateChanged(
733 case MSG_LAUNCHER_INFO_CHANGED: {
734 for (int i=0; i<mActiveSessions.size(); i++) {
735 mActiveSessions.get(i).mCallbacks.onLauncherInfoChanged();
738 case MSG_LOAD_ENTRIES_COMPLETE: {
739 for (int i=0; i<mActiveSessions.size(); i++) {
740 mActiveSessions.get(i).mCallbacks.onLoadEntriesCompleted();
747 private class BackgroundHandler extends Handler {
748 static final int MSG_REBUILD_LIST = 1;
749 static final int MSG_LOAD_ENTRIES = 2;
750 static final int MSG_LOAD_ICONS = 3;
751 static final int MSG_LOAD_SIZES = 4;
752 static final int MSG_LOAD_LAUNCHER = 5;
753 static final int MSG_LOAD_HOME_APP = 6;
757 BackgroundHandler(Looper looper) {
762 public void handleMessage(Message msg) {
763 // Always try rebuilding list first thing, if needed.
764 ArrayList<Session> rebuildingSessions = null;
765 synchronized (mRebuildingSessions) {
766 if (mRebuildingSessions.size() > 0) {
767 rebuildingSessions = new ArrayList<Session>(mRebuildingSessions);
768 mRebuildingSessions.clear();
771 if (rebuildingSessions != null) {
772 for (int i=0; i<rebuildingSessions.size(); i++) {
773 rebuildingSessions.get(i).handleRebuildList();
778 case MSG_REBUILD_LIST: {
780 case MSG_LOAD_ENTRIES: {
782 synchronized (mEntriesMap) {
783 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock");
784 for (int i = 0; i < mApplications.size() && numDone < 6; i++) {
787 Message m = mMainHandler.obtainMessage(
788 MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
789 mMainHandler.sendMessage(m);
791 ApplicationInfo info = mApplications.get(i);
792 int userId = UserHandle.getUserId(info.uid);
793 if (mEntriesMap.get(userId).get(info.packageName) == null) {
795 getEntryLocked(info);
797 if (userId != 0 && mEntriesMap.indexOfKey(0) >= 0) {
798 // If this app is for a profile and we are on the owner, remove
799 // the owner entry if it isn't installed. This will prevent
800 // duplicates of work only apps showing up as 'not installed
802 // Note: This depends on us traversing the users in order, which
803 // happens because of the way we generate the list in
804 // doResumeIfNeededLocked.
805 AppEntry entry = mEntriesMap.get(0).get(info.packageName);
807 (entry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
808 mEntriesMap.get(0).remove(info.packageName);
809 mAppEntries.remove(entry);
813 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock");
817 sendEmptyMessage(MSG_LOAD_ENTRIES);
819 if (!mMainHandler.hasMessages(MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) {
820 mMainHandler.sendEmptyMessage(MainHandler.MSG_LOAD_ENTRIES_COMPLETE);
822 sendEmptyMessage(MSG_LOAD_HOME_APP);
825 case MSG_LOAD_HOME_APP: {
826 final List<ResolveInfo> homeActivities = new ArrayList<>();
827 mPm.getHomeActivities(homeActivities);
828 synchronized (mEntriesMap) {
829 final int entryCount = mEntriesMap.size();
830 for (int i = 0; i < entryCount; i++) {
831 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP acquired lock");
832 final HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
833 for (ResolveInfo activity : homeActivities) {
834 String packageName = activity.activityInfo.packageName;
835 AppEntry entry = userEntries.get(packageName);
837 entry.isHomeApp = true;
840 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP releasing lock");
843 sendEmptyMessage(MSG_LOAD_LAUNCHER);
846 case MSG_LOAD_LAUNCHER: {
847 Intent launchIntent = new Intent(Intent.ACTION_MAIN, null)
848 .addCategory(Intent.CATEGORY_LAUNCHER);
849 for (int i = 0; i < mEntriesMap.size(); i++) {
850 int userId = mEntriesMap.keyAt(i);
851 // If we do not specify MATCH_DIRECT_BOOT_AWARE or
852 // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags
853 // according to the user's lock state. When the user is locked, components
854 // with ComponentInfo#directBootAware == false will be filtered. We should
855 // explicitly include both direct boot aware and unaware components here.
856 List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser(
858 PackageManager.GET_DISABLED_COMPONENTS
859 | PackageManager.MATCH_DIRECT_BOOT_AWARE
860 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
863 synchronized (mEntriesMap) {
864 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock");
865 HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
866 final int N = intents.size();
867 for (int j = 0; j < N; j++) {
868 String packageName = intents.get(j).activityInfo.packageName;
869 AppEntry entry = userEntries.get(packageName);
871 entry.hasLauncherEntry = true;
873 Log.w(TAG, "Cannot find pkg: " + packageName
874 + " on user " + userId);
877 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock");
881 if (!mMainHandler.hasMessages(MainHandler.MSG_LAUNCHER_INFO_CHANGED)) {
882 mMainHandler.sendEmptyMessage(MainHandler.MSG_LAUNCHER_INFO_CHANGED);
884 sendEmptyMessage(MSG_LOAD_ICONS);
886 case MSG_LOAD_ICONS: {
888 synchronized (mEntriesMap) {
889 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock");
890 for (int i=0; i<mAppEntries.size() && numDone<2; i++) {
891 AppEntry entry = mAppEntries.get(i);
892 if (entry.icon == null || !entry.mounted) {
893 synchronized (entry) {
894 if (entry.ensureIconLocked(mContext, mPm)) {
897 Message m = mMainHandler.obtainMessage(
898 MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
899 mMainHandler.sendMessage(m);
906 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock");
909 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) {
910 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED);
914 sendEmptyMessage(MSG_LOAD_ICONS);
916 sendEmptyMessage(MSG_LOAD_SIZES);
919 case MSG_LOAD_SIZES: {
920 synchronized (mEntriesMap) {
921 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock");
922 if (mCurComputingSizePkg != null) {
923 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing");
927 long now = SystemClock.uptimeMillis();
928 for (int i=0; i<mAppEntries.size(); i++) {
929 AppEntry entry = mAppEntries.get(i);
930 if (entry.size == SIZE_UNKNOWN || entry.sizeStale) {
931 if (entry.sizeLoadStart == 0 ||
932 (entry.sizeLoadStart < (now-20*1000))) {
935 Message m = mMainHandler.obtainMessage(
936 MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
937 mMainHandler.sendMessage(m);
939 entry.sizeLoadStart = now;
940 mCurComputingSizePkg = entry.info.packageName;
941 mCurComputingSizeUserId = UserHandle.getUserId(entry.info.uid);
942 mPm.getPackageSizeInfoAsUser(mCurComputingSizePkg,
943 mCurComputingSizeUserId, mStatsObserver);
945 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing");
949 if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) {
950 mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED);
952 Message m = mMainHandler.obtainMessage(
953 MainHandler.MSG_RUNNING_STATE_CHANGED, 0);
954 mMainHandler.sendMessage(m);
956 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock");
962 final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
963 public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
964 boolean sizeChanged = false;
965 synchronized (mEntriesMap) {
966 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock");
967 HashMap<String, AppEntry> userMap = mEntriesMap.get(stats.userHandle);
968 if (userMap == null) {
969 // The user must have been removed.
972 AppEntry entry = userMap.get(stats.packageName);
974 synchronized (entry) {
975 entry.sizeStale = false;
976 entry.sizeLoadStart = 0;
977 long externalCodeSize = stats.externalCodeSize
978 + stats.externalObbSize;
979 long externalDataSize = stats.externalDataSize
980 + stats.externalMediaSize;
981 long newSize = externalCodeSize + externalDataSize
982 + getTotalInternalSize(stats);
983 if (entry.size != newSize ||
984 entry.cacheSize != stats.cacheSize ||
985 entry.codeSize != stats.codeSize ||
986 entry.dataSize != stats.dataSize ||
987 entry.externalCodeSize != externalCodeSize ||
988 entry.externalDataSize != externalDataSize ||
989 entry.externalCacheSize != stats.externalCacheSize) {
990 entry.size = newSize;
991 entry.cacheSize = stats.cacheSize;
992 entry.codeSize = stats.codeSize;
993 entry.dataSize = stats.dataSize;
994 entry.externalCodeSize = externalCodeSize;
995 entry.externalDataSize = externalDataSize;
996 entry.externalCacheSize = stats.externalCacheSize;
997 entry.sizeStr = getSizeStr(entry.size);
998 entry.internalSize = getTotalInternalSize(stats);
999 entry.internalSizeStr = getSizeStr(entry.internalSize);
1000 entry.externalSize = getTotalExternalSize(stats);
1001 entry.externalSizeStr = getSizeStr(entry.externalSize);
1002 if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry
1003 + ": " + entry.sizeStr);
1008 Message msg = mMainHandler.obtainMessage(
1009 MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName);
1010 mMainHandler.sendMessage(msg);
1013 if (mCurComputingSizePkg != null
1014 && (mCurComputingSizePkg.equals(stats.packageName)
1015 && mCurComputingSizeUserId == stats.userHandle)) {
1016 mCurComputingSizePkg = null;
1017 sendEmptyMessage(MSG_LOAD_SIZES);
1019 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock");
1026 * Receives notifications when applications are added/removed.
1028 private class PackageIntentReceiver extends BroadcastReceiver {
1029 void registerReceiver() {
1030 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
1031 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
1032 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
1033 filter.addDataScheme("package");
1034 mContext.registerReceiver(this, filter);
1035 // Register for events related to sdcard installation.
1036 IntentFilter sdFilter = new IntentFilter();
1037 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
1038 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
1039 mContext.registerReceiver(this, sdFilter);
1040 // Register for events related to user creation/deletion.
1041 IntentFilter userFilter = new IntentFilter();
1042 userFilter.addAction(Intent.ACTION_USER_ADDED);
1043 userFilter.addAction(Intent.ACTION_USER_REMOVED);
1044 mContext.registerReceiver(this, userFilter);
1046 void unregisterReceiver() {
1047 mContext.unregisterReceiver(this);
1050 public void onReceive(Context context, Intent intent) {
1051 String actionStr = intent.getAction();
1052 if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) {
1053 Uri data = intent.getData();
1054 String pkgName = data.getEncodedSchemeSpecificPart();
1055 for (int i = 0; i < mEntriesMap.size(); i++) {
1056 addPackage(pkgName, mEntriesMap.keyAt(i));
1058 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) {
1059 Uri data = intent.getData();
1060 String pkgName = data.getEncodedSchemeSpecificPart();
1061 for (int i = 0; i < mEntriesMap.size(); i++) {
1062 removePackage(pkgName, mEntriesMap.keyAt(i));
1064 } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) {
1065 Uri data = intent.getData();
1066 String pkgName = data.getEncodedSchemeSpecificPart();
1067 for (int i = 0; i < mEntriesMap.size(); i++) {
1068 invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1070 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) ||
1071 Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) {
1072 // When applications become available or unavailable (perhaps because
1073 // the SD card was inserted or ejected) we need to refresh the
1074 // AppInfo with new label, icon and size information as appropriate
1075 // given the newfound (un)availability of the application.
1076 // A simple way to do that is to treat the refresh as a package
1077 // removal followed by a package addition.
1078 String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1079 if (pkgList == null || pkgList.length == 0) {
1083 boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr);
1085 for (String pkgName : pkgList) {
1086 for (int i = 0; i < mEntriesMap.size(); i++) {
1087 invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1091 } else if (Intent.ACTION_USER_ADDED.equals(actionStr)) {
1092 addUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1093 } else if (Intent.ACTION_USER_REMOVED.equals(actionStr)) {
1094 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1099 public interface Callbacks {
1100 void onRunningStateChanged(boolean running);
1101 void onPackageListChanged();
1102 void onRebuildComplete(ArrayList<AppEntry> apps);
1103 void onPackageIconChanged();
1104 void onPackageSizeChanged(String packageName);
1105 void onAllSizesComputed();
1106 void onLauncherInfoChanged();
1107 void onLoadEntriesCompleted();
1110 public static class SizeInfo {
1111 public long cacheSize;
1112 public long codeSize;
1113 public long dataSize;
1114 public long externalCodeSize;
1115 public long externalDataSize;
1117 // This is the part of externalDataSize that is in the cache
1118 // section of external storage. Note that we don't just combine
1119 // this with cacheSize because currently the platform can't
1120 // automatically trim this data when needed, so it is something
1121 // the user may need to manage. The externalDataSize also includes
1122 // this value, since what this is here is really the part of
1123 // externalDataSize that we can just consider to be "cache" files
1124 // for purposes of cleaning them up in the app details UI.
1125 public long externalCacheSize;
1128 public static class AppEntry extends SizeInfo {
1129 public final File apkFile;
1130 public final long id;
1131 public String label;
1133 public long internalSize;
1134 public long externalSize;
1136 public boolean mounted;
1139 * Setting this to {@code true} prevents the entry to be filtered by
1140 * {@link #FILTER_DOWNLOADED_AND_LAUNCHER}.
1142 public boolean hasLauncherEntry;
1145 * Whether or not it's a Home app.
1147 public boolean isHomeApp;
1149 public String getNormalizedLabel() {
1150 if (normalizedLabel != null) {
1151 return normalizedLabel;
1153 normalizedLabel = normalize(label);
1154 return normalizedLabel;
1157 // Need to synchronize on 'this' for the following.
1158 public ApplicationInfo info;
1159 public Drawable icon;
1160 public String sizeStr;
1161 public String internalSizeStr;
1162 public String externalSizeStr;
1163 public boolean sizeStale;
1164 public long sizeLoadStart;
1166 public String normalizedLabel;
1168 // A location where extra info can be placed to be used by custom filters.
1169 public Object extraInfo;
1171 AppEntry(Context context, ApplicationInfo info, long id) {
1172 apkFile = new File(info.sourceDir);
1175 this.size = SIZE_UNKNOWN;
1176 this.sizeStale = true;
1177 ensureLabel(context);
1180 public void ensureLabel(Context context) {
1181 if (this.label == null || !this.mounted) {
1182 if (!this.apkFile.exists()) {
1183 this.mounted = false;
1184 this.label = info.packageName;
1186 this.mounted = true;
1187 CharSequence label = info.loadLabel(context.getPackageManager());
1188 this.label = label != null ? label.toString() : info.packageName;
1193 boolean ensureIconLocked(Context context, PackageManager pm) {
1194 if (this.icon == null) {
1195 if (this.apkFile.exists()) {
1196 this.icon = getBadgedIcon(pm);
1199 this.mounted = false;
1200 this.icon = context.getDrawable(
1201 com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
1203 } else if (!this.mounted) {
1204 // If the app wasn't mounted but is now mounted, reload
1206 if (this.apkFile.exists()) {
1207 this.mounted = true;
1208 this.icon = getBadgedIcon(pm);
1215 private Drawable getBadgedIcon(PackageManager pm) {
1216 // Do badging ourself so that it comes from the user of the app not the current user.
1217 return pm.getUserBadgedIcon(pm.loadUnbadgedItemIcon(info, info),
1218 new UserHandle(UserHandle.getUserId(info.uid)));
1221 public String getVersion(Context context) {
1223 return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName;
1224 } catch (PackageManager.NameNotFoundException e) {
1231 * Compare by label, then package name, then uid.
1233 public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
1234 private final Collator sCollator = Collator.getInstance();
1236 public int compare(AppEntry object1, AppEntry object2) {
1237 int compareResult = sCollator.compare(object1.label, object2.label);
1238 if (compareResult != 0) {
1239 return compareResult;
1241 if (object1.info != null && object2.info != null) {
1243 sCollator.compare(object1.info.packageName, object2.info.packageName);
1244 if (compareResult != 0) {
1245 return compareResult;
1248 return object1.info.uid - object2.info.uid;
1252 public static final Comparator<AppEntry> SIZE_COMPARATOR
1253 = new Comparator<AppEntry>() {
1255 public int compare(AppEntry object1, AppEntry object2) {
1256 if (object1.size < object2.size) return 1;
1257 if (object1.size > object2.size) return -1;
1258 return ALPHA_COMPARATOR.compare(object1, object2);
1262 public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR
1263 = new Comparator<AppEntry>() {
1265 public int compare(AppEntry object1, AppEntry object2) {
1266 if (object1.internalSize < object2.internalSize) return 1;
1267 if (object1.internalSize > object2.internalSize) return -1;
1268 return ALPHA_COMPARATOR.compare(object1, object2);
1272 public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR
1273 = new Comparator<AppEntry>() {
1275 public int compare(AppEntry object1, AppEntry object2) {
1276 if (object1.externalSize < object2.externalSize) return 1;
1277 if (object1.externalSize > object2.externalSize) return -1;
1278 return ALPHA_COMPARATOR.compare(object1, object2);
1282 public interface AppFilter {
1284 default void init(Context context) {
1287 boolean filterApp(AppEntry info);
1290 public static final AppFilter FILTER_PERSONAL = new AppFilter() {
1291 private int mCurrentUser;
1293 public void init() {
1294 mCurrentUser = ActivityManager.getCurrentUser();
1298 public boolean filterApp(AppEntry entry) {
1299 return UserHandle.getUserId(entry.info.uid) == mCurrentUser;
1303 public static final AppFilter FILTER_WITHOUT_DISABLED_UNTIL_USED = new AppFilter() {
1304 public void init() {
1309 public boolean filterApp(AppEntry entry) {
1310 return entry.info.enabledSetting
1311 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
1315 public static final AppFilter FILTER_WORK = new AppFilter() {
1316 private int mCurrentUser;
1318 public void init() {
1319 mCurrentUser = ActivityManager.getCurrentUser();
1323 public boolean filterApp(AppEntry entry) {
1324 return UserHandle.getUserId(entry.info.uid) != mCurrentUser;
1329 * Displays a combined list with "downloaded" and "visible in launcher" apps only.
1331 public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER = new AppFilter() {
1332 public void init() {
1336 public boolean filterApp(AppEntry entry) {
1337 if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
1339 } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
1341 } else if (entry.hasLauncherEntry) {
1343 } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && entry.isHomeApp) {
1350 public static final AppFilter FILTER_THIRD_PARTY = new AppFilter() {
1351 public void init() {
1355 public boolean filterApp(AppEntry entry) {
1356 if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
1358 } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
1365 public static final AppFilter FILTER_DISABLED = new AppFilter() {
1366 public void init() {
1370 public boolean filterApp(AppEntry entry) {
1371 return !entry.info.enabled;
1375 public static final AppFilter FILTER_ALL_ENABLED = new AppFilter() {
1376 public void init() {
1380 public boolean filterApp(AppEntry entry) {
1381 return entry.info.enabled;
1385 public static final AppFilter FILTER_EVERYTHING = new AppFilter() {
1386 public void init() {
1390 public boolean filterApp(AppEntry entry) {
1395 public static final AppFilter FILTER_WITH_DOMAIN_URLS = new AppFilter() {
1396 public void init() {
1400 public boolean filterApp(AppEntry entry) {
1401 return (entry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0;
1405 public static final AppFilter FILTER_NOT_HIDE = new AppFilter() {
1406 private String[] mHidePackageNames;
1408 public void init(Context context) {
1409 mHidePackageNames = context.getResources()
1410 .getStringArray(R.array.config_hideWhenDisabled_packageNames);
1414 public void init() {
1418 public boolean filterApp(AppEntry entry) {
1419 if (ArrayUtils.contains(mHidePackageNames, entry.info.packageName)) {
1420 if (!entry.info.enabled) {
1422 } else if (entry.info.enabledSetting ==
1423 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
1432 public static class VolumeFilter implements AppFilter {
1433 private final String mVolumeUuid;
1435 public VolumeFilter(String volumeUuid) {
1436 mVolumeUuid = volumeUuid;
1440 public void init() {
1444 public boolean filterApp(AppEntry info) {
1445 return Objects.equals(info.info.volumeUuid, mVolumeUuid);
1449 public static class CompoundFilter implements AppFilter {
1450 private final AppFilter mFirstFilter;
1451 private final AppFilter mSecondFilter;
1453 public CompoundFilter(AppFilter first, AppFilter second) {
1454 mFirstFilter = first;
1455 mSecondFilter = second;
1459 public void init(Context context) {
1460 mFirstFilter.init(context);
1461 mSecondFilter.init(context);
1465 public void init() {
1466 mFirstFilter.init();
1467 mSecondFilter.init();
1471 public boolean filterApp(AppEntry info) {
1472 return mFirstFilter.filterApp(info) && mSecondFilter.filterApp(info);