summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java172
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java362
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java21
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