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:
@@ -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) {
|
||||||
|
@@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user