Create an AppEntitiesHeader widget

- Create a library named EntityHeaderWidgets, it can contains
  other entity header wdigets in the future.
- Create a controller of AppEntitiesHeader,
  then user can initialize its view easily.

Test: visual, robotest
Change-Id: If11b3eba75334f66b72472b2139e0e7d93984dce
Fixes: 117621156
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index cc17b25..0126e7e5 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -18,6 +18,7 @@
         "SettingsLibSettingsSpinner",
         "SettingsLayoutPreference",
         "ActionButtonsPreference",
+        "SettingsLibEntityHeaderWidgets",
     ],
 
     // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/EntityHeaderWidgets/Android.bp b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
new file mode 100644
index 0000000..3ca4ecd
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
@@ -0,0 +1,14 @@
+android_library {
+    name: "SettingsLibEntityHeaderWidgets",
+
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+
+    static_libs: [
+          "androidx.annotation_annotation",
+          "SettingsLibAppPreference"
+    ],
+
+    sdk_version: "system_current",
+    min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml b/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml
new file mode 100644
index 0000000..4b9f1ab
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.widget">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
new file mode 100644
index 0000000..9f30eda
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/app_entities_header"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="24dp"
+    android:paddingEnd="8dp"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/header_title"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:gravity="center"
+        android:textAppearance="@style/AppEntitiesHeader.Text.HeaderTitle"/>
+
+    <LinearLayout
+        android:id="@+id/all_apps_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp"
+        android:gravity="center">
+
+        <include
+            android:id="@+id/app1_view"
+            layout="@layout/app_view"/>
+
+        <include
+            android:id="@+id/app2_view"
+            layout="@layout/app_view"/>
+
+        <include
+            android:id="@+id/app3_view"
+            layout="@layout/app_view"/>
+
+    </LinearLayout>
+
+    <Button
+        android:id="@+id/header_details"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:gravity="center"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml
new file mode 100644
index 0000000..fcafa31
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="wrap_content"
+    android:layout_weight="1"
+    android:layout_marginEnd="16dp"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/app_icon"
+        android:layout_width="@dimen/secondary_app_icon_size"
+        android:layout_height="@dimen/secondary_app_icon_size"
+        android:layout_marginBottom="12dp"/>
+
+    <TextView
+        android:id="@+id/app_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="2dp"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="@style/AppEntitiesHeader.Text.Title"/>
+
+    <TextView
+        android:id="@+id/app_summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="@style/AppEntitiesHeader.Text.Summary"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml b/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml
new file mode 100644
index 0000000..0eefd4b
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <style name="AppEntitiesHeader.Text"
+           parent="@android:style/TextAppearance.Material.Subhead">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="AppEntitiesHeader.Text.HeaderTitle">
+        <item name="android:textSize">14sp</item>
+    </style>
+
+    <style name="AppEntitiesHeader.Text.Title">
+        <item name="android:textSize">16sp</item>
+    </style>
+
+    <style name="AppEntitiesHeader.Text.Summary"
+           parent="@android:style/TextAppearance.Material.Body1">
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textSize">14sp</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
new file mode 100644
index 0000000..8ccf89f
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * This is used to initialize view which was inflated
+ * from {@link R.xml.app_entities_header.xml}.
+ *
+ * <p>The view looks like below.
+ *
+ * <pre>
+ * --------------------------------------------------------------
+ * |                     Header title                           |
+ * --------------------------------------------------------------
+ * |    App1 icon       |   App2 icon        |   App3 icon      |
+ * |    App1 title      |   App2 title       |   App3 title     |
+ * |    App1 summary    |   App2 summary     |   App3 summary   |
+ * |-------------------------------------------------------------
+ * |                     Header details                         |
+ * --------------------------------------------------------------
+ * </pre>
+ *
+ * <p>How to use AppEntitiesHeaderController?
+ *
+ * <p>1. Add a {@link LayoutPreference} in layout XML file.
+ * <pre>
+ * &lt;com.android.settingslib.widget.LayoutPreference
+ *        android:key="app_entities_header"
+ *        android:layout="@layout/app_entities_header"/&gt;
+ * </pre>
+ *
+ * <p>2. Use AppEntitiesHeaderController to call below methods, then you can initialize
+ * view of <code>app_entities_header</code>.
+ *
+ * <pre>
+ *
+ * View headerView = ((LayoutPreference) screen.findPreference("app_entities_header"))
+ *         .findViewById(R.id.app_entities_header);
+ *
+ * AppEntitiesHeaderController.newInstance(context, headerView)
+ *         .setHeaderTitleRes(R.string.xxxxx)
+ *         .setHeaderDetailsRes(R.string.xxxxx)
+ *         .setHeaderDetailsClickListener(onClickListener)
+ *         .setAppEntity(0, icon, "app title", "app summary")
+ *         .setAppEntity(1, icon, "app title", "app summary")
+ *         .setAppEntity(2, icon, "app title", "app summary")
+ *         .apply();
+ * </pre>
+ */
+public class AppEntitiesHeaderController {
+
+    private static final String TAG = "AppEntitiesHeaderCtl";
+
+    @VisibleForTesting
+    static final int MAXIMUM_APPS = 3;
+
+    private final Context mContext;
+    private final TextView mHeaderTitleView;
+    private final Button mHeaderDetailsView;
+
+    private final AppEntity[] mAppEntities;
+    private final View[] mAppEntityViews;
+    private final ImageView[] mAppIconViews;
+    private final TextView[] mAppTitleViews;
+    private final TextView[] mAppSummaryViews;
+
+    private int mHeaderTitleRes;
+    private int mHeaderDetailsRes;
+    private View.OnClickListener mDetailsOnClickListener;
+
+    /**
+     * Creates a new instance of the controller.
+     *
+     * @param context the Context the view is running in
+     * @param appEntitiesHeaderView view was inflated from <code>app_entities_header</code>
+     */
+    public static AppEntitiesHeaderController newInstance(@NonNull Context context,
+            @NonNull View appEntitiesHeaderView) {
+        return new AppEntitiesHeaderController(context, appEntitiesHeaderView);
+    }
+
+    private AppEntitiesHeaderController(Context context, View appEntitiesHeaderView) {
+        mContext = context;
+        mHeaderTitleView = appEntitiesHeaderView.findViewById(R.id.header_title);
+        mHeaderDetailsView = appEntitiesHeaderView.findViewById(R.id.header_details);
+
+        mAppEntities = new AppEntity[MAXIMUM_APPS];
+        mAppIconViews = new ImageView[MAXIMUM_APPS];
+        mAppTitleViews = new TextView[MAXIMUM_APPS];
+        mAppSummaryViews = new TextView[MAXIMUM_APPS];
+
+        mAppEntityViews = new View[]{
+                appEntitiesHeaderView.findViewById(R.id.app1_view),
+                appEntitiesHeaderView.findViewById(R.id.app2_view),
+                appEntitiesHeaderView.findViewById(R.id.app3_view)
+        };
+
+        // Initialize view in advance, so we won't take too much time to do it when controller is
+        // binding view.
+        for (int index = 0; index < MAXIMUM_APPS; index++) {
+            final View appView = mAppEntityViews[index];
+            mAppIconViews[index] = (ImageView) appView.findViewById(R.id.app_icon);
+            mAppTitleViews[index] = (TextView) appView.findViewById(R.id.app_title);
+            mAppSummaryViews[index] = (TextView) appView.findViewById(R.id.app_summary);
+        }
+    }
+
+    /**
+     * Set the text resource for app entities header title.
+     */
+    public AppEntitiesHeaderController setHeaderTitleRes(@StringRes int titleRes) {
+        mHeaderTitleRes = titleRes;
+        return this;
+    }
+
+    /**
+     * Set the text resource for app entities header details.
+     */
+    public AppEntitiesHeaderController setHeaderDetailsRes(@StringRes int detailsRes) {
+        mHeaderDetailsRes = detailsRes;
+        return this;
+    }
+
+    /**
+     * Register a callback to be invoked when header details view is clicked.
+     */
+    public AppEntitiesHeaderController setHeaderDetailsClickListener(
+            @Nullable View.OnClickListener clickListener) {
+        mDetailsOnClickListener = clickListener;
+        return this;
+    }
+
+    /**
+     * Set an app entity at a specified position view.
+     *
+     * @param index the index at which the specified view is to be inserted
+     * @param icon the icon of app entity
+     * @param titleRes the title of app entity
+     * @param summaryRes the summary of app entity
+     * @return this {@code AppEntitiesHeaderController} object
+     */
+    public AppEntitiesHeaderController setAppEntity(int index, @NonNull Drawable icon,
+            @Nullable CharSequence titleRes, @Nullable CharSequence summaryRes) {
+        final AppEntity appEntity = new AppEntity(icon, titleRes, summaryRes);
+        mAppEntities[index] = appEntity;
+        return this;
+    }
+
+    /**
+     * Remove an app entity at a specified position view.
+     *
+     * @param index the index at which the specified view is to be removed
+     * @return this {@code AppEntitiesHeaderController} object
+     */
+    public AppEntitiesHeaderController removeAppEntity(int index) {
+        mAppEntities[index] = null;
+        return this;
+    }
+
+    /**
+     * Clear all app entities in app entities header.
+     *
+     * @return this {@code AppEntitiesHeaderController} object
+     */
+    public AppEntitiesHeaderController clearAllAppEntities() {
+        for (int index = 0; index < MAXIMUM_APPS; index++) {
+            removeAppEntity(index);
+        }
+        return this;
+    }
+
+    /**
+     * Done mutating app entities header, rebinds everything.
+     */
+    public void apply() {
+        bindHeaderTitleView();
+        bindHeaderDetailsView();
+
+        // Rebind all apps view
+        for (int index = 0; index < MAXIMUM_APPS; index++) {
+            bindAppEntityView(index);
+        }
+    }
+
+    private void bindHeaderTitleView() {
+        CharSequence titleText = "";
+        try {
+            titleText = mContext.getText(mHeaderTitleRes);
+        } catch (Resources.NotFoundException e) {
+            Log.e(TAG, "Resource of header title can't not be found!", e);
+        }
+        mHeaderTitleView.setText(titleText);
+        mHeaderTitleView.setVisibility(
+                TextUtils.isEmpty(titleText) ? View.GONE : View.VISIBLE);
+    }
+
+    private void bindHeaderDetailsView() {
+        CharSequence detailsText = "";
+        try {
+            detailsText = mContext.getText(mHeaderDetailsRes);
+        } catch (Resources.NotFoundException e) {
+            Log.e(TAG, "Resource of header details can't not be found!", e);
+        }
+        mHeaderDetailsView.setText(detailsText);
+        mHeaderDetailsView.setVisibility(
+                TextUtils.isEmpty(detailsText) ? View.GONE : View.VISIBLE);
+        mHeaderDetailsView.setOnClickListener(mDetailsOnClickListener);
+    }
+
+    private void bindAppEntityView(int index) {
+        final AppEntity appEntity = mAppEntities[index];
+        mAppEntityViews[index].setVisibility(appEntity != null ? View.VISIBLE : View.GONE);
+
+        if (appEntity != null) {
+            mAppIconViews[index].setImageDrawable(appEntity.icon);
+
+            mAppTitleViews[index].setVisibility(
+                    TextUtils.isEmpty(appEntity.title) ? View.INVISIBLE : View.VISIBLE);
+            mAppTitleViews[index].setText(appEntity.title);
+
+            mAppSummaryViews[index].setVisibility(
+                    TextUtils.isEmpty(appEntity.summary) ? View.INVISIBLE : View.VISIBLE);
+            mAppSummaryViews[index].setText(appEntity.summary);
+        }
+    }
+
+    private static class AppEntity {
+        public final Drawable icon;
+        public final CharSequence title;
+        public final CharSequence summary;
+
+        AppEntity(Drawable appIcon, CharSequence appTitle, CharSequence appSummary) {
+            icon = appIcon;
+            title = appTitle;
+            summary = appSummary;
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
new file mode 100644
index 0000000..c3bc8da
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class AppEntitiesHeaderControllerTest {
+
+    private static final CharSequence TITLE = "APP_TITLE";
+    private static final CharSequence SUMMARY = "APP_SUMMARY";
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    private Context mContext;
+    private Drawable mIcon;
+    private View mAppEntitiesHeaderView;
+    private AppEntitiesHeaderController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mAppEntitiesHeaderView = LayoutInflater.from(mContext).inflate(
+                R.layout.app_entities_header, null /* root */);
+        mIcon = mContext.getDrawable(R.drawable.ic_menu);
+        mController = AppEntitiesHeaderController.newInstance(mContext,
+                mAppEntitiesHeaderView);
+    }
+
+    @Test
+    public void assert_amountOfMaximumAppsAreThree() {
+        assertThat(AppEntitiesHeaderController.MAXIMUM_APPS).isEqualTo(3);
+    }
+
+    @Test
+    public void setHeaderTitleRes_setTextRes_shouldSetToTitleView() {
+        mController.setHeaderTitleRes(R.string.expand_button_title).apply();
+        final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_title);
+
+        assertThat(view.getText()).isEqualTo(mContext.getText(R.string.expand_button_title));
+    }
+
+    @Test
+    public void setHeaderDetailsRes_setTextRes_shouldSetToDetailsView() {
+        mController.setHeaderDetailsRes(R.string.expand_button_title).apply();
+        final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_details);
+
+        assertThat(view.getText()).isEqualTo(mContext.getText(R.string.expand_button_title));
+    }
+
+    @Test
+    public void setHeaderDetailsClickListener_setClickListener_detailsViewAttachClickListener() {
+        mController.setHeaderDetailsClickListener(v -> {
+        }).apply();
+        final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_details);
+
+        assertThat(view.hasOnClickListeners()).isTrue();
+    }
+
+    @Test
+    public void setAppEntity_indexLessThanZero_shouldThrowArrayIndexOutOfBoundsException() {
+        thrown.expect(ArrayIndexOutOfBoundsException.class);
+
+        mController.setAppEntity(-1, mIcon, TITLE, SUMMARY);
+    }
+
+    @Test
+    public void asetAppEntity_indexGreaterThanMaximum_shouldThrowArrayIndexOutOfBoundsException() {
+        thrown.expect(ArrayIndexOutOfBoundsException.class);
+
+        mController.setAppEntity(AppEntitiesHeaderController.MAXIMUM_APPS + 1, mIcon, TITLE,
+                SUMMARY);
+    }
+
+    @Test
+    public void setAppEntity_addAppToIndex0_shouldShowAppView1() {
+        mController.setAppEntity(0, mIcon, TITLE, SUMMARY).apply();
+        final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
+        final ImageView appIconView = app1View.findViewById(R.id.app_icon);
+        final TextView appTitle = app1View.findViewById(R.id.app_title);
+        final TextView appSummary = app1View.findViewById(R.id.app_summary);
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView.getDrawable()).isNotNull();
+        assertThat(appTitle.getText()).isEqualTo(TITLE);
+        assertThat(appSummary.getText()).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void setAppEntity_addAppToIndex1_shouldShowAppView2() {
+        mController.setAppEntity(1, mIcon, TITLE, SUMMARY).apply();
+        final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
+        final ImageView appIconView = app2View.findViewById(R.id.app_icon);
+        final TextView appTitle = app2View.findViewById(R.id.app_title);
+        final TextView appSummary = app2View.findViewById(R.id.app_summary);
+
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView.getDrawable()).isNotNull();
+        assertThat(appTitle.getText()).isEqualTo(TITLE);
+        assertThat(appSummary.getText()).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void setAppEntity_addAppToIndex2_shouldShowAppView3() {
+        mController.setAppEntity(2, mIcon, TITLE, SUMMARY).apply();
+        final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view);
+        final ImageView appIconView = app3View.findViewById(R.id.app_icon);
+        final TextView appTitle = app3View.findViewById(R.id.app_title);
+        final TextView appSummary = app3View.findViewById(R.id.app_summary);
+
+        assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView.getDrawable()).isNotNull();
+        assertThat(appTitle.getText()).isEqualTo(TITLE);
+        assertThat(appSummary.getText()).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void removeAppEntity_removeIndex0_shouldNotShowAppView1() {
+        mController.setAppEntity(0, mIcon, TITLE, SUMMARY)
+                .setAppEntity(1, mIcon, TITLE, SUMMARY).apply();
+        final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
+        final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+
+        mController.removeAppEntity(0).apply();
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.GONE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void clearAllAppEntities_shouldNotShowAllAppViews() {
+        mController.setAppEntity(0, mIcon, TITLE, SUMMARY)
+                .setAppEntity(1, mIcon, TITLE, SUMMARY)
+                .setAppEntity(2, mIcon, TITLE, SUMMARY).apply();
+        final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
+        final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
+        final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view);
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE);
+
+        mController.clearAllAppEntities().apply();
+        assertThat(app1View.getVisibility()).isEqualTo(View.GONE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.GONE);
+        assertThat(app3View.getVisibility()).isEqualTo(View.GONE);
+    }
+}