OSDN Git Service

Add app entities widget for recently opened apps
authortmfang <tmfang@google.com>
Wed, 30 Jan 2019 07:57:13 +0000 (15:57 +0800)
committertmfang <tmfang@google.com>
Thu, 21 Feb 2019 08:23:18 +0000 (16:23 +0800)
If there is no recently opened app should be shown,
just show up an "All apps" preference.

If there are some recently opened apps, we show up
result with app entites controller.

- Clean up some useless ui. (category)
- User BasePreferenceController
- Modify test cases.

Test: robotest, visual
Change-Id: I411f61ed32eaaed97921941fd5026f1d65308d00
Fixes: 123538183

res/xml/app_and_notification.xml
src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
src/com/android/settings/applications/RecentAppsPreferenceController.java
tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java

index c15df75..518379a 100644 (file)
     settings:initialExpandedChildrenCount="8">
     <!-- the initial count should include the dynamic tiles -->
 
-    <PreferenceCategory
-        android:key="recent_apps_category"
-        android:title="@string/recent_app_category_title"
-        android:order="-200">
-        <!-- Placeholder for a list of recent apps -->
+    <Preference
+        android:key="all_app_info"
+        android:title="@string/applications_settings"
+        android:order="-999"
+        android:fragment="com.android.settings.applications.manageapplications.ManageApplications"/>
 
-        <!-- See all apps button -->
-        <Preference
-            android:title="@string/applications_settings"
-            android:key="all_app_info"
-            android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
-            android:order="20"/>
-    </PreferenceCategory>
+    <com.android.settingslib.widget.LayoutPreference
+        android:key="recent_open_apps"
+        android:title="@string/recent_app_category_title"
+        android:layout="@layout/app_entities_header"
+        android:selectable="false"
+        android:order="-998"
+        settings:allowDividerBelow="true"
+        settings:controller="com.android.settings.applications.RecentAppsPreferenceController"/>
 
     <!-- Empty category to draw divider -->
     <PreferenceCategory
-        android:key="all_app_info_divider"
-        android:order="-190"/>
+        android:key="recent_apps_divider"
+        android:order="-997"/>
 
     <!-- Notifications (appears before manage_perms), default apps (appears after) -->
     <PreferenceCategory
index 94c332f..4848351 100644 (file)
 
 package com.android.settings.applications;
 
-import android.app.Activity;
-import android.app.Application;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.provider.SearchIndexableResource;
 
-import androidx.fragment.app.Fragment;
-
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.notification.EmergencyBroadcastPreferenceController;
@@ -63,27 +59,20 @@ public class AppAndNotificationDashboardFragment extends DashboardFragment {
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
+
         use(SpecialAppAccessPreferenceController.class).setSession(getSettingsLifecycle());
+        use(RecentAppsPreferenceController.class).setFragment(this /* fragment */);
     }
 
     @Override
     protected List<AbstractPreferenceController> 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<AbstractPreferenceController> buildPreferenceControllers(Context context,
-            Application app, Fragment host) {
+    private static List<AbstractPreferenceController> buildPreferenceControllers(Context context) {
         final List<AbstractPreferenceController> 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<AbstractPreferenceController> createPreferenceControllers(
                         Context context) {
-                    return buildPreferenceControllers(context, null, null /* host */);
+                    return buildPreferenceControllers(context);
                 }
             };
 }
index 6e0ae45..fe52a8b 100644 (file)
@@ -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<UsageStats> {
+public class RecentAppsPreferenceController extends BasePreferenceController
+        implements Comparator<UsageStats> {
 
-    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";
+    static final String KEY_ALL_APP_INFO = "all_app_info";
     @VisibleForTesting
-    static final String KEY_SEE_ALL = "all_app_info";
-    private static final int SHOW_RECENT_APP_COUNT = 5;
+    static final String KEY_DIVIDER = "recent_apps_divider";
+
+    private static final String TAG = "RecentAppsCtrl";
     private static final Set<String> 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<UsageStats> 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);
-
-        mHost = host;
-        mUsageStatsManager =
-                (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
-        mApplicationsState = appState;
-    }
-
-    @Override
-    public boolean isAvailable() {
-        return true;
+        mPm = mContext.getPackageManager();
+        mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        mUsageStatsManager = mContext.getSystemService(UsageStatsManager.class);
     }
 
-    @Override
-    public String getPreferenceKey() {
-        return KEY_PREF_CATEGORY;
+    public void setFragment(Fragment fragment) {
+        mHost = fragment;
     }
 
     @Override
-    public void updateNonIndexableKeys(List<String> 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<UsageStats> 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<UsageStats> recentApps) {
-        mCategory.setTitle(R.string.recent_app_category_title);
-        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<String, Preference> 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);
-            }
-        }
-        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;
-            }
+    private void displayRecentApps(List<UsageStats> recentApps) {
+        int showAppsCount = 0;
 
-            boolean rebindPref = true;
-            Preference pref = appPreferences.remove(pkgName);
-            if (pref == null) {
-                pref = new AppPreference(prefContext);
-                rebindPref = false;
+        for (UsageStats stat : recentApps) {
+            final AppEntityInfo appEntityInfoInfo = createAppEntity(stat);
+            if (appEntityInfoInfo != null) {
+                mAppEntitiesController.setAppEntity(showAppsCount++, appEntityInfoInfo);
             }
-            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);
+
+            if (showAppsCount == AppEntitiesHeaderController.MAXIMUM_APPS) {
+                break;
             }
         }
-        // Remove unused prefs from pref cache pool
-        for (Preference unusedPrefs : appPreferences.values()) {
-            mCategory.removePreference(unusedPrefs);
+        mAppEntitiesController.apply();
+        mRecentAppsPreference.setVisible(true);
+        mAllAppPref.setVisible(false);
+        mDivider.setVisible(true);
+    }
+
+    private AppEntityInfo createAppEntity(UsageStats stat) {
+        final String pkgName = stat.getPackageName();
+        final ApplicationsState.AppEntry appEntry =
+                mApplicationsState.getEntry(pkgName, mUserId);
+        if (appEntry == null) {
+            return null;
         }
+
+        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<UsageStats> 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;
             }
         }
