Loading packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +140 −32 Original line number Diff line number Diff line Loading @@ -128,12 +128,12 @@ public class ApplicationsState { // to protect access to these. final ArrayList<Session> mSessions = new ArrayList<Session>(); final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>(); final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); private InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); // Map: userid => (Map: package name => AppEntry) final SparseArray<HashMap<String, AppEntry>> mEntriesMap = new SparseArray<HashMap<String, AppEntry>>(); final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>(); List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>(); List<ApplicationInfo> mApplications = new ArrayList<>(); long mCurId = 1; UUID mCurComputingSizeUuid; String mCurComputingSizePkg; Loading Loading @@ -174,7 +174,13 @@ public class ApplicationsState { FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER }) @Retention(RetentionPolicy.SOURCE) public @interface SessionFlags {} public @interface SessionFlags { } @VisibleForTesting void setInterestingConfigChanges(InterestingConfigChanges interestingConfigChanges) { mInterestingConfigChanges = interestingConfigChanges; } public static final @SessionFlags int DEFAULT_SESSION_FLAGS = FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS | Loading @@ -190,6 +196,7 @@ public class ApplicationsState { for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) { mEntriesMap.put(userId, new HashMap<String, AppEntry>()); } mThread = new HandlerThread("ApplicationsState.Loader", Process.THREAD_PRIORITY_BACKGROUND); mThread.start(); Loading Loading @@ -256,12 +263,14 @@ public class ApplicationsState { mPackageIntentReceiver = new PackageIntentReceiver(); mPackageIntentReceiver.registerReceiver(); } mApplications = new ArrayList<ApplicationInfo>(); final List<ApplicationInfo> prevApplications = mApplications; mApplications = new ArrayList<>(); for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) { try { // If this user is new, it needs a map created. if (mEntriesMap.indexOfKey(user.id) < 0) { mEntriesMap.put(user.id, new HashMap<String, AppEntry>()); mEntriesMap.put(user.id, new HashMap<>()); } @SuppressWarnings("unchecked") ParceledListSlice<ApplicationInfo> list = Loading Loading @@ -312,8 +321,9 @@ public class ApplicationsState { entry.info = info; } } if (mAppEntries.size() > mApplications.size()) { // There are less apps now, some must have been uninstalled. if (anyAppIsRemoved(prevApplications, mApplications)) { // some apps have been uninstalled. clearEntries(); } mCurComputingSizePkg = null; Loading @@ -322,6 +332,82 @@ public class ApplicationsState { } } /* The original design is mAppEntries.size() > mApplications.size(). It's correct if there is only the owner user and only one app is removed. Problem 1: If there is a user profile, the size of mAppEntries < mApplications is normal because the number of app entries on UI (mAppEntries) should be equal to the number of apps got from PMS (mApplications). owner only case: mApplications: user 0: 191 mAppEntries : user 0: 191 total mAppEntries: 191, mApplications: 191 If an app is removed, cached mAppEntries: 191 , mApplications: 191 -> 190, it is detected as the number of apps becomes less. If there is a work profile, mAppEntries removes some apps that are not installed for the owner user. For example, in the following case, 6 apps are removed from mAppEntries for the owner. mApplications: user 0: 197, user 10: 189 => total 386 mAppEntries : user 0: 191, user 10: 189 => total 380 If an app is removed, cached mAppEntries: 380 , mApplications: 386 -> 385, the size of mAppEntries is still not larger than mApplications, then does not clear mAppEntries. Problem 2: If remove an app and add another app outside Settings (e.g. Play Store) and back to Settings, the amount of apps are not changed, it causes the entries keep the removed app. Another case, if adding more apps than removing apps (e.g. add 2 apps and remove 1 app), the final number of apps (mApplications) is even increased, Therefore, should not only count on number of apps to determine any app is removed. Compare the change of applications instead. */ private static boolean anyAppIsRemoved(List<ApplicationInfo> prevApplications, List<ApplicationInfo> applications) { // No cache if (prevApplications.size() == 0) { return false; } if (applications.size() < prevApplications.size()) { return true; } // build package sets of all applications <userId, HashSet of packages> final HashMap<String, HashSet<String>> packageMap = new HashMap<>(); for (ApplicationInfo application : applications) { final String userId = String.valueOf(UserHandle.getUserId(application.uid)); HashSet<String> appPackages = packageMap.get(userId); if (appPackages == null) { appPackages = new HashSet<>(); packageMap.put(userId, appPackages); } if (hasFlag(application.flags, ApplicationInfo.FLAG_INSTALLED)) { appPackages.add(application.packageName); } } // detect any previous app is removed for (ApplicationInfo prevApplication : prevApplications) { if (!hasFlag(prevApplication.flags, ApplicationInfo.FLAG_INSTALLED)) { continue; } final String userId = String.valueOf(UserHandle.getUserId(prevApplication.uid)); final HashSet<String> packagesSet = packageMap.get(userId); if (packagesSet == null || !packagesSet.remove(prevApplication.packageName)) { return true; } } return false; } @VisibleForTesting void clearEntries() { for (int i = 0; i < mEntriesMap.size(); i++) { Loading Loading @@ -1047,9 +1133,9 @@ public class ApplicationsState { // If we do not specify MATCH_DIRECT_BOOT_AWARE or // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags // according to the user's lock state. When the user is locked, // components // with ComponentInfo#directBootAware == false will be filtered. We should // explicitly include both direct boot aware and unaware components here. // components with ComponentInfo#directBootAware == false will be // filtered. W should explicitly include both direct boot aware and // unaware component here. List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser( launchIntent, PackageManager.MATCH_DISABLED_COMPONENTS Loading Loading @@ -1128,8 +1214,10 @@ public class ApplicationsState { synchronized (mEntriesMap) { if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock"); if (mCurComputingSizePkg != null) { if (DEBUG_LOCKING) Log.v(TAG, if (DEBUG_LOCKING) { Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing"); } return; } Loading Loading @@ -1181,8 +1269,10 @@ public class ApplicationsState { }); } if (DEBUG_LOCKING) Log.v(TAG, if (DEBUG_LOCKING) { Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing"); } return; } } Loading Loading @@ -1255,8 +1345,10 @@ public class ApplicationsState { entry.internalSizeStr = getSizeStr(entry.internalSize); entry.externalSize = getTotalExternalSize(stats); entry.externalSizeStr = getSizeStr(entry.externalSize); if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry if (DEBUG) { Log.i(TAG, "Set size of " + entry.label + " " + entry + ": " + entry.sizeStr); } sizeChanged = true; } } Loading Loading @@ -1299,9 +1391,11 @@ public class ApplicationsState { userFilter.addAction(Intent.ACTION_USER_REMOVED); mContext.registerReceiver(this, userFilter); } void unregisterReceiver() { mContext.unregisterReceiver(this); } @Override public void onReceive(Context context, Intent intent) { String actionStr = intent.getAction(); Loading Loading @@ -1354,12 +1448,19 @@ public class ApplicationsState { public interface Callbacks { void onRunningStateChanged(boolean running); void onPackageListChanged(); void onRebuildComplete(ArrayList<AppEntry> apps); void onPackageIconChanged(); void onPackageSizeChanged(String packageName); void onAllSizesComputed(); void onLauncherInfoChanged(); void onLoadEntriesCompleted(); } Loading Loading @@ -1491,6 +1592,7 @@ public class ApplicationsState { */ public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() { private final Collator sCollator = Collator.getInstance(); @Override public int compare(AppEntry object1, AppEntry object2) { int compareResult = sCollator.compare(object1.label, object2.label); Loading @@ -1504,6 +1606,7 @@ public class ApplicationsState { return compareResult; } } return object1.info.uid - object2.info.uid; } }; Loading Loading @@ -1540,9 +1643,11 @@ public class ApplicationsState { public interface AppFilter { void init(); default void init(Context context) { init(); } boolean filterApp(AppEntry info); } Loading Loading @@ -1697,7 +1802,8 @@ public class ApplicationsState { @Override public boolean filterApp(AppEntry entry) { return !AppUtils.isInstant(entry.info) && hasFlag(entry.info.privateFlags, ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS); && hasFlag(entry.info.privateFlags, ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS); } }; Loading Loading @@ -1823,7 +1929,8 @@ public class ApplicationsState { public static final AppFilter FILTER_PHOTOS = new AppFilter() { @Override public void init() {} public void init() { } @Override public boolean filterApp(AppEntry entry) { Loading @@ -1838,7 +1945,8 @@ public class ApplicationsState { public static final AppFilter FILTER_OTHER_APPS = new AppFilter() { @Override public void init() {} public void init() { } @Override public boolean filterApp(AppEntry entry) { Loading packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +359 −3 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java +15 −6 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import java.util.List; @Implements(value = UserManager.class) public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager { private List<UserInfo> mUserInfos = addProfile(0, "Owner"); @Implementation protected static UserManager get(Context context) { Loading @@ -37,16 +38,24 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager @Implementation protected int[] getProfileIdsWithDisabled(int userId) { return new int[]{0}; return mUserInfos.stream().mapToInt(s -> s.id).toArray(); } @Implementation protected List<UserInfo> getProfiles() { UserInfo userInfo = new UserInfo(); userInfo.id = 0; List<UserInfo> userInfos = new ArrayList<>(); userInfos.add(userInfo); return userInfos; return mUserInfos; } public List<UserInfo> addProfile(int id, String name) { List<UserInfo> userInfoList = mUserInfos; if (userInfoList == null) { userInfoList = new ArrayList<>(); } final UserInfo userInfo = new UserInfo(); userInfo.id = id; userInfo.name = name; userInfoList.add(userInfo); return userInfoList; } @Implementation Loading Loading
packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +140 −32 Original line number Diff line number Diff line Loading @@ -128,12 +128,12 @@ public class ApplicationsState { // to protect access to these. final ArrayList<Session> mSessions = new ArrayList<Session>(); final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>(); final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); private InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); // Map: userid => (Map: package name => AppEntry) final SparseArray<HashMap<String, AppEntry>> mEntriesMap = new SparseArray<HashMap<String, AppEntry>>(); final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>(); List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>(); List<ApplicationInfo> mApplications = new ArrayList<>(); long mCurId = 1; UUID mCurComputingSizeUuid; String mCurComputingSizePkg; Loading Loading @@ -174,7 +174,13 @@ public class ApplicationsState { FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER }) @Retention(RetentionPolicy.SOURCE) public @interface SessionFlags {} public @interface SessionFlags { } @VisibleForTesting void setInterestingConfigChanges(InterestingConfigChanges interestingConfigChanges) { mInterestingConfigChanges = interestingConfigChanges; } public static final @SessionFlags int DEFAULT_SESSION_FLAGS = FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS | Loading @@ -190,6 +196,7 @@ public class ApplicationsState { for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) { mEntriesMap.put(userId, new HashMap<String, AppEntry>()); } mThread = new HandlerThread("ApplicationsState.Loader", Process.THREAD_PRIORITY_BACKGROUND); mThread.start(); Loading Loading @@ -256,12 +263,14 @@ public class ApplicationsState { mPackageIntentReceiver = new PackageIntentReceiver(); mPackageIntentReceiver.registerReceiver(); } mApplications = new ArrayList<ApplicationInfo>(); final List<ApplicationInfo> prevApplications = mApplications; mApplications = new ArrayList<>(); for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) { try { // If this user is new, it needs a map created. if (mEntriesMap.indexOfKey(user.id) < 0) { mEntriesMap.put(user.id, new HashMap<String, AppEntry>()); mEntriesMap.put(user.id, new HashMap<>()); } @SuppressWarnings("unchecked") ParceledListSlice<ApplicationInfo> list = Loading Loading @@ -312,8 +321,9 @@ public class ApplicationsState { entry.info = info; } } if (mAppEntries.size() > mApplications.size()) { // There are less apps now, some must have been uninstalled. if (anyAppIsRemoved(prevApplications, mApplications)) { // some apps have been uninstalled. clearEntries(); } mCurComputingSizePkg = null; Loading @@ -322,6 +332,82 @@ public class ApplicationsState { } } /* The original design is mAppEntries.size() > mApplications.size(). It's correct if there is only the owner user and only one app is removed. Problem 1: If there is a user profile, the size of mAppEntries < mApplications is normal because the number of app entries on UI (mAppEntries) should be equal to the number of apps got from PMS (mApplications). owner only case: mApplications: user 0: 191 mAppEntries : user 0: 191 total mAppEntries: 191, mApplications: 191 If an app is removed, cached mAppEntries: 191 , mApplications: 191 -> 190, it is detected as the number of apps becomes less. If there is a work profile, mAppEntries removes some apps that are not installed for the owner user. For example, in the following case, 6 apps are removed from mAppEntries for the owner. mApplications: user 0: 197, user 10: 189 => total 386 mAppEntries : user 0: 191, user 10: 189 => total 380 If an app is removed, cached mAppEntries: 380 , mApplications: 386 -> 385, the size of mAppEntries is still not larger than mApplications, then does not clear mAppEntries. Problem 2: If remove an app and add another app outside Settings (e.g. Play Store) and back to Settings, the amount of apps are not changed, it causes the entries keep the removed app. Another case, if adding more apps than removing apps (e.g. add 2 apps and remove 1 app), the final number of apps (mApplications) is even increased, Therefore, should not only count on number of apps to determine any app is removed. Compare the change of applications instead. */ private static boolean anyAppIsRemoved(List<ApplicationInfo> prevApplications, List<ApplicationInfo> applications) { // No cache if (prevApplications.size() == 0) { return false; } if (applications.size() < prevApplications.size()) { return true; } // build package sets of all applications <userId, HashSet of packages> final HashMap<String, HashSet<String>> packageMap = new HashMap<>(); for (ApplicationInfo application : applications) { final String userId = String.valueOf(UserHandle.getUserId(application.uid)); HashSet<String> appPackages = packageMap.get(userId); if (appPackages == null) { appPackages = new HashSet<>(); packageMap.put(userId, appPackages); } if (hasFlag(application.flags, ApplicationInfo.FLAG_INSTALLED)) { appPackages.add(application.packageName); } } // detect any previous app is removed for (ApplicationInfo prevApplication : prevApplications) { if (!hasFlag(prevApplication.flags, ApplicationInfo.FLAG_INSTALLED)) { continue; } final String userId = String.valueOf(UserHandle.getUserId(prevApplication.uid)); final HashSet<String> packagesSet = packageMap.get(userId); if (packagesSet == null || !packagesSet.remove(prevApplication.packageName)) { return true; } } return false; } @VisibleForTesting void clearEntries() { for (int i = 0; i < mEntriesMap.size(); i++) { Loading Loading @@ -1047,9 +1133,9 @@ public class ApplicationsState { // If we do not specify MATCH_DIRECT_BOOT_AWARE or // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags // according to the user's lock state. When the user is locked, // components // with ComponentInfo#directBootAware == false will be filtered. We should // explicitly include both direct boot aware and unaware components here. // components with ComponentInfo#directBootAware == false will be // filtered. W should explicitly include both direct boot aware and // unaware component here. List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser( launchIntent, PackageManager.MATCH_DISABLED_COMPONENTS Loading Loading @@ -1128,8 +1214,10 @@ public class ApplicationsState { synchronized (mEntriesMap) { if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock"); if (mCurComputingSizePkg != null) { if (DEBUG_LOCKING) Log.v(TAG, if (DEBUG_LOCKING) { Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing"); } return; } Loading Loading @@ -1181,8 +1269,10 @@ public class ApplicationsState { }); } if (DEBUG_LOCKING) Log.v(TAG, if (DEBUG_LOCKING) { Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing"); } return; } } Loading Loading @@ -1255,8 +1345,10 @@ public class ApplicationsState { entry.internalSizeStr = getSizeStr(entry.internalSize); entry.externalSize = getTotalExternalSize(stats); entry.externalSizeStr = getSizeStr(entry.externalSize); if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry if (DEBUG) { Log.i(TAG, "Set size of " + entry.label + " " + entry + ": " + entry.sizeStr); } sizeChanged = true; } } Loading Loading @@ -1299,9 +1391,11 @@ public class ApplicationsState { userFilter.addAction(Intent.ACTION_USER_REMOVED); mContext.registerReceiver(this, userFilter); } void unregisterReceiver() { mContext.unregisterReceiver(this); } @Override public void onReceive(Context context, Intent intent) { String actionStr = intent.getAction(); Loading Loading @@ -1354,12 +1448,19 @@ public class ApplicationsState { public interface Callbacks { void onRunningStateChanged(boolean running); void onPackageListChanged(); void onRebuildComplete(ArrayList<AppEntry> apps); void onPackageIconChanged(); void onPackageSizeChanged(String packageName); void onAllSizesComputed(); void onLauncherInfoChanged(); void onLoadEntriesCompleted(); } Loading Loading @@ -1491,6 +1592,7 @@ public class ApplicationsState { */ public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() { private final Collator sCollator = Collator.getInstance(); @Override public int compare(AppEntry object1, AppEntry object2) { int compareResult = sCollator.compare(object1.label, object2.label); Loading @@ -1504,6 +1606,7 @@ public class ApplicationsState { return compareResult; } } return object1.info.uid - object2.info.uid; } }; Loading Loading @@ -1540,9 +1643,11 @@ public class ApplicationsState { public interface AppFilter { void init(); default void init(Context context) { init(); } boolean filterApp(AppEntry info); } Loading Loading @@ -1697,7 +1802,8 @@ public class ApplicationsState { @Override public boolean filterApp(AppEntry entry) { return !AppUtils.isInstant(entry.info) && hasFlag(entry.info.privateFlags, ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS); && hasFlag(entry.info.privateFlags, ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS); } }; Loading Loading @@ -1823,7 +1929,8 @@ public class ApplicationsState { public static final AppFilter FILTER_PHOTOS = new AppFilter() { @Override public void init() {} public void init() { } @Override public boolean filterApp(AppEntry entry) { Loading @@ -1838,7 +1945,8 @@ public class ApplicationsState { public static final AppFilter FILTER_OTHER_APPS = new AppFilter() { @Override public void init() {} public void init() { } @Override public boolean filterApp(AppEntry entry) { Loading
packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +359 −3 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java +15 −6 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import java.util.List; @Implements(value = UserManager.class) public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager { private List<UserInfo> mUserInfos = addProfile(0, "Owner"); @Implementation protected static UserManager get(Context context) { Loading @@ -37,16 +38,24 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager @Implementation protected int[] getProfileIdsWithDisabled(int userId) { return new int[]{0}; return mUserInfos.stream().mapToInt(s -> s.id).toArray(); } @Implementation protected List<UserInfo> getProfiles() { UserInfo userInfo = new UserInfo(); userInfo.id = 0; List<UserInfo> userInfos = new ArrayList<>(); userInfos.add(userInfo); return userInfos; return mUserInfos; } public List<UserInfo> addProfile(int id, String name) { List<UserInfo> userInfoList = mUserInfos; if (userInfoList == null) { userInfoList = new ArrayList<>(); } final UserInfo userInfo = new UserInfo(); userInfo.id = id; userInfo.name = name; userInfoList.add(userInfo); return userInfoList; } @Implementation Loading