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;
52 import java.text.Collator;
53 import java.text.Normalizer;
54 import java.text.Normalizer.Form;
55 import java.util.ArrayList;
56 import java.util.Collections;
57 import java.util.Comparator;
58 import java.util.HashMap;
59 import java.util.List;
60 import java.util.Objects;
61 import java.util.regex.Pattern;
64 * Keeps track of information about all installed applications, lazy-loading
67 public class ApplicationsState {
68 static final String TAG = "ApplicationsState";
69 static final boolean DEBUG = false;
70 static final boolean DEBUG_LOCKING = false;
72 public static final int SIZE_UNKNOWN = -1;
73 public static final int SIZE_INVALID = -2;
75 static final Pattern REMOVE_DIACRITICALS_PATTERN
76 = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
78 static final Object sLock = new Object();
79 static ApplicationsState sInstance;
81 public static ApplicationsState getInstance(Application app) {
82 synchronized (sLock) {
83 if (sInstance == null) {
84 sInstance = new ApplicationsState(app);
90 final Context mContext;
91 final PackageManager mPm;
92 final IPackageManager mIpm;
93 final UserManager mUm;
94 final int mAdminRetrieveFlags;
95 final int mRetrieveFlags;
96 PackageIntentReceiver mPackageIntentReceiver;
99 boolean mHaveDisabledApps;
101 // Information about all applications. Synchronize on mEntriesMap
102 // to protect access to these.
103 final ArrayList<Session> mSessions = new ArrayList<Session>();
104 final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>();
105 final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges();
106 // Map: userid => (Map: package name => AppEntry)
107 final SparseArray<HashMap<String, AppEntry>> mEntriesMap =
108 new SparseArray<HashMap<String, AppEntry>>();
109 final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>();
110 List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>();
112 String mCurComputingSizePkg;
113 int mCurComputingSizeUserId;
114 boolean mSessionsChanged;
116 // Temporary for dispatching session callbacks. Only touched by main thread.
117 final ArrayList<Session> mActiveSessions = new ArrayList<Session>();
119 final HandlerThread mThread;
120 final BackgroundHandler mBackgroundHandler;
121 final MainHandler mMainHandler = new MainHandler(Looper.getMainLooper());
123 private ApplicationsState(Application app) {
125 mPm = mContext.getPackageManager();
126 mIpm = AppGlobals.getPackageManager();
127 mUm = (UserManager) app.getSystemService(Context.USER_SERVICE);
128 for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) {
129 mEntriesMap.put(userId, new HashMap<String, AppEntry>());
131 mThread = new HandlerThread("ApplicationsState.Loader",
132 Process.THREAD_PRIORITY_BACKGROUND);
134 mBackgroundHandler = new BackgroundHandler(mThread.getLooper());
136 // Only the owner can see all apps.
137 mAdminRetrieveFlags = PackageManager.GET_UNINSTALLED_PACKAGES |
138 PackageManager.GET_DISABLED_COMPONENTS |
139 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS;
140 mRetrieveFlags = PackageManager.GET_DISABLED_COMPONENTS |
141 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS;
144 * This is a trick to prevent the foreground thread from being delayed.
145 * The problem is that Dalvik monitors are initially spin locks, to keep
146 * them lightweight. This leads to unfair contention -- Even though the
147 * background thread only holds the lock for a short amount of time, if
148 * it keeps running and locking again it can prevent the main thread from
149 * acquiring its lock for a long time... sometimes even > 5 seconds
150 * (leading to an ANR).
152 * Dalvik will promote a monitor to a "real" lock if it detects enough
153 * contention on it. It doesn't figure this out fast enough for us
154 * here, though, so this little trick will force it to turn into a real
157 synchronized (mEntriesMap) {
160 } catch (InterruptedException e) {
165 public Looper getBackgroundLooper() {
166 return mThread.getLooper();
169 public Session newSession(Callbacks callbacks) {
170 Session s = new Session(callbacks);
171 synchronized (mEntriesMap) {
177 void doResumeIfNeededLocked() {
182 if (mPackageIntentReceiver == null) {
183 mPackageIntentReceiver = new PackageIntentReceiver();
184 mPackageIntentReceiver.registerReceiver();
186 mApplications = new ArrayList<ApplicationInfo>();
187 for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) {
189 // If this user is new, it needs a map created.
190 if (mEntriesMap.indexOfKey(user.id) < 0) {
191 mEntriesMap.put(user.id, new HashMap<String, AppEntry>());
193 @SuppressWarnings("unchecked")
194 ParceledListSlice<ApplicationInfo> list =
195 mIpm.getInstalledApplications(
196 user.isAdmin() ? mAdminRetrieveFlags : mRetrieveFlags,
198 mApplications.addAll(list.getList());
199 } catch (RemoteException e) {
203 if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
204 // If an interesting part of the configuration has changed, we
205 // should completely reload the app entries.
208 for (int i=0; i<mAppEntries.size(); i++) {
209 mAppEntries.get(i).sizeStale = true;
213 mHaveDisabledApps = false;
214 for (int i=0; i<mApplications.size(); i++) {
215 final ApplicationInfo info = mApplications.get(i);
216 // Need to trim out any applications that are disabled by
217 // something different than the user.
219 if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
220 mApplications.remove(i);
224 mHaveDisabledApps = true;
226 int userId = UserHandle.getUserId(info.uid);
227 final AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
232 if (mAppEntries.size() > mApplications.size()) {
233 // There are less apps now, some must have been uninstalled.
236 mCurComputingSizePkg = null;
237 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
238 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
242 private void clearEntries() {
243 for (int i = 0; i < mEntriesMap.size(); i++) {
244 mEntriesMap.valueAt(i).clear();
249 public boolean haveDisabledApps() {
250 return mHaveDisabledApps;
253 void doPauseIfNeededLocked() {
257 for (int i=0; i<mSessions.size(); i++) {
258 if (mSessions.get(i).mResumed) {
265 void doPauseLocked() {
267 if (mPackageIntentReceiver != null) {
268 mPackageIntentReceiver.unregisterReceiver();
269 mPackageIntentReceiver = null;
273 public AppEntry getEntry(String packageName, int userId) {
274 if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
275 synchronized (mEntriesMap) {
276 AppEntry entry = mEntriesMap.get(userId).get(packageName);
278 ApplicationInfo info = getAppInfoLocked(packageName, userId);
281 info = mIpm.getApplicationInfo(packageName, 0, userId);
282 } catch (RemoteException e) {
283 Log.w(TAG, "getEntry couldn't reach PackageManager", e);
288 entry = getEntryLocked(info);
291 if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock");
296 private ApplicationInfo getAppInfoLocked(String pkg, int userId) {
297 for (int i = 0; i < mApplications.size(); i++) {
298 ApplicationInfo info = mApplications.get(i);
299 if (pkg.equals(info.packageName)
300 && userId == UserHandle.getUserId(info.uid)) {
307 public void ensureIcon(AppEntry entry) {
308 if (entry.icon != null) {
311 synchronized (entry) {
312 entry.ensureIconLocked(mContext, mPm);
316 public void requestSize(String packageName, int userId) {
317 if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock...");
318 synchronized (mEntriesMap) {
319 AppEntry entry = mEntriesMap.get(userId).get(packageName);
321 mPm.getPackageSizeInfoAsUser(packageName, userId, mBackgroundHandler.mStatsObserver);
323 if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock");
327 long sumCacheSizes() {
329 if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock...");
330 synchronized (mEntriesMap) {
331 if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock");
332 for (int i=mAppEntries.size()-1; i>=0; i--) {
333 sum += mAppEntries.get(i).cacheSize;
335 if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock");
340 int indexOfApplicationInfoLocked(String pkgName, int userId) {
341 for (int i=mApplications.size()-1; i>=0; i--) {
342 ApplicationInfo appInfo = mApplications.get(i);
343 if (appInfo.packageName.equals(pkgName)
344 && UserHandle.getUserId(appInfo.uid) == userId) {
351 void addPackage(String pkgName, int userId) {
353 synchronized (mEntriesMap) {
354 if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock");
355 if (DEBUG) Log.i(TAG, "Adding package " + pkgName);
357 // If we are not resumed, we will do a full query the
358 // next time we resume, so there is no reason to do work
360 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed");
363 if (indexOfApplicationInfoLocked(pkgName, userId) >= 0) {
364 if (DEBUG) Log.i(TAG, "Package already exists!");
365 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists");
368 ApplicationInfo info = mIpm.getApplicationInfo(pkgName,
369 mUm.isUserAdmin(userId) ? mAdminRetrieveFlags : mRetrieveFlags,
375 if (info.enabledSetting
376 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
379 mHaveDisabledApps = true;
381 mApplications.add(info);
382 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
383 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
385 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
386 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
388 if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock");
390 } catch (RemoteException e) {
394 public void removePackage(String pkgName, int userId) {
395 synchronized (mEntriesMap) {
396 if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock");
397 int idx = indexOfApplicationInfoLocked(pkgName, userId);
398 if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx);
400 AppEntry entry = mEntriesMap.get(userId).get(pkgName);
401 if (DEBUG) Log.i(TAG, "removePackage: " + entry);
403 mEntriesMap.get(userId).remove(pkgName);
404 mAppEntries.remove(entry);
406 ApplicationInfo info = mApplications.get(idx);
407 mApplications.remove(idx);
409 mHaveDisabledApps = false;
410 for (int i=0; i<mApplications.size(); i++) {
411 if (!mApplications.get(i).enabled) {
412 mHaveDisabledApps = true;
417 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
418 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
421 if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock");
425 public void invalidatePackage(String pkgName, int userId) {
426 removePackage(pkgName, userId);
427 addPackage(pkgName, userId);
430 private void addUser(int userId) {
431 final int profileIds[] = mUm.getProfileIdsWithDisabled(UserHandle.myUserId());
432 if (ArrayUtils.contains(profileIds, userId)) {
433 synchronized (mEntriesMap) {
434 mEntriesMap.put(userId, new HashMap<String, AppEntry>());
436 // If resumed, Manually pause, then cause a resume to repopulate the app list.
437 // This is the simplest way to reload the packages so that the new user
438 // is included. Otherwise the list will be repopulated on next resume.
440 doResumeIfNeededLocked();
442 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
443 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
449 private void removeUser(int userId) {
450 synchronized (mEntriesMap) {
451 HashMap<String, AppEntry> userMap = mEntriesMap.get(userId);
452 if (userMap != null) {
453 for (AppEntry appEntry : userMap.values()) {
454 mAppEntries.remove(appEntry);
455 mApplications.remove(appEntry.info);
457 mEntriesMap.remove(userId);
458 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
459 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
465 private AppEntry getEntryLocked(ApplicationInfo info) {
466 int userId = UserHandle.getUserId(info.uid);
467 AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
468 if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
470 if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName);
471 entry = new AppEntry(mContext, info, mCurId++);
472 mEntriesMap.get(userId).put(info.packageName, entry);
473 mAppEntries.add(entry);
474 } else if (entry.info != info) {
480 // --------------------------------------------------------------
482 private long getTotalInternalSize(PackageStats ps) {
484 return ps.codeSize + ps.dataSize;
489 private long getTotalExternalSize(PackageStats ps) {
491 // We also include the cache size here because for non-emulated
492 // we don't automtically clean cache files.
493 return ps.externalCodeSize + ps.externalDataSize
494 + ps.externalCacheSize
495 + ps.externalMediaSize + ps.externalObbSize;
500 private String getSizeStr(long size) {
502 return Formatter.formatFileSize(mContext, size);
507 void rebuildActiveSessions() {
508 synchronized (mEntriesMap) {
509 if (!mSessionsChanged) {
512 mActiveSessions.clear();
513 for (int i=0; i<mSessions.size(); i++) {
514 Session s = mSessions.get(i);
516 mActiveSessions.add(s);
522 public static String normalize(String str) {
523 String tmp = Normalizer.normalize(str, Form.NFD);
524 return REMOVE_DIACRITICALS_PATTERN.matcher(tmp)
525 .replaceAll("").toLowerCase();
528 public class Session {
529 final Callbacks mCallbacks;
532 // Rebuilding of app list. Synchronized on mRebuildSync.
533 final Object mRebuildSync = new Object();
534 boolean mRebuildRequested;
535 boolean mRebuildAsync;
536 AppFilter mRebuildFilter;
537 Comparator<AppEntry> mRebuildComparator;
538 ArrayList<AppEntry> mRebuildResult;
539 ArrayList<AppEntry> mLastAppList;
540 boolean mRebuildForeground;
542 Session(Callbacks callbacks) {
543 mCallbacks = callbacks;
546 public void resume() {
547 if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
548 synchronized (mEntriesMap) {
551 mSessionsChanged = true;
552 doResumeIfNeededLocked();
555 if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
558 public void pause() {
559 if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
560 synchronized (mEntriesMap) {
563 mSessionsChanged = true;
564 mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this);
565 doPauseIfNeededLocked();
567 if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock");
571 public ArrayList<AppEntry> getAllApps() {
572 synchronized (mEntriesMap) {
573 return new ArrayList<>(mAppEntries);
577 // Creates a new list of app entries with the given filter and comparator.
578 public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) {
579 return rebuild(filter, comparator, true);
582 public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator,
583 boolean foreground) {
584 synchronized (mRebuildSync) {
585 synchronized (mRebuildingSessions) {
586 mRebuildingSessions.add(this);
587 mRebuildRequested = true;
588 mRebuildAsync = true;
589 mRebuildFilter = filter;
590 mRebuildComparator = comparator;
591 mRebuildForeground = foreground;
592 mRebuildResult = null;
593 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) {
594 Message msg = mBackgroundHandler.obtainMessage(
595 BackgroundHandler.MSG_REBUILD_LIST);
596 mBackgroundHandler.sendMessage(msg);
604 void handleRebuildList() {
606 Comparator<AppEntry> comparator;
607 synchronized (mRebuildSync) {
608 if (!mRebuildRequested) {
612 filter = mRebuildFilter;
613 comparator = mRebuildComparator;
614 mRebuildRequested = false;
615 mRebuildFilter = null;
616 mRebuildComparator = null;
617 if (mRebuildForeground) {
618 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
619 mRebuildForeground = false;
623 if (filter != null) {
628 synchronized (mEntriesMap) {
629 apps = new ArrayList<>(mAppEntries);
632 ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>();
633 if (DEBUG) Log.i(TAG, "Rebuilding...");
634 for (int i=0; i<apps.size(); i++) {
635 AppEntry entry = apps.get(i);
636 if (entry != null && (filter == null || filter.filterApp(entry))) {
637 synchronized (mEntriesMap) {
638 if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock");
639 if (comparator != null) {
640 // Only need the label if we are going to be sorting.
641 entry.ensureLabel(mContext);
643 if (DEBUG) Log.i(TAG, "Using " + entry.info.packageName + ": " + entry);
644 filteredApps.add(entry);
645 if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock");
650 if (comparator != null) {
651 Collections.sort(filteredApps, comparator);
654 synchronized (mRebuildSync) {
655 if (!mRebuildRequested) {
656 mLastAppList = filteredApps;
657 if (!mRebuildAsync) {
658 mRebuildResult = filteredApps;
659 mRebuildSync.notifyAll();
661 if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) {
662 Message msg = mMainHandler.obtainMessage(
663 MainHandler.MSG_REBUILD_COMPLETE, this);
664 mMainHandler.sendMessage(msg);
670 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
673 public void release() {
675 synchronized (mEntriesMap) {
676 mSessions.remove(this);
681 class MainHandler extends Handler {
682 static final int MSG_REBUILD_COMPLETE = 1;
683 static final int MSG_PACKAGE_LIST_CHANGED = 2;
684 static final int MSG_PACKAGE_ICON_CHANGED = 3;
685 static final int MSG_PACKAGE_SIZE_CHANGED = 4;
686 static final int MSG_ALL_SIZES_COMPUTED = 5;
687 static final int MSG_RUNNING_STATE_CHANGED = 6;
688 static final int MSG_LAUNCHER_INFO_CHANGED = 7;
689 static final int MSG_LOAD_ENTRIES_COMPLETE = 8;
691 public MainHandler(Looper looper) {
696 public void handleMessage(Message msg) {
697 rebuildActiveSessions();
699 case MSG_REBUILD_COMPLETE: {
700 Session s = (Session)msg.obj;
701 if (mActiveSessions.contains(s)) {
702 s.mCallbacks.onRebuildComplete(s.mLastAppList);
705 case MSG_PACKAGE_LIST_CHANGED: {
706 for (int i=0; i<mActiveSessions.size(); i++) {
707 mActiveSessions.get(i).mCallbacks.onPackageListChanged();
710 case MSG_PACKAGE_ICON_CHANGED: {
711 for (int i=0; i<mActiveSessions.size(); i++) {
712 mActiveSessions.get(i).mCallbacks.onPackageIconChanged();
715 case MSG_PACKAGE_SIZE_CHANGED: {
716 for (int i=0; i<mActiveSessions.size(); i++) {
717 mActiveSessions.get(i).mCallbacks.onPackageSizeChanged(
721 case MSG_ALL_SIZES_COMPUTED: {
722 for (int i=0; i<mActiveSessions.size(); i++) {
723 mActiveSessions.get(i).mCallbacks.onAllSizesComputed();
726 case MSG_RUNNING_STATE_CHANGED: {
727 for (int i=0; i<mActiveSessions.size(); i++) {
728 mActiveSessions.get(i).mCallbacks.onRunningStateChanged(
732 case MSG_LAUNCHER_INFO_CHANGED: {
733 for (int i=0; i<mActiveSessions.size(); i++) {
734 mActiveSessions.get(i).mCallbacks.onLauncherInfoChanged();
737 case MSG_LOAD_ENTRIES_COMPLETE: {
738 for (int i=0; i<mActiveSessions.size(); i++) {
739 mActiveSessions.get(i).mCallbacks.onLoadEntriesCompleted();
746 private class BackgroundHandler extends Handler {
747 static final int MSG_REBUILD_LIST = 1;
748 static final int MSG_LOAD_ENTRIES = 2;
749 static final int MSG_LOAD_ICONS = 3;
750 static final int MSG_LOAD_SIZES = 4;
751 static final int MSG_LOAD_LAUNCHER = 5;
752 static final int MSG_LOAD_HOME_APP = 6;
756 BackgroundHandler(Looper looper) {
761 public void handleMessage(Message msg) {
762 // Always try rebuilding list first thing, if needed.
763 ArrayList<Session> rebuildingSessions = null;
764 synchronized (mRebuildingSessions) {
765 if (mRebuildingSessions.size() > 0) {
766 rebuildingSessions = new ArrayList<Session>(mRebuildingSessions);
767 mRebuildingSessions.clear();
770 if (rebuildingSessions != null) {
771 for (int i=0; i<rebuildingSessions.size(); i++) {
772 rebuildingSessions.get(i).handleRebuildList();
777 case MSG_REBUILD_LIST: {
779 case MSG_LOAD_ENTRIES: {
781 synchronized (mEntriesMap) {
782 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock");
783 for (int i = 0; i < mApplications.size() && numDone < 6; i++) {
786 Message m = mMainHandler.obtainMessage(
787 MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
788 mMainHandler.sendMessage(m);
790 ApplicationInfo info = mApplications.get(i);
791 int userId = UserHandle.getUserId(info.uid);
792 if (mEntriesMap.get(userId).get(info.packageName) == null) {
794 getEntryLocked(info);
796 if (userId != 0 && mEntriesMap.indexOfKey(0) >= 0) {
797 // If this app is for a profile and we are on the owner, remove
798 // the owner entry if it isn't installed. This will prevent
799 // duplicates of work only apps showing up as 'not installed
801 // Note: This depends on us traversing the users in order, which
802 // happens because of the way we generate the list in
803 // doResumeIfNeededLocked.
804 AppEntry entry = mEntriesMap.get(0).get(info.packageName);
806 (entry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
807 mEntriesMap.get(0).remove(info.packageName);
808 mAppEntries.remove(entry);
812 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock");
816 sendEmptyMessage(MSG_LOAD_ENTRIES);
818 if (!mMainHandler.hasMessages(MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) {
819 mMainHandler.sendEmptyMessage(MainHandler.MSG_LOAD_ENTRIES_COMPLETE);
821 sendEmptyMessage(MSG_LOAD_HOME_APP);
824 case MSG_LOAD_HOME_APP: {
825 final List<ResolveInfo> homeActivities = new ArrayList<>();
826 mPm.getHomeActivities(homeActivities);
827 synchronized (mEntriesMap) {
828 final int entryCount = mEntriesMap.size();
829 for (int i = 0; i < entryCount; i++) {
830 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP acquired lock");
831 final HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
832 for (ResolveInfo activity : homeActivities) {
833 String packageName = activity.activityInfo.packageName;
834 AppEntry entry = userEntries.get(packageName);
836 entry.isHomeApp = true;
839 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP releasing lock");
842 sendEmptyMessage(MSG_LOAD_LAUNCHER);
845 case MSG_LOAD_LAUNCHER: {
846 Intent launchIntent = new Intent(Intent.ACTION_MAIN, null)
847 .addCategory(Intent.CATEGORY_LAUNCHER);
848 for (int i = 0; i < mEntriesMap.size(); i++) {
849 int userId = mEntriesMap.keyAt(i);
850 // If we do not specify MATCH_DIRECT_BOOT_AWARE or
851 // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags
852 // according to the user's lock state. When the user is locked, components
853 // with ComponentInfo#directBootAware == false will be filtered. We should
854 // explicitly include both direct boot aware and unaware components here.
855 List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser(
857 PackageManager.GET_DISABLED_COMPONENTS
858 | PackageManager.MATCH_DIRECT_BOOT_AWARE
859 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
862 synchronized (mEntriesMap) {
863 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock");
864 HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
865 final int N = intents.size();
866 for (int j = 0; j < N; j++) {
867 String packageName = intents.get(j).activityInfo.packageName;
868 AppEntry entry = userEntries.get(packageName);
870 entry.hasLauncherEntry = true;
872 Log.w(TAG, "Cannot find pkg: " + packageName
873 + " on user " + userId);
876 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock");
880 if (!mMainHandler.hasMessages(MainHandler.MSG_LAUNCHER_INFO_CHANGED)) {
881 mMainHandler.sendEmptyMessage(MainHandler.MSG_LAUNCHER_INFO_CHANGED);
883 sendEmptyMessage(MSG_LOAD_ICONS);
885 case MSG_LOAD_ICONS: {
887 synchronized (mEntriesMap) {
888 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock");
889 for (int i=0; i<mAppEntries.size() && numDone<2; i++) {
890 AppEntry entry = mAppEntries.get(i);
891 if (entry.icon == null || !entry.mounted) {
892 synchronized (entry) {
893 if (entry.ensureIconLocked(mContext, mPm)) {
896 Message m = mMainHandler.obtainMessage(
897 MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
898 mMainHandler.sendMessage(m);
905 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock");
908 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) {
909 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED);
913 sendEmptyMessage(MSG_LOAD_ICONS);
915 sendEmptyMessage(MSG_LOAD_SIZES);
918 case MSG_LOAD_SIZES: {
919 synchronized (mEntriesMap) {
920 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock");
921 if (mCurComputingSizePkg != null) {
922 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing");
926 long now = SystemClock.uptimeMillis();
927 for (int i=0; i<mAppEntries.size(); i++) {
928 AppEntry entry = mAppEntries.get(i);
929 if (entry.size == SIZE_UNKNOWN || entry.sizeStale) {
930 if (entry.sizeLoadStart == 0 ||
931 (entry.sizeLoadStart < (now-20*1000))) {
934 Message m = mMainHandler.obtainMessage(
935 MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
936 mMainHandler.sendMessage(m);
938 entry.sizeLoadStart = now;
939 mCurComputingSizePkg = entry.info.packageName;
940 mCurComputingSizeUserId = UserHandle.getUserId(entry.info.uid);
941 mPm.getPackageSizeInfoAsUser(mCurComputingSizePkg,
942 mCurComputingSizeUserId, mStatsObserver);
944 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing");
948 if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) {
949 mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED);
951 Message m = mMainHandler.obtainMessage(
952 MainHandler.MSG_RUNNING_STATE_CHANGED, 0);
953 mMainHandler.sendMessage(m);
955 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock");
961 final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
962 public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
963 boolean sizeChanged = false;
964 synchronized (mEntriesMap) {
965 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock");
966 HashMap<String, AppEntry> userMap = mEntriesMap.get(stats.userHandle);
967 if (userMap == null) {
968 // The user must have been removed.
971 AppEntry entry = userMap.get(stats.packageName);
973 synchronized (entry) {
974 entry.sizeStale = false;
975 entry.sizeLoadStart = 0;
976 long externalCodeSize = stats.externalCodeSize
977 + stats.externalObbSize;
978 long externalDataSize = stats.externalDataSize
979 + stats.externalMediaSize;
980 long newSize = externalCodeSize + externalDataSize
981 + getTotalInternalSize(stats);
982 if (entry.size != newSize ||
983 entry.cacheSize != stats.cacheSize ||
984 entry.codeSize != stats.codeSize ||
985 entry.dataSize != stats.dataSize ||
986 entry.externalCodeSize != externalCodeSize ||
987 entry.externalDataSize != externalDataSize ||
988 entry.externalCacheSize != stats.externalCacheSize) {
989 entry.size = newSize;
990 entry.cacheSize = stats.cacheSize;
991 entry.codeSize = stats.codeSize;
992 entry.dataSize = stats.dataSize;
993 entry.externalCodeSize = externalCodeSize;
994 entry.externalDataSize = externalDataSize;
995 entry.externalCacheSize = stats.externalCacheSize;
996 entry.sizeStr = getSizeStr(entry.size);
997 entry.internalSize = getTotalInternalSize(stats);
998 entry.internalSizeStr = getSizeStr(entry.internalSize);
999 entry.externalSize = getTotalExternalSize(stats);
1000 entry.externalSizeStr = getSizeStr(entry.externalSize);
1001 if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry
1002 + ": " + entry.sizeStr);
1007 Message msg = mMainHandler.obtainMessage(
1008 MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName);
1009 mMainHandler.sendMessage(msg);
1012 if (mCurComputingSizePkg != null
1013 && (mCurComputingSizePkg.equals(stats.packageName)
1014 && mCurComputingSizeUserId == stats.userHandle)) {
1015 mCurComputingSizePkg = null;
1016 sendEmptyMessage(MSG_LOAD_SIZES);
1018 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock");
1025 * Receives notifications when applications are added/removed.
1027 private class PackageIntentReceiver extends BroadcastReceiver {
1028 void registerReceiver() {
1029 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
1030 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
1031 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
1032 filter.addDataScheme("package");
1033 mContext.registerReceiver(this, filter);
1034 // Register for events related to sdcard installation.
1035 IntentFilter sdFilter = new IntentFilter();
1036 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
1037 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
1038 mContext.registerReceiver(this, sdFilter);
1039 // Register for events related to user creation/deletion.
1040 IntentFilter userFilter = new IntentFilter();
1041 userFilter.addAction(Intent.ACTION_USER_ADDED);
1042 userFilter.addAction(Intent.ACTION_USER_REMOVED);
1043 mContext.registerReceiver(this, userFilter);
1045 void unregisterReceiver() {
1046 mContext.unregisterReceiver(this);
1049 public void onReceive(Context context, Intent intent) {
1050 String actionStr = intent.getAction();
1051 if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) {
1052 Uri data = intent.getData();
1053 String pkgName = data.getEncodedSchemeSpecificPart();
1054 for (int i = 0; i < mEntriesMap.size(); i++) {
1055 addPackage(pkgName, mEntriesMap.keyAt(i));
1057 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) {
1058 Uri data = intent.getData();
1059 String pkgName = data.getEncodedSchemeSpecificPart();
1060 for (int i = 0; i < mEntriesMap.size(); i++) {
1061 removePackage(pkgName, mEntriesMap.keyAt(i));
1063 } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) {
1064 Uri data = intent.getData();
1065 String pkgName = data.getEncodedSchemeSpecificPart();
1066 for (int i = 0; i < mEntriesMap.size(); i++) {
1067 invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1069 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) ||
1070 Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) {
1071 // When applications become available or unavailable (perhaps because
1072 // the SD card was inserted or ejected) we need to refresh the
1073 // AppInfo with new label, icon and size information as appropriate
1074 // given the newfound (un)availability of the application.
1075 // A simple way to do that is to treat the refresh as a package
1076 // removal followed by a package addition.
1077 String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1078 if (pkgList == null || pkgList.length == 0) {
1082 boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr);
1084 for (String pkgName : pkgList) {
1085 for (int i = 0; i < mEntriesMap.size(); i++) {
1086 invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1090 } else if (Intent.ACTION_USER_ADDED.equals(actionStr)) {
1091 addUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1092 } else if (Intent.ACTION_USER_REMOVED.equals(actionStr)) {
1093 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1098 public interface Callbacks {
1099 void onRunningStateChanged(boolean running);
1100 void onPackageListChanged();
1101 void onRebuildComplete(ArrayList<AppEntry> apps);
1102 void onPackageIconChanged();
1103 void onPackageSizeChanged(String packageName);
1104 void onAllSizesComputed();
1105 void onLauncherInfoChanged();
1106 void onLoadEntriesCompleted();
1109 public static class SizeInfo {
1110 public long cacheSize;
1111 public long codeSize;
1112 public long dataSize;
1113 public long externalCodeSize;
1114 public long externalDataSize;
1116 // This is the part of externalDataSize that is in the cache
1117 // section of external storage. Note that we don't just combine
1118 // this with cacheSize because currently the platform can't
1119 // automatically trim this data when needed, so it is something
1120 // the user may need to manage. The externalDataSize also includes
1121 // this value, since what this is here is really the part of
1122 // externalDataSize that we can just consider to be "cache" files
1123 // for purposes of cleaning them up in the app details UI.
1124 public long externalCacheSize;
1127 public static class AppEntry extends SizeInfo {
1128 public final File apkFile;
1129 public final long id;
1130 public String label;
1132 public long internalSize;
1133 public long externalSize;
1135 public boolean mounted;
1138 * Setting this to {@code true} prevents the entry to be filtered by
1139 * {@link #FILTER_DOWNLOADED_AND_LAUNCHER}.
1141 public boolean hasLauncherEntry;
1144 * Whether or not it's a Home app.
1146 public boolean isHomeApp;
1148 public String getNormalizedLabel() {
1149 if (normalizedLabel != null) {
1150 return normalizedLabel;
1152 normalizedLabel = normalize(label);
1153 return normalizedLabel;
1156 // Need to synchronize on 'this' for the following.
1157 public ApplicationInfo info;
1158 public Drawable icon;
1159 public String sizeStr;
1160 public String internalSizeStr;
1161 public String externalSizeStr;
1162 public boolean sizeStale;
1163 public long sizeLoadStart;
1165 public String normalizedLabel;
1167 // A location where extra info can be placed to be used by custom filters.
1168 public Object extraInfo;
1170 AppEntry(Context context, ApplicationInfo info, long id) {
1171 apkFile = new File(info.sourceDir);
1174 this.size = SIZE_UNKNOWN;
1175 this.sizeStale = true;
1176 ensureLabel(context);
1179 public void ensureLabel(Context context) {
1180 if (this.label == null || !this.mounted) {
1181 if (!this.apkFile.exists()) {
1182 this.mounted = false;
1183 this.label = info.packageName;
1185 this.mounted = true;
1186 CharSequence label = info.loadLabel(context.getPackageManager());
1187 this.label = label != null ? label.toString() : info.packageName;
1192 boolean ensureIconLocked(Context context, PackageManager pm) {
1193 if (this.icon == null) {
1194 if (this.apkFile.exists()) {
1195 this.icon = getBadgedIcon(pm);
1198 this.mounted = false;
1199 this.icon = context.getDrawable(
1200 com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
1202 } else if (!this.mounted) {
1203 // If the app wasn't mounted but is now mounted, reload
1205 if (this.apkFile.exists()) {
1206 this.mounted = true;
1207 this.icon = getBadgedIcon(pm);
1214 private Drawable getBadgedIcon(PackageManager pm) {
1215 // Do badging ourself so that it comes from the user of the app not the current user.
1216 return pm.getUserBadgedIcon(pm.loadUnbadgedItemIcon(info, info),
1217 new UserHandle(UserHandle.getUserId(info.uid)));
1220 public String getVersion(Context context) {
1222 return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName;
1223 } catch (PackageManager.NameNotFoundException e) {
1230 * Compare by label, then package name, then uid.
1232 public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
1233 private final Collator sCollator = Collator.getInstance();
1235 public int compare(AppEntry object1, AppEntry object2) {
1236 int compareResult = sCollator.compare(object1.label, object2.label);
1237 if (compareResult != 0) {
1238 return compareResult;
1240 if (object1.info != null && object2.info != null) {
1242 sCollator.compare(object1.info.packageName, object2.info.packageName);
1243 if (compareResult != 0) {
1244 return compareResult;
1247 return object1.info.uid - object2.info.uid;
1251 public static final Comparator<AppEntry> SIZE_COMPARATOR
1252 = new Comparator<AppEntry>() {
1254 public int compare(AppEntry object1, AppEntry object2) {
1255 if (object1.size < object2.size) return 1;
1256 if (object1.size > object2.size) return -1;
1257 return ALPHA_COMPARATOR.compare(object1, object2);
1261 public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR
1262 = new Comparator<AppEntry>() {
1264 public int compare(AppEntry object1, AppEntry object2) {
1265 if (object1.internalSize < object2.internalSize) return 1;
1266 if (object1.internalSize > object2.internalSize) return -1;
1267 return ALPHA_COMPARATOR.compare(object1, object2);
1271 public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR
1272 = new Comparator<AppEntry>() {
1274 public int compare(AppEntry object1, AppEntry object2) {
1275 if (object1.externalSize < object2.externalSize) return 1;
1276 if (object1.externalSize > object2.externalSize) return -1;
1277 return ALPHA_COMPARATOR.compare(object1, object2);
1281 public interface AppFilter {
1283 boolean filterApp(AppEntry info);
1286 public static final AppFilter FILTER_PERSONAL = new AppFilter() {
1287 private int mCurrentUser;
1289 public void init() {
1290 mCurrentUser = ActivityManager.getCurrentUser();
1294 public boolean filterApp(AppEntry entry) {
1295 return UserHandle.getUserId(entry.info.uid) == mCurrentUser;
1299 public static final AppFilter FILTER_WITHOUT_DISABLED_UNTIL_USED = new AppFilter() {
1300 public void init() {
1305 public boolean filterApp(AppEntry entry) {
1306 return entry.info.enabledSetting
1307 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
1311 public static final AppFilter FILTER_WORK = new AppFilter() {
1312 private int mCurrentUser;
1314 public void init() {
1315 mCurrentUser = ActivityManager.getCurrentUser();
1319 public boolean filterApp(AppEntry entry) {
1320 return UserHandle.getUserId(entry.info.uid) != mCurrentUser;
1325 * Displays a combined list with "downloaded" and "visible in launcher" apps only.
1327 public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER = new AppFilter() {
1328 public void init() {
1332 public boolean filterApp(AppEntry entry) {
1333 if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
1335 } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
1337 } else if (entry.hasLauncherEntry) {
1339 } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && entry.isHomeApp) {
1346 public static final AppFilter FILTER_THIRD_PARTY = new AppFilter() {
1347 public void init() {
1351 public boolean filterApp(AppEntry entry) {
1352 if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
1354 } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
1361 public static final AppFilter FILTER_DISABLED = new AppFilter() {
1362 public void init() {
1366 public boolean filterApp(AppEntry entry) {
1367 return !entry.info.enabled;
1371 public static final AppFilter FILTER_ALL_ENABLED = new AppFilter() {
1372 public void init() {
1376 public boolean filterApp(AppEntry entry) {
1377 return entry.info.enabled;
1381 public static final AppFilter FILTER_EVERYTHING = new AppFilter() {
1382 public void init() {
1386 public boolean filterApp(AppEntry entry) {
1391 public static final AppFilter FILTER_WITH_DOMAIN_URLS = new AppFilter() {
1392 public void init() {
1396 public boolean filterApp(AppEntry entry) {
1397 return (entry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0;
1401 public static class VolumeFilter implements AppFilter {
1402 private final String mVolumeUuid;
1404 public VolumeFilter(String volumeUuid) {
1405 mVolumeUuid = volumeUuid;
1409 public void init() {
1413 public boolean filterApp(AppEntry info) {
1414 return Objects.equals(info.info.volumeUuid, mVolumeUuid);
1418 public static class CompoundFilter implements AppFilter {
1419 private final AppFilter mFirstFilter;
1420 private final AppFilter mSecondFilter;
1422 public CompoundFilter(AppFilter first, AppFilter second) {
1423 mFirstFilter = first;
1424 mSecondFilter = second;
1428 public void init() {
1429 mFirstFilter.init();
1430 mSecondFilter.init();
1434 public boolean filterApp(AppEntry info) {
1435 return mFirstFilter.filterApp(info) && mSecondFilter.filterApp(info);