index 3fd4369..1411bc0 100644 (file)
 
 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[]{});
+
+        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));
+
+        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(dividerPreference);
+        when(mScreen.findPreference("test_key")).thenReturn(mRecentAppsPreference);
+        when(mRecentAppsPreference.findViewById(R.id.app_entities_header)).thenReturn(
+                appEntitiesHeaderView);
+    }
 
-        mController = new RecentAppsPreferenceController(mContext, mAppState, null);
-        when(mScreen.findPreference(anyString())).thenReturn(mCategory);
+    @Test
+    public void getAvailabilityStatus_hasRecentApps_shouldReturnAvailable() {
+        final List<UsageStats> 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;
 
-        when(mScreen.findPreference(RecentAppsPreferenceController.KEY_SEE_ALL))
-                .thenReturn(mSeeAllPref);
-        when(mScreen.findPreference(RecentAppsPreferenceController.KEY_DIVIDER))
-                .thenReturn(mDivider);
-        when(mCategory.getContext()).thenReturn(mContext);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
     }
 
     @Test
-    public void isAlwaysAvailable() {
-        assertThat(mController.isAvailable()).isTrue();
+    public void getAvailabilityStatus_noRecentApps_shouldReturnAvailableUnsearchable() {
+        // No data
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE);
     }
 
     @Test
-    public void doNotIndexCategory() {
-        final List<String> nonIndexable = new ArrayList<>();
+    public void displayPreferenceAndUpdateState_shouldRefreshUi() {
+        doNothing().when(mController).refreshUi();
 
-        mController.updateNonIndexableKeys(nonIndexable);
+        mController.displayPreference(mScreen);
+        mController.updateState(mScreen);
 
-        assertThat(nonIndexable).containsAllOf(mController.getPreferenceKey(),
-                RecentAppsPreferenceController.KEY_DIVIDER);
+        verify(mController, times(2)).refreshUi();
     }
 
     @Test
-    public void onDisplayAndUpdateState_shouldRefreshUi() {
-        mController = spy(new RecentAppsPreferenceController(mContext, (Application) null, null));
-
-        doNothing().when(mController).refreshUi(mContext);
-
+    public void displayPreference_shouldSetupAppEntitiesHeaderController() {
         mController.displayPreference(mScreen);
-        mController.updateState(mCategory);
 
-        verify(mController, times(2)).refreshUi(mContext);
+        assertThat(mController.mAppEntitiesController).isNotNull();
     }
 
     @Test
-    @Config(qualifiers = "mcc999")
-    public void display_shouldNotShowRecents_showAppInfoPreference() {
-        mController.displayPreference(mScreen);
+    public void updateState_threeValidRecentOpenAppsSet_setAppEntityThreeTime() {
+        final List<UsageStats> 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(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);
+        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 display_showRecents() {
+    public void updateState_oneValidRecentOpenAppSet_setAppEntityOneTime() {
         final List<UsageStats> 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<UsageStats> 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<UsageStats> 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<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
-        verify(mCategory, times(2)).addPreference(prefCaptor.capture());
-        List<Preference> 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<UsageStats> 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);
-    }
-
-    @Test
-    public void display_hasRecentButNoneDisplayable_showAppInfo() {
-        final List<UsageStats> 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<UsageStats> 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")));
+        mController.updateState(mRecentAppsPreference);
     }
 
     @Test
-    public void displayPreference_shouldNotShowHiddenSystemModule() {
+    public void updateState_hiddenSystemModuleSet_shouldNotShowHiddenSystemModule() {
         final List<UsageStats> stats = new ArrayList<>();
         // Regular app.
         final UsageStats stat1 = new UsageStats();
@@ -389,24 +383,17 @@ public class RecentAppsPreferenceControllerTest {
         final List<ModuleInfo> 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<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
-        verify(mCategory).addPreference(prefCaptor.capture());
-        final Preference pref = prefCaptor.getValue();
-        assertThat(pref.getKey()).isEqualTo(stat1.mPackageName);
-    }
-
-    private static ArgumentMatcher<Preference> 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.