diff options
3 files changed, 514 insertions, 41 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index c9fbc7ba9f05..d4d0519fcc5f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -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; @@ -166,7 +166,7 @@ public class ApplicationsState { /** * Flags to configure the session to request various types of info. */ - @IntDef(prefix = { "FLAG_SESSION_" }, value = { + @IntDef(prefix = {"FLAG_SESSION_"}, value = { FLAG_SESSION_REQUEST_HOME_APP, FLAG_SESSION_REQUEST_ICONS, FLAG_SESSION_REQUEST_SIZES, @@ -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 | @@ -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(); @@ -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 = @@ -279,14 +288,14 @@ public class ApplicationsState { // should completely reload the app entries. clearEntries(); } else { - for (int i=0; i<mAppEntries.size(); i++) { + for (int i = 0; i < mAppEntries.size(); i++) { mAppEntries.get(i).sizeStale = true; } } mHaveDisabledApps = false; mHaveInstantApps = false; - for (int i=0; i<mApplications.size(); i++) { + for (int i = 0; i < mApplications.size(); i++) { final ApplicationInfo info = mApplications.get(i); // Need to trim out any applications that are disabled by // something different than the user. @@ -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; @@ -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++) { @@ -346,7 +432,7 @@ public class ApplicationsState { if (!mResumed) { return; } - for (int i=0; i<mSessions.size(); i++) { + for (int i = 0; i < mSessions.size(); i++) { if (mSessions.get(i).mResumed) { return; } @@ -449,7 +535,7 @@ public class ApplicationsState { if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock..."); synchronized (mEntriesMap) { if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock"); - for (int i=mAppEntries.size()-1; i>=0; i--) { + for (int i = mAppEntries.size() - 1; i >= 0; i--) { sum += mAppEntries.get(i).cacheSize; } if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock"); @@ -458,7 +544,7 @@ public class ApplicationsState { } int indexOfApplicationInfoLocked(String pkgName, int userId) { - for (int i=mApplications.size()-1; i>=0; i--) { + for (int i = mApplications.size() - 1; i >= 0; i--) { ApplicationInfo appInfo = mApplications.get(i); if (appInfo.packageName.equals(pkgName) && UserHandle.getUserId(appInfo.uid) == userId) { @@ -642,7 +728,7 @@ public class ApplicationsState { return; } mActiveSessions.clear(); - for (int i=0; i<mSessions.size(); i++) { + for (int i = 0; i < mSessions.size(); i++) { Session s = mSessions.get(i); if (s.mResumed) { mActiveSessions.add(new WeakReference<>(s)); @@ -784,7 +870,7 @@ public class ApplicationsState { ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>(); if (DEBUG) Log.i(TAG, "Rebuilding..."); - for (int i=0; i<apps.size(); i++) { + for (int i = 0; i < apps.size(); i++) { AppEntry entry = apps.get(i); if (entry != null && (filter == null || filter.filterApp(entry))) { synchronized (mEntriesMap) { @@ -954,7 +1040,7 @@ public class ApplicationsState { } } if (rebuildingSessions != null) { - for (int i=0; i<rebuildingSessions.size(); i++) { + for (int i = 0; i < rebuildingSessions.size(); i++) { rebuildingSessions.get(i).handleRebuildList(); } } @@ -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 @@ -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, - "MSG_LOAD_SIZES releasing: currently computing"); + if (DEBUG_LOCKING) { + Log.v(TAG, + "MSG_LOAD_SIZES releasing: currently computing"); + } return; } @@ -1181,8 +1269,10 @@ public class ApplicationsState { }); } - if (DEBUG_LOCKING) Log.v(TAG, - "MSG_LOAD_SIZES releasing: now computing"); + if (DEBUG_LOCKING) { + Log.v(TAG, + "MSG_LOAD_SIZES releasing: now computing"); + } return; } } @@ -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 - + ": " + entry.sizeStr); + if (DEBUG) { + Log.i(TAG, "Set size of " + entry.label + " " + entry + + ": " + entry.sizeStr); + } sizeChanged = true; } } @@ -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(); @@ -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(); } @@ -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); @@ -1504,6 +1606,7 @@ public class ApplicationsState { return compareResult; } } + return object1.info.uid - object2.info.uid; } }; @@ -1540,9 +1643,11 @@ public class ApplicationsState { public interface AppFilter { void init(); + default void init(Context context) { init(); } + boolean filterApp(AppEntry info); } @@ -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); } }; @@ -1707,7 +1813,7 @@ public class ApplicationsState { @Override public void init(Context context) { mHidePackageNames = context.getResources() - .getStringArray(R.array.config_hideWhenDisabled_packageNames); + .getStringArray(R.array.config_hideWhenDisabled_packageNames); } @Override @@ -1720,7 +1826,7 @@ public class ApplicationsState { if (!entry.info.enabled) { return false; } else if (entry.info.enabledSetting == - PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { return false; } } @@ -1798,7 +1904,7 @@ public class ApplicationsState { @Override public boolean filterApp(AppEntry entry) { boolean isMusicApp; - synchronized(entry) { + synchronized (entry) { isMusicApp = entry.info.category == ApplicationInfo.CATEGORY_AUDIO; } return isMusicApp; @@ -1813,7 +1919,7 @@ public class ApplicationsState { @Override public boolean filterApp(AppEntry entry) { boolean isMovieApp; - synchronized(entry) { + synchronized (entry) { isMovieApp = entry.info.category == ApplicationInfo.CATEGORY_VIDEO; } return isMovieApp; @@ -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) { @@ -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) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index a098ecc17b3d..b27efd0edc8b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -16,11 +16,17 @@ package com.android.settingslib.applications; +import static android.os.UserHandle.MU_ENABLED; + import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadow.api.Shadow.extract; @@ -40,10 +46,13 @@ import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.text.TextUtils; import android.util.IconDrawableFactory; @@ -70,6 +79,7 @@ import org.robolectric.shadows.ShadowContextImpl; import org.robolectric.shadows.ShadowLooper; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -82,6 +92,19 @@ public class ApplicationsStateRoboTest { private final static String HOME_PACKAGE_NAME = "com.android.home"; private final static String LAUNCHABLE_PACKAGE_NAME = "com.android.launchable"; + private static final int PROFILE_USERID = 10; + + private static final String PKG_1 = "PKG1"; + private static final int OWNER_UID_1 = 1001; + private static final int PROFILE_UID_1 = UserHandle.getUid(PROFILE_USERID, OWNER_UID_1); + + private static final String PKG_2 = "PKG2"; + private static final int OWNER_UID_2 = 1002; + private static final int PROFILE_UID_2 = UserHandle.getUid(PROFILE_USERID, OWNER_UID_2); + + private static final String PKG_3 = "PKG3"; + private static final int OWNER_UID_3 = 1003; + /** Class under test */ private ApplicationsState mApplicationsState; private Session mSession; @@ -171,7 +194,7 @@ public class ApplicationsStateRoboTest { storageStats.dataBytes = 20; storageStats.cacheBytes = 30; when(mStorageStatsManager.queryStatsForPackage(any(UUID.class), - anyString(), any(UserHandle.class))).thenReturn(storageStats); + anyString(), any(UserHandle.class))).thenReturn(storageStats); // Set up 3 installed apps, in which 1 is hidden module final List<ApplicationInfo> infos = new ArrayList<>(); @@ -195,11 +218,16 @@ public class ApplicationsStateRoboTest { } private ApplicationInfo createApplicationInfo(String packageName) { + return createApplicationInfo(packageName, 0); + } + + private ApplicationInfo createApplicationInfo(String packageName, int uid) { ApplicationInfo appInfo = new ApplicationInfo(); appInfo.sourceDir = "foo"; appInfo.flags |= ApplicationInfo.FLAG_INSTALLED; appInfo.storageUuid = UUID.randomUUID(); appInfo.packageName = packageName; + appInfo.uid = uid; return appInfo; } @@ -211,10 +239,14 @@ public class ApplicationsStateRoboTest { } private void addApp(String packageName, int id) { - ApplicationInfo appInfo = createApplicationInfo(packageName); + addApp(packageName, id, 0); + } + + private void addApp(String packageName, int id, int userId) { + ApplicationInfo appInfo = createApplicationInfo(packageName, id); AppEntry appEntry = createAppEntry(appInfo, id); mApplicationsState.mAppEntries.add(appEntry); - mApplicationsState.mEntriesMap.get(0).put(appInfo.packageName, appEntry); + mApplicationsState.mEntriesMap.get(userId).put(appInfo.packageName, appEntry); } private void processAllMessages() { @@ -351,4 +383,328 @@ public class ApplicationsStateRoboTest { assertThat(mApplications.get(1).packageName).isEqualTo("test.package.3"); } + @Test + public void removeAndInstall_noWorkprofile_doResumeIfNeededLocked_shouldClearEntries() + throws RemoteException { + // scenario: only owner user + // (PKG_1, PKG_2) -> (PKG_2, PKG_3) + // PKG_1 is removed and PKG_3 is installed before app is resumed. + ApplicationsState.sInstance = null; + mApplicationsState = spy( + ApplicationsState + .getInstance(RuntimeEnvironment.application, mock(IPackageManager.class))); + + // Previous Applications: + ApplicationInfo appInfo; + final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); + prevAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); + prevAppList.add(appInfo); + mApplicationsState.mApplications = prevAppList; + + // Previous Entries: + // (PKG_1, PKG_2) + addApp(PKG_1, OWNER_UID_1, 0); + addApp(PKG_2, OWNER_UID_2, 0); + + // latest Applications: + // (PKG_2, PKG_3) + final ArrayList<ApplicationInfo> appList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); + appList.add(appInfo); + appInfo = createApplicationInfo(PKG_3, OWNER_UID_3); + appList.add(appInfo); + setupDoResumeIfNeededLocked(appList, null); + + mApplicationsState.doResumeIfNeededLocked(); + + verify(mApplicationsState).clearEntries(); + } + + @Test + public void noAppRemoved_noWorkprofile_doResumeIfNeededLocked_shouldNotClearEntries() + throws RemoteException { + // scenario: only owner user + // (PKG_1, PKG_2) + ApplicationsState.sInstance = null; + mApplicationsState = spy( + ApplicationsState + .getInstance(RuntimeEnvironment.application, mock(IPackageManager.class))); + + ApplicationInfo appInfo; + // Previous Applications + final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); + prevAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); + prevAppList.add(appInfo); + mApplicationsState.mApplications = prevAppList; + + // Previous Entries: + // (pk1, PKG_2) + addApp(PKG_1, OWNER_UID_1, 0); + addApp(PKG_2, OWNER_UID_2, 0); + + // latest Applications: + // (PKG_2, PKG_3) + final ArrayList<ApplicationInfo> appList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); + appList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); + appList.add(appInfo); + setupDoResumeIfNeededLocked(appList, null); + + mApplicationsState.doResumeIfNeededLocked(); + + verify(mApplicationsState, never()).clearEntries(); + } + + @Test + public void removeProfileApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries() + throws RemoteException { + if (!MU_ENABLED) { + return; + } + // [Preconditions] + // 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state + // 2 apps (PKG_1, PKG_2) for non-owner. + // + // [Actions] + // profile user's PKG_2 is removed before resume + // + // Applications: + // owner - (PKG_1 - uninstalled, PKG_2) -> (PKG_1 - uninstalled, PKG_2) + // profile - (PKG_1, PKG_2) -> (PKG_1) + // + // Previous Entries: + // owner - (PKG_2) + // profile - (PKG_1, PKG_2) + + ShadowUserManager shadowUserManager = Shadow + .extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); + shadowUserManager.addProfile(PROFILE_USERID, "profile"); + + ApplicationsState.sInstance = null; + mApplicationsState = spy( + ApplicationsState + .getInstance(RuntimeEnvironment.application, mock(IPackageManager.class))); + + ApplicationInfo appInfo; + // Previous Applications + // owner - (PKG_1 - uninstalled, PKG_2) + // profile - (PKG_1, PKG_2) + final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); + appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; + prevAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); + prevAppList.add(appInfo); + + appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); + prevAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2); + prevAppList.add(appInfo); + + mApplicationsState.mApplications = prevAppList; + // Previous Entries: + // owner (PKG_2), profile (pk1, PKG_2) + // PKG_1 is not installed for owner, hence it's removed from entries + addApp(PKG_2, OWNER_UID_2, 0); + addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID); + addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID); + + // latest Applications: + // owner (PKG_1, PKG_2), profile (PKG_1) + // owner's PKG_1 is still listed and is in non-installed state + // profile user's PKG_2 is removed by a user before resume + //owner + final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); + appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; + ownerAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); + ownerAppList.add(appInfo); + //profile + appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); + setupDoResumeIfNeededLocked(ownerAppList, new ArrayList<>(Arrays.asList(appInfo))); + + mApplicationsState.doResumeIfNeededLocked(); + + verify(mApplicationsState).clearEntries(); + } + + @Test + public void removeOwnerApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries() + throws RemoteException { + if (!MU_ENABLED) { + return; + } + // [Preconditions] + // 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state + // 2 apps (PKG_1, PKG_2) for non-owner. + // + // [Actions] + // Owner user's PKG_2 is removed before resume + // + // Applications: + // owner - (PKG_1 - uninstalled, PKG_2) -> (PKG_1 - uninstalled, PKG_2 - uninstalled) + // profile - (PKG_1, PKG_2) -> (PKG_1, PKG_2) + // + // Previous Entries: + // owner - (PKG_2) + // profile - (PKG_1, PKG_2) + + ShadowUserManager shadowUserManager = Shadow + .extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); + shadowUserManager.addProfile(PROFILE_USERID, "profile"); + + ApplicationsState.sInstance = null; + mApplicationsState = spy( + ApplicationsState + .getInstance(RuntimeEnvironment.application, mock(IPackageManager.class))); + + ApplicationInfo appInfo; + // Previous Applications: + // owner - (PKG_1 - uninstalled, PKG_2) + // profile - (PKG_1, PKG_2) + final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); + appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; + prevAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); + prevAppList.add(appInfo); + + appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); + prevAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2); + prevAppList.add(appInfo); + + mApplicationsState.mApplications = prevAppList; + + // Previous Entries: + // owner (PKG_2), profile (pk1, PKG_2) + // PKG_1 is not installed for owner, hence it's removed from entries + addApp(PKG_2, OWNER_UID_2, 0); + addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID); + addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID); + + // latest Applications: + // owner (PKG_1 - uninstalled, PKG_2 - uninstalled), profile (PKG_1, PKG_2) + // owner's PKG_1, PKG_2 is still listed and is in non-installed state + // profile user's PKG_2 is removed before resume + //owner + final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); + appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; + ownerAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); + appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; + ownerAppList.add(appInfo); + + //profile + final ArrayList<ApplicationInfo> profileAppList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); + profileAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2); + profileAppList.add(appInfo); + setupDoResumeIfNeededLocked(ownerAppList, profileAppList); + + mApplicationsState.doResumeIfNeededLocked(); + + verify(mApplicationsState).clearEntries(); + } + + @Test + public void noAppRemoved_workprofileExists_doResumeIfNeededLocked_shouldNotClearEntries() + throws RemoteException { + if (!MU_ENABLED) { + return; + } + // [Preconditions] + // 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state + // 2 apps (PKG_1, PKG_2) for non-owner. + // + // Applications: + // owner - (PKG_1 - uninstalled, PKG_2) + // profile - (PKG_1, PKG_2) + // + // Previous Entries: + // owner - (PKG_2) + // profile - (PKG_1, PKG_2) + + ShadowUserManager shadowUserManager = Shadow + .extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); + shadowUserManager.addProfile(PROFILE_USERID, "profile"); + + ApplicationsState.sInstance = null; + mApplicationsState = spy( + ApplicationsState + .getInstance(RuntimeEnvironment.application, mock(IPackageManager.class))); + + ApplicationInfo appInfo; + // Previous Applications: + // owner - (PKG_1 - uninstalled, PKG_2) + // profile - (PKG_1, PKG_2) + final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); + appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; + prevAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); + prevAppList.add(appInfo); + + appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); + prevAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2); + prevAppList.add(appInfo); + + mApplicationsState.mApplications = prevAppList; + // Previous Entries: + // owner (PKG_2), profile (pk1, PKG_2) + // PKG_1 is not installed for owner, hence it's removed from entries + addApp(PKG_2, OWNER_UID_2, 0); + addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID); + addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID); + + // latest Applications: + // owner (PKG_1 - uninstalled, PKG_2), profile (PKG_1, PKG_2) + // owner's PKG_1 is still listed and is in non-installed state + + // owner + final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); + appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; + ownerAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); + ownerAppList.add(appInfo); + + // profile + final ArrayList<ApplicationInfo> profileAppList = new ArrayList<>(); + appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); + profileAppList.add(appInfo); + appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2); + profileAppList.add(appInfo); + setupDoResumeIfNeededLocked(ownerAppList, profileAppList); + + mApplicationsState.doResumeIfNeededLocked(); + + verify(mApplicationsState, never()).clearEntries(); + } + + private void setupDoResumeIfNeededLocked(ArrayList<ApplicationInfo> ownerApps, + ArrayList<ApplicationInfo> profileApps) + throws RemoteException { + + if (ownerApps != null) { + when(mApplicationsState.mIpm.getInstalledApplications(anyInt(), eq(0))) + .thenReturn(new ParceledListSlice<>(ownerApps)); + } + if (profileApps != null) { + when(mApplicationsState.mIpm.getInstalledApplications(anyInt(), eq(PROFILE_USERID))) + .thenReturn(new ParceledListSlice<>(profileApps)); + } + final InterestingConfigChanges configChanges = mock(InterestingConfigChanges.class); + when(configChanges.applyNewConfig(any(Resources.class))).thenReturn(false); + mApplicationsState.setInterestingConfigChanges(configChanges); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java index c50d646c0861..ca1eefcad7de 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java @@ -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) { @@ -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 |