diff --git a/res/xml/app_and_notification.xml b/res/xml/app_and_notification.xml index c15df75afdc..518379ac86e 100644 --- a/res/xml/app_and_notification.xml +++ b/res/xml/app_and_notification.xml @@ -23,24 +23,25 @@ settings:initialExpandedChildrenCount="8"> - - + - - - + + android:key="recent_apps_divider" + android:order="-997"/> createPreferenceControllers(Context context) { - final Activity activity = getActivity(); - final Application app; - if (activity != null) { - app = activity.getApplication(); - } else { - app = null; - } - return buildPreferenceControllers(context, app, this); + return buildPreferenceControllers(context); } - private static List buildPreferenceControllers(Context context, - Application app, Fragment host) { + private static List buildPreferenceControllers(Context context) { final List controllers = new ArrayList<>(); controllers.add(new EmergencyBroadcastPreferenceController(context, "app_and_notif_cell_broadcast_settings")); - controllers.add(new RecentAppsPreferenceController(context, app, host)); return controllers; } @@ -100,7 +89,7 @@ public class AppAndNotificationDashboardFragment extends DashboardFragment { @Override public List createPreferenceControllers( Context context) { - return buildPreferenceControllers(context, null, null /* host */); + return buildPreferenceControllers(context); } }; } diff --git a/src/com/android/settings/applications/RecentAppsPreferenceController.java b/src/com/android/settings/applications/RecentAppsPreferenceController.java index 6e0ae454913..fe52a8b92f5 100644 --- a/src/com/android/settings/applications/RecentAppsPreferenceController.java +++ b/src/com/android/settings/applications/RecentAppsPreferenceController.java @@ -27,26 +27,28 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.PowerManager; import android.os.UserHandle; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.IconDrawableFactory; import android.util.Log; +import android.view.View; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.applications.appinfo.AppInfoDashboardFragment; -import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.applications.manageapplications.ManageApplications; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.SubSettingLauncher; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; -import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.utils.StringUtil; -import com.android.settingslib.widget.apppreference.AppPreference; +import com.android.settingslib.widget.AppEntitiesHeaderController; +import com.android.settingslib.widget.AppEntityInfo; +import com.android.settingslib.widget.LayoutPreference; import java.util.ArrayList; import java.util.Arrays; @@ -58,22 +60,29 @@ import java.util.Map; import java.util.Set; /** - * This controller displays a list of recently used apps and a "See all" button. If there is - * no recently used app, "See all" will be displayed as "App info". + * This controller displays up to three recently used apps. + * If there is no recently used app, we only show up an "App Info" preference. */ -public class RecentAppsPreferenceController extends AbstractPreferenceController - implements PreferenceControllerMixin, Comparator { +public class RecentAppsPreferenceController extends BasePreferenceController + implements Comparator { + + @VisibleForTesting + static final String KEY_ALL_APP_INFO = "all_app_info"; + @VisibleForTesting + static final String KEY_DIVIDER = "recent_apps_divider"; private static final String TAG = "RecentAppsCtrl"; - private static final String KEY_PREF_CATEGORY = "recent_apps_category"; - @VisibleForTesting - static final String KEY_DIVIDER = "all_app_info_divider"; - @VisibleForTesting - static final String KEY_SEE_ALL = "all_app_info"; - private static final int SHOW_RECENT_APP_COUNT = 5; private static final Set SKIP_SYSTEM_PACKAGES = new ArraySet<>(); - private final Fragment mHost; + @VisibleForTesting + AppEntitiesHeaderController mAppEntitiesController; + @VisibleForTesting + LayoutPreference mRecentAppsPreference; + @VisibleForTesting + Preference mAllAppPref; + @VisibleForTesting + Preference mDivider; + private final PackageManager mPm; private final UsageStatsManager mUsageStatsManager; private final ApplicationsState mApplicationsState; @@ -81,12 +90,9 @@ public class RecentAppsPreferenceController extends AbstractPreferenceController private final IconDrawableFactory mIconDrawableFactory; private final PowerManager mPowerManager; + private Fragment mHost; private Calendar mCal; private List mStats; - - private PreferenceCategory mCategory; - private Preference mSeeAllPref; - private Preference mDivider; private boolean mHasRecentApps; static { @@ -100,68 +106,67 @@ public class RecentAppsPreferenceController extends AbstractPreferenceController )); } - public RecentAppsPreferenceController(Context context, Application app, Fragment host) { - this(context, app == null ? null : ApplicationsState.getInstance(app), host); - } - - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - RecentAppsPreferenceController(Context context, ApplicationsState appState, Fragment host) { - super(context); - mIconDrawableFactory = IconDrawableFactory.newInstance(context); + public RecentAppsPreferenceController(Context context, String key) { + super(context, key); + mApplicationsState = ApplicationsState.getInstance( + (Application) mContext.getApplicationContext()); mUserId = UserHandle.myUserId(); - mPm = context.getPackageManager(); - mPowerManager = context.getSystemService(PowerManager.class); + mPm = mContext.getPackageManager(); + mIconDrawableFactory = IconDrawableFactory.newInstance(mContext); + mPowerManager = mContext.getSystemService(PowerManager.class); + mUsageStatsManager = mContext.getSystemService(UsageStatsManager.class); + } - mHost = host; - mUsageStatsManager = - (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); - mApplicationsState = appState; + public void setFragment(Fragment fragment) { + mHost = fragment; } @Override - public boolean isAvailable() { - return true; - } - - @Override - public String getPreferenceKey() { - return KEY_PREF_CATEGORY; - } - - @Override - public void updateNonIndexableKeys(List keys) { - PreferenceControllerMixin.super.updateNonIndexableKeys(keys); - // Don't index category name into search. It's not actionable. - keys.add(KEY_PREF_CATEGORY); - keys.add(KEY_DIVIDER); + public int getAvailabilityStatus() { + reloadData(); + return getDisplayableRecentAppList().isEmpty() ? AVAILABLE_UNSEARCHABLE : AVAILABLE; } @Override public void displayPreference(PreferenceScreen screen) { - mCategory = screen.findPreference(getPreferenceKey()); - mSeeAllPref = screen.findPreference(KEY_SEE_ALL); - mDivider = screen.findPreference(KEY_DIVIDER); super.displayPreference(screen); - refreshUi(mCategory.getContext()); + + mAllAppPref = screen.findPreference(KEY_ALL_APP_INFO); + mDivider = screen.findPreference(KEY_DIVIDER); + mRecentAppsPreference = (LayoutPreference) screen.findPreference(getPreferenceKey()); + final View view = mRecentAppsPreference.findViewById(R.id.app_entities_header); + mAppEntitiesController = AppEntitiesHeaderController.newInstance(mContext, view) + .setHeaderTitleRes(R.string.recent_app_category_title) + .setHeaderDetailsClickListener((View v) -> { + new SubSettingLauncher(mContext) + .setDestination(ManageApplications.class.getName()) + .setArguments(null /* arguments */) + .setTitleRes(R.string.application_info_label) + .setSourceMetricsCategory(SettingsEnums.SETTINGS_APP_NOTIF_CATEGORY) + .launch(); + }); + + refreshUi(); } @Override public void updateState(Preference preference) { super.updateState(preference); - refreshUi(mCategory.getContext()); + refreshUi(); // Show total number of installed apps as See all's summary. new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON, mContext.getPackageManager()) { @Override protected void onCountComplete(int num) { if (mHasRecentApps) { - mSeeAllPref.setTitle(mContext.getString(R.string.see_all_apps_title, num)); + mAppEntitiesController.setHeaderDetails( + mContext.getString(R.string.see_all_apps_title, num)); + mAppEntitiesController.apply(); } else { - mSeeAllPref.setSummary(mContext.getString(R.string.apps_summary, num)); + mAllAppPref.setSummary(mContext.getString(R.string.apps_summary, num)); } } }.execute(); - } @Override @@ -171,12 +176,12 @@ public class RecentAppsPreferenceController extends AbstractPreferenceController } @VisibleForTesting - void refreshUi(Context prefContext) { + void refreshUi() { reloadData(); final List recentApps = getDisplayableRecentAppList(); if (recentApps != null && !recentApps.isEmpty()) { mHasRecentApps = true; - displayRecentApps(prefContext, recentApps); + displayRecentApps(recentApps); } else { mHasRecentApps = false; displayOnlyAppInfo(); @@ -195,73 +200,50 @@ public class RecentAppsPreferenceController extends AbstractPreferenceController } private void displayOnlyAppInfo() { - mCategory.setTitle(null); mDivider.setVisible(false); - mSeeAllPref.setTitle(R.string.applications_settings); - mSeeAllPref.setIcon(null); - int prefCount = mCategory.getPreferenceCount(); - for (int i = prefCount - 1; i >= 0; i--) { - final Preference pref = mCategory.getPreference(i); - if (!TextUtils.equals(pref.getKey(), KEY_SEE_ALL)) { - mCategory.removePreference(pref); - } - } + mAllAppPref.setTitle(R.string.applications_settings); + mAllAppPref.setVisible(true); + mRecentAppsPreference.setVisible(false); } - private void displayRecentApps(Context prefContext, List recentApps) { - mCategory.setTitle(R.string.recent_app_category_title); + private void displayRecentApps(List recentApps) { + int showAppsCount = 0; + + for (UsageStats stat : recentApps) { + final AppEntityInfo appEntityInfoInfo = createAppEntity(stat); + if (appEntityInfoInfo != null) { + mAppEntitiesController.setAppEntity(showAppsCount++, appEntityInfoInfo); + } + + if (showAppsCount == AppEntitiesHeaderController.MAXIMUM_APPS) { + break; + } + } + mAppEntitiesController.apply(); + mRecentAppsPreference.setVisible(true); + mAllAppPref.setVisible(false); mDivider.setVisible(true); - mSeeAllPref.setSummary(null); - mSeeAllPref.setIcon(R.drawable.ic_chevron_right_24dp); + } - // Rebind prefs/avoid adding new prefs if possible. Adding/removing prefs causes jank. - // Build a cached preference pool - final Map appPreferences = new ArrayMap<>(); - int prefCount = mCategory.getPreferenceCount(); - for (int i = 0; i < prefCount; i++) { - final Preference pref = mCategory.getPreference(i); - final String key = pref.getKey(); - if (!TextUtils.equals(key, KEY_SEE_ALL)) { - appPreferences.put(key, pref); - } + private AppEntityInfo createAppEntity(UsageStats stat) { + final String pkgName = stat.getPackageName(); + final ApplicationsState.AppEntry appEntry = + mApplicationsState.getEntry(pkgName, mUserId); + if (appEntry == null) { + return null; } - final int recentAppsCount = recentApps.size(); - for (int i = 0; i < recentAppsCount; i++) { - final UsageStats stat = recentApps.get(i); - // Bind recent apps to existing prefs if possible, or create a new pref. - final String pkgName = stat.getPackageName(); - final ApplicationsState.AppEntry appEntry = - mApplicationsState.getEntry(pkgName, mUserId); - if (appEntry == null) { - continue; - } - boolean rebindPref = true; - Preference pref = appPreferences.remove(pkgName); - if (pref == null) { - pref = new AppPreference(prefContext); - rebindPref = false; - } - pref.setKey(pkgName); - pref.setTitle(appEntry.label); - pref.setIcon(mIconDrawableFactory.getBadgedIcon(appEntry.info)); - pref.setSummary(StringUtil.formatRelativeTime(mContext, - System.currentTimeMillis() - stat.getLastTimeUsed(), false)); - pref.setOrder(i); - pref.setOnPreferenceClickListener(preference -> { - AppInfoBase.startAppInfoFragment(AppInfoDashboardFragment.class, - R.string.application_info_label, pkgName, appEntry.info.uid, mHost, - 1001 /*RequestCode*/, SettingsEnums.SETTINGS_APP_NOTIF_CATEGORY); - return true; - }); - if (!rebindPref) { - mCategory.addPreference(pref); - } - } - // Remove unused prefs from pref cache pool - for (Preference unusedPrefs : appPreferences.values()) { - mCategory.removePreference(unusedPrefs); - } + return new AppEntityInfo.Builder() + .setIcon(mIconDrawableFactory.getBadgedIcon(appEntry.info)) + .setTitle(appEntry.label) + .setSummary(StringUtil.formatRelativeTime(mContext, + System.currentTimeMillis() - stat.getLastTimeUsed(), false)) + .setOnClickListener(v -> + AppInfoBase.startAppInfoFragment(AppInfoDashboardFragment.class, + R.string.application_info_label, pkgName, appEntry.info.uid, + mHost, 1001 /*RequestCode*/, + SettingsEnums.SETTINGS_APP_NOTIF_CATEGORY)) + .build(); } private List getDisplayableRecentAppList() { @@ -293,7 +275,7 @@ public class RecentAppsPreferenceController extends AbstractPreferenceController } recentApps.add(stat); count++; - if (count >= SHOW_RECENT_APP_COUNT) { + if (count >= AppEntitiesHeaderController.MAXIMUM_APPS) { break; } } diff --git a/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java index 3fd43695396..1411bc0a30f 100644 --- a/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java @@ -16,23 +16,23 @@ package com.android.settings.applications; +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.Application; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.content.Context; @@ -44,27 +44,29 @@ import android.content.pm.ResolveInfo; import android.os.PowerManager; import android.os.UserHandle; import android.os.UserManager; -import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import androidx.fragment.app.Fragment; import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.instantapps.InstantAppDataProvider; +import com.android.settingslib.widget.AppEntitiesHeaderController; +import com.android.settingslib.widget.AppEntityInfo; +import com.android.settingslib.widget.LayoutPreference; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; @@ -76,12 +78,6 @@ public class RecentAppsPreferenceControllerTest { @Mock private PreferenceScreen mScreen; @Mock - private PreferenceCategory mCategory; - @Mock - private Preference mSeeAllPref; - @Mock - private PreferenceCategory mDivider; - @Mock private UsageStatsManager mUsageStatsManager; @Mock private UserManager mUserManager; @@ -95,73 +91,130 @@ public class RecentAppsPreferenceControllerTest { private ApplicationInfo mApplicationInfo; @Mock private PowerManager mPowerManager; + @Mock + private Fragment mFragment; - private Context mContext; + private LayoutPreference mRecentAppsPreference; private RecentAppsPreferenceController mController; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - when(mContext.getApplicationContext()).thenReturn(mContext); + final Context context = spy(RuntimeEnvironment.application); + when(context.getApplicationContext()).thenReturn(context); ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", mAppState); - doReturn(mUsageStatsManager).when(mContext).getSystemService(Context.USAGE_STATS_SERVICE); - 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[] {}); + 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); + when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{}); - mController = new RecentAppsPreferenceController(mContext, mAppState, null); - when(mScreen.findPreference(anyString())).thenReturn(mCategory); + final View appEntitiesHeaderView = LayoutInflater.from(context).inflate( + R.layout.app_entities_header, null /* root */); + final Preference seeAllPreference = new Preference(context); + final Preference dividerPreference = new Preference(context); + mRecentAppsPreference = spy(new LayoutPreference(context, appEntitiesHeaderView)); - when(mScreen.findPreference(RecentAppsPreferenceController.KEY_SEE_ALL)) - .thenReturn(mSeeAllPref); + mController = spy(new RecentAppsPreferenceController(context, "test_key")); + mController.setFragment(mFragment); + mController.mAppEntitiesController = mock(AppEntitiesHeaderController.class); + mController.mRecentAppsPreference = mRecentAppsPreference; + mController.mAllAppPref = seeAllPreference; + mController.mDivider = dividerPreference; + + when(mScreen.findPreference(RecentAppsPreferenceController.KEY_ALL_APP_INFO)) + .thenReturn(seeAllPreference); when(mScreen.findPreference(RecentAppsPreferenceController.KEY_DIVIDER)) - .thenReturn(mDivider); - when(mCategory.getContext()).thenReturn(mContext); + .thenReturn(dividerPreference); + when(mScreen.findPreference("test_key")).thenReturn(mRecentAppsPreference); + when(mRecentAppsPreference.findViewById(R.id.app_entities_header)).thenReturn( + appEntitiesHeaderView); } @Test - public void isAlwaysAvailable() { - assertThat(mController.isAvailable()).isTrue(); + public void getAvailabilityStatus_hasRecentApps_shouldReturnAvailable() { + final List stats = new ArrayList<>(); + final UsageStats stat1 = new UsageStats(); + stat1.mLastTimeUsed = System.currentTimeMillis(); + stat1.mPackageName = "pkg.class"; + stats.add(stat1); + // stat1 is valid app. + when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) + .thenReturn(new ResolveInfo()); + when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(stats); + mAppEntry.info = mApplicationInfo; + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); } @Test - public void doNotIndexCategory() { - final List nonIndexable = new ArrayList<>(); - - mController.updateNonIndexableKeys(nonIndexable); - - assertThat(nonIndexable).containsAllOf(mController.getPreferenceKey(), - RecentAppsPreferenceController.KEY_DIVIDER); + public void getAvailabilityStatus_noRecentApps_shouldReturnAvailableUnsearchable() { + // No data + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE); } @Test - public void onDisplayAndUpdateState_shouldRefreshUi() { - mController = spy(new RecentAppsPreferenceController(mContext, (Application) null, null)); - - doNothing().when(mController).refreshUi(mContext); + public void displayPreferenceAndUpdateState_shouldRefreshUi() { + doNothing().when(mController).refreshUi(); mController.displayPreference(mScreen); - mController.updateState(mCategory); + mController.updateState(mScreen); - verify(mController, times(2)).refreshUi(mContext); + verify(mController, times(2)).refreshUi(); } @Test - @Config(qualifiers = "mcc999") - public void display_shouldNotShowRecents_showAppInfoPreference() { + public void displayPreference_shouldSetupAppEntitiesHeaderController() { mController.displayPreference(mScreen); - verify(mCategory, never()).addPreference(any(Preference.class)); - verify(mCategory).setTitle(null); - verify(mSeeAllPref).setTitle(R.string.applications_settings); - verify(mSeeAllPref).setIcon(null); - verify(mDivider).setVisible(false); + assertThat(mController.mAppEntitiesController).isNotNull(); } @Test - public void display_showRecents() { + public void updateState_threeValidRecentOpenAppsSet_setAppEntityThreeTime() { + final List stats = new ArrayList<>(); + final UsageStats stat1 = new UsageStats(); + final UsageStats stat2 = new UsageStats(); + final UsageStats stat3 = new UsageStats(); + stat1.mLastTimeUsed = System.currentTimeMillis(); + stat1.mPackageName = "pkg.class"; + stats.add(stat1); + + stat2.mLastTimeUsed = System.currentTimeMillis(); + stat2.mPackageName = "pkg.class2"; + stats.add(stat2); + + stat3.mLastTimeUsed = System.currentTimeMillis(); + stat3.mPackageName = "pkg.class3"; + stats.add(stat3); + + // stat1, stat2 are valid apps. stat3 is invalid. + when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mAppState.getEntry(stat3.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) + .thenReturn(new ResolveInfo()); + when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(stats); + mAppEntry.info = mApplicationInfo; + + mController.updateState(mRecentAppsPreference); + + verify(mController.mAppEntitiesController, times(3)) + .setAppEntity(anyInt(), any(AppEntityInfo.class)); + assertThat(mController.mRecentAppsPreference.isVisible()).isTrue(); + assertThat(mController.mDivider.isVisible()).isTrue(); + assertThat(mController.mAllAppPref.isVisible()).isFalse(); + } + + @Test + public void updateState_oneValidRecentOpenAppSet_setAppEntityOneTime() { final List stats = new ArrayList<>(); final UsageStats stat1 = new UsageStats(); final UsageStats stat2 = new UsageStats(); @@ -175,7 +228,7 @@ public class RecentAppsPreferenceControllerTest { stats.add(stat2); stat3.mLastTimeUsed = System.currentTimeMillis(); - stat3.mPackageName = "pkg.class2"; + stat3.mPackageName = "pkg.class3"; stats.add(stat3); // stat1, stat2 are valid apps. stat3 is invalid. @@ -191,20 +244,19 @@ public class RecentAppsPreferenceControllerTest { .thenReturn(stats); mAppEntry.info = mApplicationInfo; - mController.displayPreference(mScreen); + mController.updateState(mRecentAppsPreference); - verify(mCategory).setTitle(R.string.recent_app_category_title); // Only add stat1. stat2 is skipped because of the package name, stat3 skipped because // it's invalid app. - verify(mCategory, times(1)).addPreference(any(Preference.class)); - - verify(mSeeAllPref).setSummary(null); - verify(mSeeAllPref).setIcon(R.drawable.ic_chevron_right_24dp); - verify(mDivider).setVisible(true); + verify(mController.mAppEntitiesController, times(1)) + .setAppEntity(anyInt(), any(AppEntityInfo.class)); + assertThat(mController.mRecentAppsPreference.isVisible()).isTrue(); + assertThat(mController.mDivider.isVisible()).isTrue(); + assertThat(mController.mAllAppPref.isVisible()).isFalse(); } @Test - public void display_powerSaverMode_showNoRecents() { + public void updateState_powerSaverModeOn_headerIsNotVisible() { when(mPowerManager.isPowerSaveMode()).thenReturn(true); final List stats = new ArrayList<>(); @@ -223,17 +275,15 @@ public class RecentAppsPreferenceControllerTest { .thenReturn(stats); mAppEntry.info = mApplicationInfo; - mController.displayPreference(mScreen); + mController.updateState(mRecentAppsPreference); - verify(mCategory, never()).addPreference(any(Preference.class)); - verify(mCategory).setTitle(null); - verify(mSeeAllPref).setTitle(R.string.applications_settings); - verify(mSeeAllPref).setIcon(null); - verify(mDivider).setVisible(false); + assertThat(mController.mRecentAppsPreference.isVisible()).isFalse(); + assertThat(mController.mDivider.isVisible()).isFalse(); + assertThat(mController.mAllAppPref.isVisible()).isTrue(); } @Test - public void display_showRecentsWithInstantApp() { + public void updateState_instantAppSet_shouldSetAppEntityForInstantApp() { // Regular app. final List stats = new ArrayList<>(); final UsageStats stat1 = new UsageStats(); @@ -258,7 +308,6 @@ public class RecentAppsPreferenceControllerTest { // Only the regular app stat1 should have its intent resolve. when(mPackageManager.resolveActivity(argThat(intentMatcher(stat1.mPackageName)), anyInt())) .thenReturn(new ResolveInfo()); - when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) .thenReturn(stats); @@ -266,17 +315,14 @@ public class RecentAppsPreferenceControllerTest { ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", (InstantAppDataProvider) (ApplicationInfo info) -> info == stat2Entry.info); - mController.displayPreference(mScreen); + mController.updateState(mRecentAppsPreference); - ArgumentCaptor prefCaptor = ArgumentCaptor.forClass(Preference.class); - verify(mCategory, times(2)).addPreference(prefCaptor.capture()); - List prefs = prefCaptor.getAllValues(); - assertThat(prefs.get(1).getKey()).isEqualTo(stat1.mPackageName); - assertThat(prefs.get(0).getKey()).isEqualTo(stat2.mPackageName); + verify(mController.mAppEntitiesController, times(2)) + .setAppEntity(anyInt(), any(AppEntityInfo.class)); } @Test - public void display_showRecentsWithNullAppEntryOrInfo() { + public void updateState_withNullAppEntryOrInfo_shouldNotCrash() { final List stats = new ArrayList<>(); final UsageStats stat1 = new UsageStats(); final UsageStats stat2 = new UsageStats(); @@ -299,63 +345,11 @@ public class RecentAppsPreferenceControllerTest { .thenReturn(stats); // We should not crash here. - mController.displayPreference(mScreen); + mController.updateState(mRecentAppsPreference); } @Test - public void display_hasRecentButNoneDisplayable_showAppInfo() { - final List stats = new ArrayList<>(); - final UsageStats stat1 = new UsageStats(); - final UsageStats stat2 = new UsageStats(); - stat1.mLastTimeUsed = System.currentTimeMillis(); - stat1.mPackageName = "com.android.phone"; - stats.add(stat1); - - stat2.mLastTimeUsed = System.currentTimeMillis(); - stat2.mPackageName = "com.android.settings"; - stats.add(stat2); - - // stat1, stat2 are not displayable - when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) - .thenReturn(mock(ApplicationsState.AppEntry.class)); - when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) - .thenReturn(mock(ApplicationsState.AppEntry.class)); - when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) - .thenReturn(new ResolveInfo()); - when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) - .thenReturn(stats); - - mController.displayPreference(mScreen); - - verify(mCategory, never()).addPreference(any(Preference.class)); - verify(mCategory).setTitle(null); - verify(mSeeAllPref).setTitle(R.string.applications_settings); - verify(mSeeAllPref).setIcon(null); - } - - @Test - public void display_showRecents_formatSummary() { - final UsageStats stat1 = new UsageStats(); - stat1.mLastTimeUsed = System.currentTimeMillis(); - stat1.mPackageName = "pkg.class"; - final List stats = new ArrayList<>(); - stats.add(stat1); - - when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) - .thenReturn(mAppEntry); - when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) - .thenReturn(new ResolveInfo()); - when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) - .thenReturn(stats); - mAppEntry.info = mApplicationInfo; - - mController.displayPreference(mScreen); - - verify(mCategory).addPreference(argThat(summaryMatches("0 minutes ago"))); - } - - @Test - public void displayPreference_shouldNotShowHiddenSystemModule() { + public void updateState_hiddenSystemModuleSet_shouldNotShowHiddenSystemModule() { final List stats = new ArrayList<>(); // Regular app. final UsageStats stat1 = new UsageStats(); @@ -389,24 +383,17 @@ public class RecentAppsPreferenceControllerTest { final List modules = new ArrayList<>(); modules.add(moduleInfo2); when(mPackageManager.getInstalledModules(anyInt() /* flags */)) - .thenReturn(modules); + .thenReturn(modules); when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) - .thenReturn(new ResolveInfo()); + .thenReturn(new ResolveInfo()); when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) - .thenReturn(stats); + .thenReturn(stats); - mController.displayPreference(mScreen); + mController.updateState(mRecentAppsPreference); // Only add stat1. stat2 is skipped because it is hidden module. - final ArgumentCaptor prefCaptor = ArgumentCaptor.forClass(Preference.class); - verify(mCategory).addPreference(prefCaptor.capture()); - final Preference pref = prefCaptor.getValue(); - assertThat(pref.getKey()).isEqualTo(stat1.mPackageName); - } - - private static ArgumentMatcher summaryMatches(String expected) { - return preference -> TextUtils.equals(expected, preference.getSummary()); + verify(mController.mAppEntitiesController).setAppEntity(anyInt(), any(AppEntityInfo.class)); } // Used for matching an intent with a specific package name.