diff --git a/src/com/android/settings/applications/RecentAppStatsMixin.java b/src/com/android/settings/applications/RecentAppStatsMixin.java index 03b22034dd1..6705b256e64 100644 --- a/src/com/android/settings/applications/RecentAppStatsMixin.java +++ b/src/com/android/settings/applications/RecentAppStatsMixin.java @@ -34,7 +34,6 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import com.android.settings.Utils; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.core.lifecycle.LifecycleObserver; @@ -49,6 +48,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; /** * A helper class that loads recent app data in the background and sends it in a callback to a @@ -65,10 +65,8 @@ public class RecentAppStatsMixin implements LifecycleObserver, OnStart { private final int mMaximumApps; private final Context mContext; private final PackageManager mPm; + private final UserManager mUserManager; private final PowerManager mPowerManager; - private final int mWorkUserId; - private final UsageStatsManager mPersonalUsageStatsManager; - private final Optional mWorkUsageStatsManager; private final ApplicationsState mApplicationsState; private final List mAppStatsListeners; private Calendar mCalendar; @@ -89,13 +87,7 @@ public class RecentAppStatsMixin implements LifecycleObserver, OnStart { mMaximumApps = maximumApps; mPm = mContext.getPackageManager(); mPowerManager = mContext.getSystemService(PowerManager.class); - final UserManager userManager = mContext.getSystemService(UserManager.class); - mWorkUserId = Utils.getManagedProfileId(userManager, UserHandle.myUserId()); - mPersonalUsageStatsManager = mContext.getSystemService(UsageStatsManager.class); - final UserHandle workUserHandle = Utils.getManagedProfile(userManager); - mWorkUsageStatsManager = Optional.ofNullable(workUserHandle).map( - handle -> mContext.createContextAsUser(handle, /* flags */ 0) - .getSystemService(UsageStatsManager.class)); + mUserManager = mContext.getSystemService(UserManager.class); mApplicationsState = ApplicationsState.getInstance( (Application) mContext.getApplicationContext()); mRecentApps = new ArrayList<>(); @@ -122,34 +114,34 @@ public class RecentAppStatsMixin implements LifecycleObserver, OnStart { mCalendar = Calendar.getInstance(); mCalendar.add(Calendar.DAY_OF_YEAR, -1); - final int personalUserId = UserHandle.myUserId(); - final List personalStats = - getRecentAppsStats(mPersonalUsageStatsManager, personalUserId); - final List workStats = mWorkUsageStatsManager - .map(statsManager -> getRecentAppsStats(statsManager, mWorkUserId)) - .orElse(new ArrayList<>()); + List usageStatsAllUsers = new ArrayList<>(); - // Both lists are already sorted, so we can create a sorted merge in linear time - int personal = 0; - int work = 0; - while (personal < personalStats.size() && work < workStats.size() - && mRecentApps.size() < limit) { - UsageStats currentPersonal = personalStats.get(personal); - UsageStats currentWork = workStats.get(work); - if (currentPersonal.getLastTimeUsed() > currentWork.getLastTimeUsed()) { - mRecentApps.add(new UsageStatsWrapper(currentPersonal, personalUserId)); - personal++; + List profiles = mUserManager.getUserProfiles(); + for (UserHandle userHandle : profiles) { + int userId = userHandle.getIdentifier(); + + final Optional usageStatsManager; + if (userHandle.getIdentifier() == UserHandle.myUserId()) { + usageStatsManager = Optional.ofNullable(userHandle).map( + handle -> mContext.getSystemService(UsageStatsManager.class)); } else { - mRecentApps.add(new UsageStatsWrapper(currentWork, mWorkUserId)); - work++; + usageStatsManager = Optional.ofNullable(userHandle).map( + handle -> mContext.createContextAsUser(handle, /* flags */ 0) + .getSystemService(UsageStatsManager.class)); } + + List profileStats = usageStatsManager + .map(statsManager -> getRecentAppsStats(statsManager, userId)) + .orElse(new ArrayList<>()); + usageStatsAllUsers.addAll(profileStats.stream() + .map(usageStats-> new UsageStatsWrapper(usageStats, userId)) + .collect(Collectors.toList())); } - while (personal < personalStats.size() && mRecentApps.size() < limit) { - mRecentApps.add(new UsageStatsWrapper(personalStats.get(personal++), personalUserId)); - } - while (work < workStats.size() && mRecentApps.size() < limit) { - mRecentApps.add(new UsageStatsWrapper(workStats.get(work++), mWorkUserId)); - } + + // Sort apps by latest timestamp. + usageStatsAllUsers.sort( + Comparator.comparingLong(a -> -1 * a.mUsageStats.getLastTimeUsed())); + mRecentApps.addAll(usageStatsAllUsers.stream().limit(limit).collect(Collectors.toList())); } private List getRecentAppsStats(UsageStatsManager usageStatsManager, int userId) { diff --git a/tests/robotests/src/com/android/settings/applications/RecentAppStatsMixinTest.java b/tests/robotests/src/com/android/settings/applications/RecentAppStatsMixinTest.java index 6b94bcef926..4fb0c0501fd 100644 --- a/tests/robotests/src/com/android/settings/applications/RecentAppStatsMixinTest.java +++ b/tests/robotests/src/com/android/settings/applications/RecentAppStatsMixinTest.java @@ -53,12 +53,16 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.Optional; @RunWith(RobolectricTestRunner.class) public class RecentAppStatsMixinTest { + private static final UserHandle NORMAL_USER = UserHandle.SYSTEM; + private static final UserHandle CLONE_USER = new UserHandle(2222); + private static final UserHandle WORK_USER = new UserHandle(3333); + @Mock private UsageStatsManager mUsageStatsManager; @Mock @@ -75,24 +79,33 @@ public class RecentAppStatsMixinTest { private ApplicationInfo mApplicationInfo; @Mock private PowerManager mPowerManager; + @Mock + private UsageStatsManager mCloneUsageStatsManager; + @Mock + Context mMockContext; + + private Context mContext; private RecentAppStatsMixin mRecentAppStatsMixin; @Before - public void setUp() { + public void setUp() throws PackageManager.NameNotFoundException { MockitoAnnotations.initMocks(this); - final Context context = spy(RuntimeEnvironment.application); - when(context.getApplicationContext()).thenReturn(context); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getApplicationContext()).thenReturn(mContext); ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", mAppState); - doReturn(mUsageStatsManager).when(context).getSystemService(Context.USAGE_STATS_SERVICE); - doReturn(mUserManager).when(context).getSystemService(Context.USER_SERVICE); - doReturn(mPackageManager).when(context).getPackageManager(); - doReturn(mPowerManager).when(context).getSystemService(PowerManager.class); + doReturn(mUsageStatsManager).when(mContext).getSystemService(UsageStatsManager.class); + doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE); + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{}); - mRecentAppStatsMixin = new RecentAppStatsMixin(context, 3 /* maximumApps */); - ReflectionHelpers.setField(mRecentAppStatsMixin, "mWorkUsageStatsManager", - Optional.of(mWorkUsageStatsManager)); + doReturn(mMockContext).when(mContext).createContextAsUser(any(), anyInt()); + doReturn(mMockContext).when(mContext).createPackageContextAsUser(any(), anyInt(), any()); + when(mUserManager.getUserProfiles()) + .thenReturn(new ArrayList<>(Arrays.asList(NORMAL_USER))); + + mRecentAppStatsMixin = new RecentAppStatsMixin(mContext, 3 /* maximumApps */); } @Test @@ -336,6 +349,10 @@ public class RecentAppStatsMixinTest { .thenReturn(mAppEntry); when(mPackageManager.resolveActivityAsUser(any(Intent.class), anyInt(), anyInt())) .thenReturn(new ResolveInfo()); + when(mUserManager.getUserProfiles()) + .thenReturn(new ArrayList<>(Arrays.asList(NORMAL_USER, WORK_USER))); + when(mMockContext.getSystemService(UsageStatsManager.class)) + .thenReturn(mWorkUsageStatsManager); // personal app stats when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) .thenReturn(personalStats); @@ -356,7 +373,8 @@ public class RecentAppStatsMixinTest { } @Test - public void loadDisplayableRecentApps_usePersonalAndWorkApps_shouldBeUniquePerProfile() { + public void loadDisplayableRecentApps_usePersonalAndWorkApps_shouldBeUniquePerProfile() + throws PackageManager.NameNotFoundException { final String firstAppPackageName = "app1.pkg.class"; final String secondAppPackageName = "app2.pkg.class"; final List personalStats = new ArrayList<>(); @@ -383,6 +401,10 @@ public class RecentAppStatsMixinTest { when(mAppState.getEntry(anyString(), anyInt())) .thenReturn(mAppEntry); + when(mUserManager.getUserProfiles()) + .thenReturn(new ArrayList<>(Arrays.asList(NORMAL_USER, WORK_USER))); + when(mMockContext.getSystemService(UsageStatsManager.class)) + .thenReturn(mWorkUsageStatsManager); when(mPackageManager.resolveActivityAsUser(any(Intent.class), anyInt(), anyInt())) .thenReturn(new ResolveInfo()); // personal app stats @@ -405,4 +427,63 @@ public class RecentAppStatsMixinTest { assertThat(mRecentAppStatsMixin.mRecentApps.get(2).mUsageStats.mPackageName).isEqualTo( secondAppPackageName); } + + @Test + public void loadDisplayableRecentApps_multipleProfileApps_shouldBeSortedByLastTimeUse() + throws PackageManager.NameNotFoundException { + final List personalStats = new ArrayList<>(); + final UsageStats stats1 = new UsageStats(); + final UsageStats stats2 = new UsageStats(); + stats1.mLastTimeUsed = System.currentTimeMillis(); + stats1.mPackageName = "personal.pkg.class"; + personalStats.add(stats1); + + stats2.mLastTimeUsed = System.currentTimeMillis() - 5000; + stats2.mPackageName = "personal.pkg.class2"; + personalStats.add(stats2); + + final List workStats = new ArrayList<>(); + final UsageStats stat3 = new UsageStats(); + stat3.mLastTimeUsed = System.currentTimeMillis() - 2000; + stat3.mPackageName = "work.pkg.class3"; + workStats.add(stat3); + + final List cloneStats = new ArrayList<>(); + final UsageStats stat4 = new UsageStats(); + stat4.mLastTimeUsed = System.currentTimeMillis() - 1000; + stat4.mPackageName = "clone.pkg.class4"; + cloneStats.add(stat4); + + when(mAppState.getEntry(anyString(), anyInt())) + .thenReturn(mAppEntry); + when(mUserManager.getUserProfiles()) + .thenReturn(new ArrayList<>(Arrays.asList(NORMAL_USER, CLONE_USER, WORK_USER))); + when(mMockContext.getSystemService(UsageStatsManager.class)) + .thenReturn(mCloneUsageStatsManager, mWorkUsageStatsManager); + when(mPackageManager.resolveActivityAsUser(any(Intent.class), anyInt(), anyInt())) + .thenReturn(new ResolveInfo()); + // personal app stats + when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(personalStats); + // work app stats + when(mWorkUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(workStats); + // clone app stats + when(mCloneUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(cloneStats); + + mAppEntry.info = mApplicationInfo; + + mRecentAppStatsMixin.loadDisplayableRecentApps(4); + + assertThat(mRecentAppStatsMixin.mRecentApps.size()).isEqualTo(4); + assertThat(mRecentAppStatsMixin.mRecentApps.get(0).mUsageStats.mPackageName).isEqualTo( + "personal.pkg.class"); + assertThat(mRecentAppStatsMixin.mRecentApps.get(1).mUsageStats.mPackageName).isEqualTo( + "clone.pkg.class4"); + assertThat(mRecentAppStatsMixin.mRecentApps.get(2).mUsageStats.mPackageName).isEqualTo( + "work.pkg.class3"); + assertThat(mRecentAppStatsMixin.mRecentApps.get(3).mUsageStats.mPackageName).isEqualTo( + "personal.pkg.class2"); + } }