Show apps from user and all of its profiles in recents category.

Previously only personal and work profile apps were displayed.

Bug: 259627902
Test: make RunSettingsRoboTests ROBOTEST_FILTER=RecentAppStatsMixinTest
Change-Id: Id975534343dab3c816a6a18d53ba639a74ff81b0
This commit is contained in:
“Ankita
2022-11-18 05:25:46 +00:00
parent 15066fe36e
commit 4c2ef22a81
2 changed files with 120 additions and 47 deletions

View File

@@ -34,7 +34,6 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.android.settings.Utils;
import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -49,6 +48,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; 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 * 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 int mMaximumApps;
private final Context mContext; private final Context mContext;
private final PackageManager mPm; private final PackageManager mPm;
private final UserManager mUserManager;
private final PowerManager mPowerManager; private final PowerManager mPowerManager;
private final int mWorkUserId;
private final UsageStatsManager mPersonalUsageStatsManager;
private final Optional<UsageStatsManager> mWorkUsageStatsManager;
private final ApplicationsState mApplicationsState; private final ApplicationsState mApplicationsState;
private final List<RecentAppStatsListener> mAppStatsListeners; private final List<RecentAppStatsListener> mAppStatsListeners;
private Calendar mCalendar; private Calendar mCalendar;
@@ -89,13 +87,7 @@ public class RecentAppStatsMixin implements LifecycleObserver, OnStart {
mMaximumApps = maximumApps; mMaximumApps = maximumApps;
mPm = mContext.getPackageManager(); mPm = mContext.getPackageManager();
mPowerManager = mContext.getSystemService(PowerManager.class); mPowerManager = mContext.getSystemService(PowerManager.class);
final UserManager userManager = mContext.getSystemService(UserManager.class); mUserManager = 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));
mApplicationsState = ApplicationsState.getInstance( mApplicationsState = ApplicationsState.getInstance(
(Application) mContext.getApplicationContext()); (Application) mContext.getApplicationContext());
mRecentApps = new ArrayList<>(); mRecentApps = new ArrayList<>();
@@ -122,34 +114,34 @@ public class RecentAppStatsMixin implements LifecycleObserver, OnStart {
mCalendar = Calendar.getInstance(); mCalendar = Calendar.getInstance();
mCalendar.add(Calendar.DAY_OF_YEAR, -1); mCalendar.add(Calendar.DAY_OF_YEAR, -1);
final int personalUserId = UserHandle.myUserId(); List<UsageStatsWrapper> usageStatsAllUsers = new ArrayList<>();
final List<UsageStats> personalStats =
getRecentAppsStats(mPersonalUsageStatsManager, personalUserId);
final List<UsageStats> workStats = mWorkUsageStatsManager
.map(statsManager -> getRecentAppsStats(statsManager, mWorkUserId))
.orElse(new ArrayList<>());
// Both lists are already sorted, so we can create a sorted merge in linear time List<UserHandle> profiles = mUserManager.getUserProfiles();
int personal = 0; for (UserHandle userHandle : profiles) {
int work = 0; int userId = userHandle.getIdentifier();
while (personal < personalStats.size() && work < workStats.size()
&& mRecentApps.size() < limit) { final Optional<UsageStatsManager> usageStatsManager;
UsageStats currentPersonal = personalStats.get(personal); if (userHandle.getIdentifier() == UserHandle.myUserId()) {
UsageStats currentWork = workStats.get(work); usageStatsManager = Optional.ofNullable(userHandle).map(
if (currentPersonal.getLastTimeUsed() > currentWork.getLastTimeUsed()) { handle -> mContext.getSystemService(UsageStatsManager.class));
mRecentApps.add(new UsageStatsWrapper(currentPersonal, personalUserId));
personal++;
} else { } else {
mRecentApps.add(new UsageStatsWrapper(currentWork, mWorkUserId)); usageStatsManager = Optional.ofNullable(userHandle).map(
work++; handle -> mContext.createContextAsUser(handle, /* flags */ 0)
.getSystemService(UsageStatsManager.class));
} }
List<UsageStats> 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)); // Sort apps by latest timestamp.
} usageStatsAllUsers.sort(
while (work < workStats.size() && mRecentApps.size() < limit) { Comparator.comparingLong(a -> -1 * a.mUsageStats.getLastTimeUsed()));
mRecentApps.add(new UsageStatsWrapper(workStats.get(work++), mWorkUserId)); mRecentApps.addAll(usageStatsAllUsers.stream().limit(limit).collect(Collectors.toList()));
}
} }
private List<UsageStats> getRecentAppsStats(UsageStatsManager usageStatsManager, int userId) { private List<UsageStats> getRecentAppsStats(UsageStatsManager usageStatsManager, int userId) {

View File

@@ -53,12 +53,16 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class RecentAppStatsMixinTest { 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 @Mock
private UsageStatsManager mUsageStatsManager; private UsageStatsManager mUsageStatsManager;
@Mock @Mock
@@ -75,24 +79,33 @@ public class RecentAppStatsMixinTest {
private ApplicationInfo mApplicationInfo; private ApplicationInfo mApplicationInfo;
@Mock @Mock
private PowerManager mPowerManager; private PowerManager mPowerManager;
@Mock
private UsageStatsManager mCloneUsageStatsManager;
@Mock
Context mMockContext;
private Context mContext;
private RecentAppStatsMixin mRecentAppStatsMixin; private RecentAppStatsMixin mRecentAppStatsMixin;
@Before @Before
public void setUp() { public void setUp() throws PackageManager.NameNotFoundException {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
final Context context = spy(RuntimeEnvironment.application); mContext = spy(RuntimeEnvironment.application);
when(context.getApplicationContext()).thenReturn(context); when(mContext.getApplicationContext()).thenReturn(mContext);
ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", mAppState); ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", mAppState);
doReturn(mUsageStatsManager).when(context).getSystemService(Context.USAGE_STATS_SERVICE); doReturn(mUsageStatsManager).when(mContext).getSystemService(UsageStatsManager.class);
doReturn(mUserManager).when(context).getSystemService(Context.USER_SERVICE); doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE);
doReturn(mPackageManager).when(context).getPackageManager(); doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(mPowerManager).when(context).getSystemService(PowerManager.class); doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{}); when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{});
mRecentAppStatsMixin = new RecentAppStatsMixin(context, 3 /* maximumApps */); doReturn(mMockContext).when(mContext).createContextAsUser(any(), anyInt());
ReflectionHelpers.setField(mRecentAppStatsMixin, "mWorkUsageStatsManager", doReturn(mMockContext).when(mContext).createPackageContextAsUser(any(), anyInt(), any());
Optional.of(mWorkUsageStatsManager)); when(mUserManager.getUserProfiles())
.thenReturn(new ArrayList<>(Arrays.asList(NORMAL_USER)));
mRecentAppStatsMixin = new RecentAppStatsMixin(mContext, 3 /* maximumApps */);
} }
@Test @Test
@@ -336,6 +349,10 @@ public class RecentAppStatsMixinTest {
.thenReturn(mAppEntry); .thenReturn(mAppEntry);
when(mPackageManager.resolveActivityAsUser(any(Intent.class), anyInt(), anyInt())) when(mPackageManager.resolveActivityAsUser(any(Intent.class), anyInt(), anyInt()))
.thenReturn(new ResolveInfo()); .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 // personal app stats
when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong()))
.thenReturn(personalStats); .thenReturn(personalStats);
@@ -356,7 +373,8 @@ public class RecentAppStatsMixinTest {
} }
@Test @Test
public void loadDisplayableRecentApps_usePersonalAndWorkApps_shouldBeUniquePerProfile() { public void loadDisplayableRecentApps_usePersonalAndWorkApps_shouldBeUniquePerProfile()
throws PackageManager.NameNotFoundException {
final String firstAppPackageName = "app1.pkg.class"; final String firstAppPackageName = "app1.pkg.class";
final String secondAppPackageName = "app2.pkg.class"; final String secondAppPackageName = "app2.pkg.class";
final List<UsageStats> personalStats = new ArrayList<>(); final List<UsageStats> personalStats = new ArrayList<>();
@@ -383,6 +401,10 @@ public class RecentAppStatsMixinTest {
when(mAppState.getEntry(anyString(), anyInt())) when(mAppState.getEntry(anyString(), anyInt()))
.thenReturn(mAppEntry); .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())) when(mPackageManager.resolveActivityAsUser(any(Intent.class), anyInt(), anyInt()))
.thenReturn(new ResolveInfo()); .thenReturn(new ResolveInfo());
// personal app stats // personal app stats
@@ -405,4 +427,63 @@ public class RecentAppStatsMixinTest {
assertThat(mRecentAppStatsMixin.mRecentApps.get(2).mUsageStats.mPackageName).isEqualTo( assertThat(mRecentAppStatsMixin.mRecentApps.get(2).mUsageStats.mPackageName).isEqualTo(
secondAppPackageName); secondAppPackageName);
} }
@Test
public void loadDisplayableRecentApps_multipleProfileApps_shouldBeSortedByLastTimeUse()
throws PackageManager.NameNotFoundException {
final List<UsageStats> 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<UsageStats> workStats = new ArrayList<>();
final UsageStats stat3 = new UsageStats();
stat3.mLastTimeUsed = System.currentTimeMillis() - 2000;
stat3.mPackageName = "work.pkg.class3";
workStats.add(stat3);
final List<UsageStats> 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");
}
} }