Separate suggestions and conditions.

This is the initial change for updating the suggestion cards:
- add a feature flag to swap between the new and current UI
- change suggestions to a standalone dashboard item. It becomes a
  horizontal scroll list that won't collapse/expand.
- the expand/collapse logic now only control the conditions list
- add draft for the new suggestion UI elements, but detail to fine-tune
to match the UI spec will come in following changes.

Bug: 70573674
Test: make RunSettingsRoboTests
Change-Id: I00c901e2598b26a34288fc73fd6031cc26a29ac6
diff --git a/res/drawable/ic_suggestion_close_button.xml b/res/drawable/ic_suggestion_close_button.xml
new file mode 100644
index 0000000..615b215
--- /dev/null
+++ b/res/drawable/ic_suggestion_close_button.xml
@@ -0,0 +1,25 @@
+<!--
+     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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18.3,5.71a0.996,0.996 0,0 0,-1.41 0L12,10.59 7.11,5.7A0.996,0.996 0,1 0,5.7 7.11L10.59,12 5.7,16.89a0.996,0.996 0,1 0,1.41 1.41L12,13.41l4.89,4.89a0.996,0.996 0,1 0,1.41 -1.41L13.41,12l4.89,-4.89c0.38,-0.38 0.38,-1.02 0,-1.4z"/>
+</vector>
diff --git a/res/layout/condition_container.xml b/res/layout/condition_container.xml
new file mode 100644
index 0000000..808c4ac
--- /dev/null
+++ b/res/layout/condition_container.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 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.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    style="@style/SuggestionConditionStyle"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="16dp"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp"
+    android:paddingBottom="@dimen/dashboard_padding_bottom">
+
+    <android.support.v7.widget.CardView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:cardUseCompatPadding="true"
+        app:cardElevation="2dp">
+
+        <android.support.v7.widget.RecyclerView
+            android:id="@+id/data"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@color/material_grey_300"
+            android:scrollbars="none"/>
+
+    </android.support.v7.widget.CardView>
+
+</FrameLayout>
diff --git a/res/layout/suggestion_container.xml b/res/layout/suggestion_container.xml
new file mode 100644
index 0000000..2aa1043
--- /dev/null
+++ b/res/layout/suggestion_container.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"
+    style="@style/SuggestionConditionStyle"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="20dp"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        android:gravity="center_vertical">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="24dp"
+            android:layout_centerVertical="true"
+            android:gravity="start"
+            android:text="@string/suggestions_title_v2"
+            android:textAppearance="@style/TextAppearance.SuggestionHeader" />
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_marginEnd="24dp"
+            android:layout_centerVertical="true"
+            android:gravity="end"
+            android:textAppearance="@style/TextAppearance.SuggestionHeader" />
+
+    </LinearLayout>
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/suggestion_list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="20dp"
+        android:paddingBottom="16dp"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:scrollbars="none"/>
+
+</LinearLayout>
diff --git a/res/layout/suggestion_tile_v2.xml b/res/layout/suggestion_tile_v2.xml
new file mode 100644
index 0000000..e180897
--- /dev/null
+++ b/res/layout/suggestion_tile_v2.xml
@@ -0,0 +1,77 @@
+<?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.
+-->
+
+<android.support.v7.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/suggestion_card"
+    android:layout_width="328dp"
+    android:layout_height="wrap_content"
+    app:cardUseCompatPadding="true"
+    app:cardElevation="2dp">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="112dp"
+        android:orientation="vertical">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@android:id/icon"
+                android:layout_width="@dimen/dashboard_tile_image_size"
+                android:layout_height="@dimen/dashboard_tile_image_size"
+                android:layout_centerHorizontal = "true"
+                android:layout_marginTop="16dp"
+                android:layout_marginBottom="8dp" />
+
+            <ImageView
+                android:id="@+id/close_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_marginTop="8dp"
+                android:layout_marginEnd="8dp"
+                android:src="@drawable/ic_suggestion_close_button"/>
+
+        </RelativeLayout>
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:singleLine="true"
+            android:layout_marginLeft="12dp"
+            android:layout_marginRight="12dp"
+            android:textAppearance="@style/TextAppearance.TileTitle"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:textAppearance="@style/TextAppearance.SuggestionSummary" />
+
+    </LinearLayout>
+
+</android.support.v7.widget.CardView>
diff --git a/res/layout/suggestion_tile_with_button_v2.xml b/res/layout/suggestion_tile_with_button_v2.xml
new file mode 100644
index 0000000..01be236
--- /dev/null
+++ b/res/layout/suggestion_tile_with_button_v2.xml
@@ -0,0 +1,84 @@
+<?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.
+-->
+
+<android.support.v7.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/suggestion_card"
+    android:layout_width="328dp"
+    android:layout_height="wrap_content"
+    app:cardUseCompatPadding="true"
+    app:cardElevation="2dp">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="112dp"
+        android:orientation="vertical"
+        android:background="@android:color/white">
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@android:id/icon"
+                android:layout_width="@dimen/dashboard_tile_image_size"
+                android:layout_height="@dimen/dashboard_tile_image_size"
+                android:layout_centerHorizontal = "true"
+                android:layout_marginTop="16dp"
+                android:layout_marginBottom="8dp" />
+
+            <ImageView
+                android:id="@+id/close_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_marginTop="8dp"
+                android:layout_marginEnd="8dp"
+                android:src="@drawable/ic_suggestion_close_button"/>
+
+        </RelativeLayout>
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:singleLine="true"
+            android:textAppearance="@style/TextAppearance.TileTitle"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:textAppearance="@style/TextAppearance.SuggestionSummary" />
+
+        <Button
+            android:id="@android:id/primary"
+            style="@style/ActionPrimaryButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12dp"
+            android:text="@string/suggestion_button_text" />
+
+    </LinearLayout>
+
+</android.support.v7.widget.CardView>
diff --git a/res/values-sw400dp/dimens.xml b/res/values-sw400dp/dimens.xml
index 35a25d8..4f13e09 100755
--- a/res/values-sw400dp/dimens.xml
+++ b/res/values-sw400dp/dimens.xml
@@ -21,4 +21,11 @@
 
     <dimen name="support_escalation_card_padding_start">56dp</dimen>
     <dimen name="support_escalation_card_padding_end">56dp</dimen>
+
+    <!-- Suggestion cards-->
+    <dimen name="suggestion_card_width_one_card">380dp</dimen>
+    <dimen name="suggestion_card_width_two_cards">184dp</dimen>
+    <dimen name="suggestion_card_width_multiple_cards">176dp</dimen>
+    <dimen name="suggestion_card_padding_bottom_one_card">22dp</dimen>
+
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 00d9705..f8205e3 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -300,4 +300,11 @@
     <dimen name="suggestion_condition_header_padding_collapsed">10dp</dimen>
     <dimen name="suggestion_condition_header_padding_expanded">5dp</dimen>
 
+    <!-- Suggestion cards-->
+    <dimen name="suggestion_card_width_one_card">328dp</dimen>
+    <dimen name="suggestion_card_width_two_cards">158dp</dimen>
+    <dimen name="suggestion_card_width_multiple_cards">152dp</dimen>
+    <dimen name="suggestion_card_margin_end">12dp</dimen>
+    <dimen name="suggestion_card_padding_bottom_one_card">16dp</dimen>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cfefa55..c9ecf9d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8401,6 +8401,9 @@
     <string name="condition_summary" translatable="false"><xliff:g name="count" example="3">%1$d</xliff:g></string>
 
     <!-- Title for the suggestions section on the dashboard [CHAR LIMIT=30] -->
+    <string name="suggestions_title_v2">Suggested for You</string>
+
+    <!-- Title for the suggestions section on the dashboard [CHAR LIMIT=30] -->
     <string name="suggestions_title">Suggestions</string>
 
     <!-- Summary for the suggestions section on the dashboard, representing number of suggestions. [CHAR LIMIT=10] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 579ee48..9555d5e 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -316,6 +316,13 @@
     <style name="TextAppearance.RecentsTitle" parent="TextAppearance.CategoryTitle" />
     <style name="TextAppearance.ResultTitle" parent="TextAppearance.CategoryTitle" />
 
+    <style name="TextAppearance.SuggestionHeader"
+           parent="@android:style/TextAppearance.Material.Subhead">
+        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">?android:attr/colorAccent</item>
+    </style>
+
     <style name="TextAppearance.SuggestionTitle"
            parent="@android:style/TextAppearance.Material.Subhead">
         <item name="android:fontFamily">sans-serif-medium</item>
diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java
index 7d9b331..dd9d316 100644
--- a/src/com/android/settings/core/FeatureFlags.java
+++ b/src/com/android/settings/core/FeatureFlags.java
@@ -27,4 +27,5 @@
     public static final String BATTERY_DISPLAY_APP_LIST = "settings_battery_display_app_list";
     public static final String SECURITY_SETTINGS_V2 = "settings_security_settings_v2";
     public static final String ZONE_PICKER_V2 = "settings_zone_picker_v2";
+    public static final String SUGGESTION_UI_V2 = "settings_suggestion_ui_v2";
 }
diff --git a/src/com/android/settings/dashboard/DashboardAdapterV2.java b/src/com/android/settings/dashboard/DashboardAdapterV2.java
new file mode 100644
index 0000000..cc511c5
--- /dev/null
+++ b/src/com/android/settings/dashboard/DashboardAdapterV2.java
@@ -0,0 +1,442 @@
+/*
+ * 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.settings.dashboard;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.R.id;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
+import com.android.settings.dashboard.DashboardDataV2.ConditionHeaderData;
+import com.android.settings.dashboard.conditional.Condition;
+import com.android.settings.dashboard.conditional.ConditionAdapterV2;
+import com.android.settings.dashboard.suggestions.SuggestionAdapterV2;
+import com.android.settings.dashboard.suggestions.SuggestionControllerMixin;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+
+import java.util.List;
+
+public class DashboardAdapterV2 extends RecyclerView.Adapter<DashboardAdapterV2.DashboardItemHolder>
+        implements SummaryLoader.SummaryConsumer, SuggestionAdapterV2.Callback, LifecycleObserver,
+        OnSaveInstanceState {
+    public static final String TAG = "DashboardAdapterV2";
+    private static final String STATE_CATEGORY_LIST = "category_list";
+
+    @VisibleForTesting
+    static final String STATE_CONDITION_EXPANDED = "condition_expanded";
+
+    private final IconCache mCache;
+    private final Context mContext;
+    private final MetricsFeatureProvider mMetricsFeatureProvider;
+    private final DashboardFeatureProvider mDashboardFeatureProvider;
+    private boolean mFirstFrameDrawn;
+    private RecyclerView mRecyclerView;
+    private SuggestionAdapterV2 mSuggestionAdapter;
+
+    @VisibleForTesting
+    DashboardDataV2 mDashboardData;
+
+    private View.OnClickListener mTileClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            //TODO: get rid of setTag/getTag
+            mDashboardFeatureProvider.openTileIntent((Activity) mContext, (Tile) v.getTag());
+        }
+    };
+
+    public DashboardAdapterV2(Context context, Bundle savedInstanceState,
+            List<Condition> conditions, SuggestionControllerMixin suggestionControllerMixin,
+            Lifecycle lifecycle) {
+
+        DashboardCategory category = null;
+        boolean conditionExpanded = false;
+
+        mContext = context;
+        final FeatureFactory factory = FeatureFactory.getFactory(context);
+        mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
+        mDashboardFeatureProvider = factory.getDashboardFeatureProvider(context);
+        mCache = new IconCache(context);
+        mSuggestionAdapter = new SuggestionAdapterV2(mContext, suggestionControllerMixin,
+            savedInstanceState, this /* callback */, lifecycle);
+
+        setHasStableIds(true);
+
+        if (savedInstanceState != null) {
+            category = savedInstanceState.getParcelable(STATE_CATEGORY_LIST);
+            conditionExpanded = savedInstanceState.getBoolean(
+                    STATE_CONDITION_EXPANDED, conditionExpanded);
+        }
+
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
+
+        mDashboardData = new DashboardDataV2.Builder()
+            .setConditions(conditions)
+            .setSuggestions(mSuggestionAdapter.getSuggestions())
+            .setCategory(category)
+            .setConditionExpanded(conditionExpanded)
+            .build();
+    }
+
+    public void setSuggestions(List<Suggestion> data) {
+        final DashboardDataV2 prevData = mDashboardData;
+        mDashboardData = new DashboardDataV2.Builder(prevData)
+                .setSuggestions(data)
+                .build();
+        notifyDashboardDataChanged(prevData);
+    }
+
+    public void setCategory(DashboardCategory category) {
+        tintIcons(category);
+        final DashboardDataV2 prevData = mDashboardData;
+        Log.d(TAG, "adapter setCategory called");
+        mDashboardData = new DashboardDataV2.Builder(prevData)
+                .setCategory(category)
+                .build();
+        notifyDashboardDataChanged(prevData);
+    }
+
+    public void setConditions(List<Condition> conditions) {
+        final DashboardDataV2 prevData = mDashboardData;
+        Log.d(TAG, "adapter setConditions called");
+        mDashboardData = new DashboardDataV2.Builder(prevData)
+                .setConditions(conditions)
+                .build();
+        notifyDashboardDataChanged(prevData);
+    }
+
+    @Override
+    public void onSuggestionClosed(Suggestion suggestion) {
+        final List<Suggestion> list = mDashboardData.getSuggestions();
+        if (list == null || list.size() == 0) {
+            return;
+        }
+        if (list.size() == 1) {
+            // The only suggestion is dismissed, and the the empty suggestion container will
+            // remain as the dashboard item. Need to refresh the dashboard list.
+            final DashboardDataV2 prevData = mDashboardData;
+            mDashboardData = new DashboardDataV2.Builder(prevData)
+                .setSuggestions(null)
+                .build();
+            notifyDashboardDataChanged(prevData);
+        } else {
+            mSuggestionAdapter.removeSuggestion(suggestion);
+        }
+    }
+
+    @Override
+    public void notifySummaryChanged(Tile tile) {
+        final int position = mDashboardData.getPositionByTile(tile);
+        if (position != DashboardDataV2.POSITION_NOT_FOUND) {
+            // Since usually tile in parameter and tile in mCategories are same instance,
+            // which is hard to be detected by DiffUtil, so we notifyItemChanged directly.
+            notifyItemChanged(position, mDashboardData.getItemTypeByPosition(position));
+        }
+    }
+
+    @Override
+    public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
+        if (viewType == R.layout.suggestion_condition_header) {
+            return new ConditionHeaderHolder(view);
+        }
+        if (viewType == R.layout.condition_container) {
+            return new ConditionContainerHolder(view);
+        }
+        if (viewType == R.layout.suggestion_container) {
+            return new SuggestionContainerHolder(view);
+        }
+        return new DashboardItemHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(DashboardItemHolder holder, int position) {
+        final int type = mDashboardData.getItemTypeByPosition(position);
+        switch (type) {
+            case R.layout.dashboard_tile:
+                final Tile tile = (Tile) mDashboardData.getItemEntityByPosition(position);
+                onBindTile((DashboardItemHolder) holder, tile);
+                holder.itemView.setTag(tile);
+                holder.itemView.setOnClickListener(mTileClickListener);
+                break;
+            case R.layout.suggestion_container:
+                onBindSuggestion((SuggestionContainerHolder) holder, position);
+                break;
+            case R.layout.condition_container:
+                onBindCondition((ConditionContainerHolder) holder, position);
+                break;
+            case R.layout.suggestion_condition_header:
+                onBindConditionHeader((ConditionHeaderHolder) holder,
+                        (ConditionHeaderData) mDashboardData.getItemEntityByPosition(position));
+                break;
+            case R.layout.suggestion_condition_footer:
+                holder.itemView.setOnClickListener(v -> {
+                    mMetricsFeatureProvider.action(mContext,
+                            MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);
+                    DashboardDataV2 prevData = mDashboardData;
+                    mDashboardData = new DashboardDataV2.Builder(prevData).
+                        setConditionExpanded(false).build();
+                    notifyDashboardDataChanged(prevData);
+                    scrollToTopOfConditions();
+                });
+                break;
+        }
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mDashboardData.getItemIdByPosition(position);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mDashboardData.getItemTypeByPosition(position);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mDashboardData.size();
+    }
+
+    @Override
+    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+        super.onAttachedToRecyclerView(recyclerView);
+        // save the view so that we can scroll it when expanding/collapsing the suggestion and
+        // conditions.
+        mRecyclerView = recyclerView;
+    }
+
+    public Object getItem(long itemId) {
+        return mDashboardData.getItemEntityById(itemId);
+    }
+
+    public Suggestion getSuggestion(int position) {
+        return mSuggestionAdapter.getSuggestion(position);
+    }
+
+    @VisibleForTesting
+    void notifyDashboardDataChanged(DashboardDataV2 prevData) {
+        if (mFirstFrameDrawn && prevData != null) {
+            final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DashboardDataV2
+                    .ItemsDataDiffCallback(prevData.getItemList(), mDashboardData.getItemList()));
+            diffResult.dispatchUpdatesTo(this);
+        } else {
+            mFirstFrameDrawn = true;
+            notifyDataSetChanged();
+        }
+    }
+
+    @VisibleForTesting
+    void onBindConditionHeader(final ConditionHeaderHolder holder, ConditionHeaderData data) {
+        holder.icon.setImageIcon(data.conditionIcons.get(0));
+        if (data.conditionCount == 1) {
+            holder.title.setText(data.title);
+            holder.summary.setText(null);
+            holder.icons.setVisibility(View.INVISIBLE);
+        } else {
+            holder.title.setText(null);
+            holder.summary.setText(
+                mContext.getString(R.string.condition_summary, data.conditionCount));
+            updateConditionIcons(data.conditionIcons, holder.icons);
+            holder.icons.setVisibility(View.VISIBLE);
+        }
+
+        holder.itemView.setOnClickListener(v -> {
+            mMetricsFeatureProvider.action(mContext,
+                MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true);
+            final DashboardDataV2 prevData = mDashboardData;
+            mDashboardData = new DashboardDataV2.Builder(prevData)
+                    .setConditionExpanded(true).build();
+            notifyDashboardDataChanged(prevData);
+            scrollToTopOfConditions();
+        });
+    }
+
+    @VisibleForTesting
+    void onBindCondition(final ConditionContainerHolder holder, int position) {
+        final ConditionAdapterV2 adapter = new ConditionAdapterV2(mContext,
+            (List<Condition>) mDashboardData.getItemEntityByPosition(position),
+            mDashboardData.isConditionExpanded());
+        adapter.addDismissHandling(holder.data);
+        holder.data.setAdapter(adapter);
+        holder.data.setLayoutManager(new LinearLayoutManager(mContext));
+    }
+
+    @VisibleForTesting
+    void onBindSuggestion(final SuggestionContainerHolder holder, int position) {
+        // If there is suggestions to show, it will be at position 0 as we don't show the suggestion
+        // header anymore.
+        final List<Suggestion> suggestions = mDashboardData.getSuggestions();
+        final int suggestionCount = suggestions.size();
+        if (suggestions != null && suggestionCount > 0) {
+            holder.summary.setText(""+suggestionCount);
+            mSuggestionAdapter.setSuggestions(suggestions);
+            holder.data.setAdapter(mSuggestionAdapter);
+        }
+        final LinearLayoutManager layoutManager = new LinearLayoutManager(mContext);
+        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
+        holder.data.setLayoutManager(layoutManager);
+    }
+
+    private void onBindTile(DashboardItemHolder holder, Tile tile) {
+        holder.icon.setImageDrawable(mCache.getIcon(tile.icon));
+        holder.title.setText(tile.title);
+        if (!TextUtils.isEmpty(tile.summary)) {
+            holder.summary.setText(tile.summary);
+            holder.summary.setVisibility(View.VISIBLE);
+        } else {
+            holder.summary.setVisibility(View.GONE);
+        }
+    }
+
+    private void tintIcons(DashboardCategory category) {
+        if (!mDashboardFeatureProvider.shouldTintIcon()) {
+            return;
+        }
+        // TODO: Better place for tinting?
+        final TypedArray a = mContext.obtainStyledAttributes(new int[]{
+                android.R.attr.colorControlNormal});
+        final int tintColor = a.getColor(0, mContext.getColor(R.color.fallback_tintColor));
+        a.recycle();
+        if (category != null) {
+            for (Tile tile : category.getTiles()) {
+                if (tile.isIconTintable) {
+                    // If this drawable is tintable, tint it to match the color.
+                    tile.icon.setTint(tintColor);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        final DashboardCategory category = mDashboardData.getCategory();
+        if (category != null) {
+            outState.putParcelable(STATE_CATEGORY_LIST, category);
+        }
+        outState.putBoolean(STATE_CONDITION_EXPANDED, mDashboardData.isConditionExpanded());
+    }
+
+    private void updateConditionIcons(List<Icon> icons, ViewGroup parent) {
+        if (icons == null || icons.size() < 2) {
+            parent.setVisibility(View.INVISIBLE);
+            return;
+        }
+        final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+        parent.removeAllViews();
+        for (int i = 1, size = icons.size(); i < size; i++) {
+            ImageView icon = (ImageView) inflater.inflate(
+                    R.layout.condition_header_icon, parent, false);
+            icon.setImageIcon(icons.get(i));
+            parent.addView(icon);
+        }
+        parent.setVisibility(View.VISIBLE);
+    }
+
+    private void scrollToTopOfConditions() {
+        mRecyclerView.scrollToPosition(mDashboardData.hasSuggestion() ? 1 : 0);
+    }
+
+    public static class IconCache {
+        private final Context mContext;
+        private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
+
+        public IconCache(Context context) {
+            mContext = context;
+        }
+
+        public Drawable getIcon(Icon icon) {
+            if (icon == null) {
+                return null;
+            }
+            Drawable drawable = mMap.get(icon);
+            if (drawable == null) {
+                drawable = icon.loadDrawable(mContext);
+                mMap.put(icon, drawable);
+            }
+            return drawable;
+        }
+    }
+
+    public static class DashboardItemHolder extends RecyclerView.ViewHolder {
+        public final ImageView icon;
+        public final TextView title;
+        public final TextView summary;
+
+        public DashboardItemHolder(View itemView) {
+            super(itemView);
+            icon = itemView.findViewById(android.R.id.icon);
+            title = itemView.findViewById(android.R.id.title);
+            summary = itemView.findViewById(android.R.id.summary);
+        }
+    }
+
+    public static class ConditionHeaderHolder extends DashboardItemHolder {
+        public final LinearLayout icons;
+        public final ImageView expandIndicator;
+
+        public ConditionHeaderHolder(View itemView) {
+            super(itemView);
+            icons = itemView.findViewById(id.additional_icons);
+            expandIndicator = itemView.findViewById(id.expand_indicator);
+        }
+    }
+
+    public static class ConditionContainerHolder extends DashboardItemHolder {
+        public final RecyclerView data;
+
+        public ConditionContainerHolder(View itemView) {
+            super(itemView);
+            data = itemView.findViewById(id.data);
+        }
+    }
+
+    public static class SuggestionContainerHolder extends DashboardItemHolder {
+        public final RecyclerView data;
+
+        public SuggestionContainerHolder(View itemView) {
+            super(itemView);
+            data = itemView.findViewById(id.suggestion_list);
+        }
+    }
+
+}
diff --git a/src/com/android/settings/dashboard/DashboardDataV2.java b/src/com/android/settings/dashboard/DashboardDataV2.java
new file mode 100644
index 0000000..e25ee05
--- /dev/null
+++ b/src/com/android/settings/dashboard/DashboardDataV2.java
@@ -0,0 +1,446 @@
+/*
+ * 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.settings.dashboard;
+
+import android.annotation.IntDef;
+import android.graphics.drawable.Icon;
+import android.service.settings.suggestions.Suggestion;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.util.DiffUtil;
+import android.text.TextUtils;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.conditional.Condition;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Description about data list used in the DashboardAdapter. In the data list each item can be
+ * Condition, suggestion or category tile.
+ * <p>
+ * ItemsData has inner class Item, which represents the Item in data list.
+ */
+public class DashboardDataV2 {
+    public static final int POSITION_NOT_FOUND = -1;
+    public static final int MAX_SUGGESTION_COUNT = 4;
+
+    // stable id for different type of items.
+    @VisibleForTesting
+    static final int STABLE_ID_SUGGESTION_CONTAINER = 0;
+    static final int STABLE_ID_SUGGESTION_CONDITION_DIVIDER = 1;
+    @VisibleForTesting
+    static final int STABLE_ID_CONDITION_HEADER = 2;
+    @VisibleForTesting
+    static final int STABLE_ID_CONDITION_FOOTER = 3;
+    @VisibleForTesting
+    static final int STABLE_ID_CONDITION_CONTAINER = 4;
+
+    private final List<Item> mItems;
+    private final DashboardCategory mCategory;
+    private final List<Condition> mConditions;
+    private final List<Suggestion> mSuggestions;
+    private final boolean mConditionExpanded;
+
+    private DashboardDataV2(Builder builder) {
+        mCategory = builder.mCategory;
+        mConditions = builder.mConditions;
+        mSuggestions = builder.mSuggestions;
+        mConditionExpanded = builder.mConditionExpanded;
+        mItems = new ArrayList<>();
+
+        buildItemsData();
+    }
+
+    public int getItemIdByPosition(int position) {
+        return mItems.get(position).id;
+    }
+
+    public int getItemTypeByPosition(int position) {
+        return mItems.get(position).type;
+    }
+
+    public Object getItemEntityByPosition(int position) {
+        return mItems.get(position).entity;
+    }
+
+    public List<Item> getItemList() {
+        return mItems;
+    }
+
+    public int size() {
+        return mItems.size();
+    }
+
+    public Object getItemEntityById(long id) {
+        for (final Item item : mItems) {
+            if (item.id == id) {
+                return item.entity;
+            }
+        }
+        return null;
+    }
+
+    public DashboardCategory getCategory() {
+        return mCategory;
+    }
+
+    public List<Condition> getConditions() {
+        return mConditions;
+    }
+
+    public List<Suggestion> getSuggestions() {
+        return mSuggestions;
+    }
+
+    public boolean hasSuggestion() {
+        return sizeOf(mSuggestions) > 0;
+    }
+
+    public boolean isConditionExpanded() {
+        return mConditionExpanded;
+    }
+
+    /**
+     * Find the position of the object in mItems list, using the equals method to compare
+     *
+     * @param entity the object that need to be found in list
+     * @return position of the object, return POSITION_NOT_FOUND if object isn't in the list
+     */
+    public int getPositionByEntity(Object entity) {
+        if (entity == null) return POSITION_NOT_FOUND;
+
+        final int size = mItems.size();
+        for (int i = 0; i < size; i++) {
+            final Object item = mItems.get(i).entity;
+            if (entity.equals(item)) {
+                return i;
+            }
+        }
+
+        return POSITION_NOT_FOUND;
+    }
+
+    /**
+     * Find the position of the Tile object.
+     * <p>
+     * First, try to find the exact identical instance of the tile object, if not found,
+     * then try to find a tile has the same title.
+     *
+     * @param tile tile that need to be found
+     * @return position of the object, return INDEX_NOT_FOUND if object isn't in the list
+     */
+    public int getPositionByTile(Tile tile) {
+        final int size = mItems.size();
+        for (int i = 0; i < size; i++) {
+            final Object entity = mItems.get(i).entity;
+            if (entity == tile) {
+                return i;
+            } else if (entity instanceof Tile && tile.title.equals(((Tile) entity).title)) {
+                return i;
+            }
+        }
+
+        return POSITION_NOT_FOUND;
+    }
+
+    /**
+     * Add item into list when {@paramref add} is true.
+     *
+     * @param item     maybe {@link Condition}, {@link Tile}, {@link DashboardCategory} or null
+     * @param type     type of the item, and value is the layout id
+     * @param stableId The stable id for this item
+     * @param add      flag about whether to add item into list
+     */
+    private void addToItemList(Object item, int type, int stableId, boolean add) {
+        if (add) {
+            mItems.add(new Item(item, type, stableId));
+        }
+    }
+
+    /**
+     * Build the mItems list using mConditions, mSuggestions, mCategories data
+     * and mIsShowingAll, mConditionExpanded flag.
+     */
+    private void buildItemsData() {
+        final List<Condition> conditions = getConditionsToShow(mConditions);
+        final boolean hasConditions = sizeOf(conditions) > 0;
+
+        final List<Suggestion> suggestions = getSuggestionsToShow(mSuggestions);
+        final boolean hasSuggestions = sizeOf(suggestions) > 0;
+
+        /* Suggestion container. This is the card view that contains the list of suggestions.
+         * This will be added whenever the suggestion list is not empty */
+        addToItemList(suggestions, R.layout.suggestion_container,
+            STABLE_ID_SUGGESTION_CONTAINER, hasSuggestions);
+
+        /* Divider between suggestion and conditions if both are present. */
+        addToItemList(suggestions, R.layout.horizontal_divider,
+            STABLE_ID_SUGGESTION_CONDITION_DIVIDER, hasSuggestions && hasConditions);
+
+        /* Condition header. This will be present when there is condition and it is collapsed */
+        addToItemList(new ConditionHeaderData(conditions),
+            R.layout.suggestion_condition_header,
+            STABLE_ID_CONDITION_HEADER, hasConditions && !mConditionExpanded);
+
+        /* Condition container. This is the card view that contains the list of conditions.
+         * This will be added whenever the condition list is not empty and expanded */
+        addToItemList(conditions, R.layout.condition_container,
+            STABLE_ID_CONDITION_CONTAINER, hasConditions && mConditionExpanded);
+
+        /* Condition footer. This will be present when there is condition and it is expanded */
+        addToItemList(null /* item */, R.layout.suggestion_condition_footer,
+            STABLE_ID_CONDITION_FOOTER, hasConditions && mConditionExpanded);
+
+        if (mCategory != null) {
+            final List<Tile> tiles = mCategory.getTiles();
+            for (int i = 0; i < tiles.size(); i++) {
+                final Tile tile = tiles.get(i);
+                addToItemList(tile, R.layout.dashboard_tile, Objects.hash(tile.title),
+                    true /* add */);
+            }
+        }
+    }
+
+    private static int sizeOf(List<?> list) {
+        return list == null ? 0 : list.size();
+    }
+
+    private List<Condition> getConditionsToShow(List<Condition> conditions) {
+        if (conditions == null) {
+            return null;
+        }
+        List<Condition> result = new ArrayList<>();
+        final int size = conditions == null ? 0 : conditions.size();
+        for (int i = 0; i < size; i++) {
+            final Condition condition = conditions.get(i);
+            if (condition.shouldShow()) {
+                result.add(condition);
+            }
+        }
+        return result;
+    }
+
+    private List<Suggestion> getSuggestionsToShow(List<Suggestion> suggestions) {
+        if (suggestions == null) {
+            return null;
+        }
+        if (suggestions.size() <= MAX_SUGGESTION_COUNT) {
+            return suggestions;
+        }
+        return suggestions.subList(0, MAX_SUGGESTION_COUNT);
+    }
+
+    /**
+     * Builder used to build the ItemsData
+     */
+    public static class Builder {
+        private DashboardCategory mCategory;
+        private List<Condition> mConditions;
+        private List<Suggestion> mSuggestions;
+        private boolean mConditionExpanded;
+
+        public Builder() {
+        }
+
+        public Builder(DashboardDataV2 dashboardData) {
+            mCategory = dashboardData.mCategory;
+            mConditions = dashboardData.mConditions;
+            mSuggestions = dashboardData.mSuggestions;
+            mConditionExpanded = dashboardData.mConditionExpanded;
+        }
+
+        public Builder setCategory(DashboardCategory category) {
+            this.mCategory = category;
+            return this;
+        }
+
+        public Builder setConditions(List<Condition> conditions) {
+            this.mConditions = conditions;
+            return this;
+        }
+
+        public Builder setSuggestions(List<Suggestion> suggestions) {
+            this.mSuggestions = suggestions;
+            return this;
+        }
+
+        public Builder setConditionExpanded(boolean expanded) {
+            this.mConditionExpanded = expanded;
+            return this;
+        }
+
+        public DashboardDataV2 build() {
+            return new DashboardDataV2(this);
+        }
+    }
+
+    /**
+     * A DiffCallback to calculate the difference between old and new Item
+     * List in DashboardDataV2
+     */
+    public static class ItemsDataDiffCallback extends DiffUtil.Callback {
+        final private List<Item> mOldItems;
+        final private List<Item> mNewItems;
+
+        public ItemsDataDiffCallback(List<Item> oldItems, List<Item> newItems) {
+            mOldItems = oldItems;
+            mNewItems = newItems;
+        }
+
+        @Override
+        public int getOldListSize() {
+            return mOldItems.size();
+        }
+
+        @Override
+        public int getNewListSize() {
+            return mNewItems.size();
+        }
+
+        @Override
+        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+            return mOldItems.get(oldItemPosition).id == mNewItems.get(newItemPosition).id;
+        }
+
+        @Override
+        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+            return mOldItems.get(oldItemPosition).equals(mNewItems.get(newItemPosition));
+        }
+
+    }
+
+    /**
+     * An item contains the data needed in the DashboardDataV2.
+     */
+    static class Item {
+        // valid types in field type
+        private static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile;
+        private static final int TYPE_SUGGESTION_CONTAINER =
+            R.layout.suggestion_container;
+        private static final int TYPE_CONDITION_CONTAINER =
+            R.layout.condition_container;
+        private static final int TYPE_CONDITION_HEADER =
+            R.layout.suggestion_condition_header;
+        private static final int TYPE_CONDITION_FOOTER =
+            R.layout.suggestion_condition_footer;
+        private static final int TYPE_SUGGESTION_CONDITION_DIVIDER = R.layout.horizontal_divider;
+
+        @IntDef({TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_CONTAINER, TYPE_CONDITION_CONTAINER,
+            TYPE_CONDITION_HEADER, TYPE_CONDITION_FOOTER, TYPE_SUGGESTION_CONDITION_DIVIDER})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface ItemTypes {
+        }
+
+        /**
+         * The main data object in item, usually is a {@link Tile}, {@link Condition}
+         * object. This object can also be null when the
+         * item is an divider line. Please refer to {@link #buildItemsData()} for
+         * detail usage of the Item.
+         */
+        public final Object entity;
+
+        /**
+         * The type of item, value inside is the layout id(e.g. R.layout.dashboard_tile)
+         */
+        @ItemTypes
+        public final int type;
+
+        /**
+         * Id of this item, used in the {@link ItemsDataDiffCallback} to identify the same item.
+         */
+        public final int id;
+
+        public Item(Object entity, @ItemTypes int type, int id) {
+            this.entity = entity;
+            this.type = type;
+            this.id = id;
+        }
+
+        /**
+         * Override it to make comparision in the {@link ItemsDataDiffCallback}
+         *
+         * @param obj object to compared with
+         * @return true if the same object or has equal value.
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+
+            if (!(obj instanceof Item)) {
+                return false;
+            }
+
+            final Item targetItem = (Item) obj;
+            if (type != targetItem.type || id != targetItem.id) {
+                return false;
+            }
+
+            switch (type) {
+                case TYPE_DASHBOARD_TILE:
+                    final Tile localTile = (Tile) entity;
+                    final Tile targetTile = (Tile) targetItem.entity;
+
+                    // Only check title and summary for dashboard tile
+                    return TextUtils.equals(localTile.title, targetTile.title)
+                        && TextUtils.equals(localTile.summary, targetTile.summary);
+                case TYPE_SUGGESTION_CONTAINER:
+                case TYPE_CONDITION_CONTAINER:
+                    // If entity is suggestion and contains remote view, force refresh
+                    final List entities = (List) entity;
+                    if (!entities.isEmpty()) {
+                        Object firstEntity = entities.get(0);
+                        if (firstEntity instanceof Tile
+                            && ((Tile) firstEntity).remoteViews != null) {
+                            return false;
+                        }
+                    }
+                    // Otherwise Fall through to default
+                default:
+                    return entity == null ? targetItem.entity == null
+                        : entity.equals(targetItem.entity);
+            }
+        }
+    }
+
+    /**
+     * This class contains the data needed to build the suggestion/condition header. The data can
+     * also be used to check the diff in DiffUtil.Callback
+     */
+    public static class ConditionHeaderData {
+        public final List<Icon> conditionIcons;
+        public final CharSequence title;
+        public final int conditionCount;
+
+        public ConditionHeaderData(List<Condition> conditions) {
+            conditionCount = sizeOf(conditions);
+            title = conditionCount > 0 ? conditions.get(0).getTitle() : null;
+            conditionIcons = new ArrayList<>();
+            for (int i = 0; conditions != null && i < conditions.size(); i++) {
+                final Condition condition = conditions.get(i);
+                conditionIcons.add(condition.getIcon());
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/com/android/settings/dashboard/DashboardFeatureProvider.java b/src/com/android/settings/dashboard/DashboardFeatureProvider.java
index 3ca146b..c493e672 100644
--- a/src/com/android/settings/dashboard/DashboardFeatureProvider.java
+++ b/src/com/android/settings/dashboard/DashboardFeatureProvider.java
@@ -88,4 +88,9 @@
      */
     void openTileIntent(Activity activity, Tile tile);
 
+    /**
+     * Whether or not we should use the v2 of suggestions UI.
+     */
+    boolean useSuggestionUiV2();
+
 }
diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
index 048f6ed..a06fee9 100644
--- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
+++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
@@ -33,12 +33,14 @@
 import android.support.v7.preference.Preference;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Pair;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
+import com.android.settings.core.FeatureFlags;
 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.drawer.CategoryManager;
@@ -213,6 +215,11 @@
         launchIntentOrSelectProfile(activity, tile, intent, MetricsEvent.DASHBOARD_SUMMARY);
     }
 
+    @Override
+    public boolean useSuggestionUiV2() {
+        return FeatureFlagUtils.isEnabled(mContext, FeatureFlags.SUGGESTION_UI_V2);
+    }
+
     private void bindSummary(Preference preference, Tile tile) {
         if (tile.summary != null) {
             preference.setSummary(tile.summary);
diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java
index 61c202e..15b407b 100644
--- a/src/com/android/settings/dashboard/DashboardSummary.java
+++ b/src/com/android/settings/dashboard/DashboardSummary.java
@@ -65,6 +65,7 @@
 
     private FocusRecyclerView mDashboard;
     private DashboardAdapter mAdapter;
+    private DashboardAdapterV2 mAdapterV2;
     private SummaryLoader mSummaryLoader;
     private ConditionManager mConditionManager;
     private LinearLayoutManager mLayoutManager;
@@ -175,8 +176,10 @@
         super.onSaveInstanceState(outState);
         if (mLayoutManager == null) return;
         outState.putInt(EXTRA_SCROLL_POSITION, mLayoutManager.findFirstVisibleItemPosition());
-        if (mAdapter != null) {
-            mAdapter.onSaveInstanceState(outState);
+        if (!mDashboardFeatureProvider.useSuggestionUiV2()) {
+            if (mAdapter != null) {
+                mAdapter.onSaveInstanceState(outState);
+            }
         }
     }
 
@@ -194,11 +197,18 @@
         mDashboard.setLayoutManager(mLayoutManager);
         mDashboard.setHasFixedSize(true);
         mDashboard.setListener(this);
-        mAdapter = new DashboardAdapter(getContext(), bundle, mConditionManager.getConditions(),
-                mSuggestionControllerMixin, this /* SuggestionDismissController.Callback */);
-        mDashboard.setAdapter(mAdapter);
         mDashboard.setItemAnimator(new DashboardItemAnimator());
-        mSummaryLoader.setSummaryConsumer(mAdapter);
+        if (mDashboardFeatureProvider.useSuggestionUiV2()) {
+            mAdapterV2 = new DashboardAdapterV2(getContext(), bundle,
+                mConditionManager.getConditions(), mSuggestionControllerMixin, getLifecycle());
+            mDashboard.setAdapter(mAdapterV2);
+            mSummaryLoader.setSummaryConsumer(mAdapterV2);
+        } else {
+            mAdapter = new DashboardAdapter(getContext(), bundle, mConditionManager.getConditions(),
+                mSuggestionControllerMixin, this /* SuggestionDismissController.Callback */);
+            mDashboard.setAdapter(mAdapter);
+            mSummaryLoader.setSummaryConsumer(mAdapter);
+        }
         ActionBarShadowController.attachToRecyclerView(
                 getActivity().findViewById(R.id.search_bar_container), getLifecycle(), mDashboard);
         rebuildUI();
@@ -237,7 +247,11 @@
         if (mOnConditionsChangedCalled) {
             final boolean scrollToTop =
                     mLayoutManager.findFirstCompletelyVisibleItemPosition() <= 1;
-            mAdapter.setConditions(mConditionManager.getConditions());
+            if (mDashboardFeatureProvider.useSuggestionUiV2()) {
+                mAdapterV2.setConditions(mConditionManager.getConditions());
+            } else {
+                mAdapter.setConditions(mConditionManager.getConditions());
+            }
             if (scrollToTop) {
                 mDashboard.scrollToPosition(0);
             }
@@ -248,7 +262,11 @@
 
     @Override
     public Suggestion getSuggestionAt(int position) {
-        return mAdapter.getSuggestion(position);
+        if (mDashboardFeatureProvider.useSuggestionUiV2()) {
+            return mAdapterV2.getSuggestion(position);
+        } else {
+            return mAdapter.getSuggestion(position);
+        }
     }
 
     @Override
@@ -259,11 +277,20 @@
     @Override
     public void onSuggestionReady(List<Suggestion> suggestions) {
         mStagingSuggestions = suggestions;
-        mAdapter.setSuggestions(suggestions);
-        if (mStagingCategory != null) {
-            Log.d(TAG, "Category has loaded, setting category from suggestionReady");
-            mHandler.removeCallbacksAndMessages(null);
-            mAdapter.setCategory(mStagingCategory);
+        if (mDashboardFeatureProvider.useSuggestionUiV2()) {
+            mAdapterV2.setSuggestions(suggestions);
+            if (mStagingCategory != null) {
+                Log.d(TAG, "Category has loaded, setting category from suggestionReady");
+                mHandler.removeCallbacksAndMessages(null);
+                mAdapterV2.setCategory(mStagingCategory);
+            }
+        } else {
+            mAdapter.setSuggestions(suggestions);
+            if (mStagingCategory != null) {
+                Log.d(TAG, "Category has loaded, setting category from suggestionReady");
+                mHandler.removeCallbacksAndMessages(null);
+                mAdapter.setCategory(mStagingCategory);
+            }
         }
     }
 
@@ -276,14 +303,26 @@
         if (mSuggestionControllerMixin.isSuggestionLoaded()) {
             Log.d(TAG, "Suggestion has loaded, setting suggestion/category");
             ThreadUtils.postOnMainThread(() -> {
-                if (mStagingSuggestions != null) {
-                    mAdapter.setSuggestions(mStagingSuggestions);
+                if (mDashboardFeatureProvider.useSuggestionUiV2()) {
+                    if (mStagingSuggestions != null) {
+                        mAdapterV2.setSuggestions(mStagingSuggestions);
+                    }
+                    mAdapterV2.setCategory(mStagingCategory);
+                } else {
+                    if (mStagingSuggestions != null) {
+                        mAdapter.setSuggestions(mStagingSuggestions);
+                    }
+                    mAdapter.setCategory(mStagingCategory);
                 }
-                mAdapter.setCategory(mStagingCategory);
             });
         } else {
             Log.d(TAG, "Suggestion NOT loaded, delaying setCategory by " + MAX_WAIT_MILLIS + "ms");
-            mHandler.postDelayed(() -> mAdapter.setCategory(mStagingCategory), MAX_WAIT_MILLIS);
+            if (mDashboardFeatureProvider.useSuggestionUiV2()) {
+                mHandler.postDelayed(()
+                    -> mAdapterV2.setCategory(mStagingCategory), MAX_WAIT_MILLIS);
+            } else {
+                mHandler.postDelayed(() -> mAdapter.setCategory(mStagingCategory), MAX_WAIT_MILLIS);
+            }
         }
     }
 }
diff --git a/src/com/android/settings/dashboard/conditional/ConditionAdapterV2.java b/src/com/android/settings/dashboard/conditional/ConditionAdapterV2.java
new file mode 100644
index 0000000..3f3e5c9
--- /dev/null
+++ b/src/com/android/settings/dashboard/conditional/ConditionAdapterV2.java
@@ -0,0 +1,186 @@
+/*
+ * 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.settings.dashboard.conditional;
+
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
+import com.android.settings.dashboard.DashboardAdapterV2.DashboardItemHolder;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.WirelessUtils;
+
+import java.util.List;
+import java.util.Objects;
+
+public class ConditionAdapterV2 extends RecyclerView.Adapter<DashboardItemHolder> {
+    public static final String TAG = "ConditionAdapter";
+
+    private final Context mContext;
+    private final MetricsFeatureProvider mMetricsFeatureProvider;
+    private List<Condition> mConditions;
+    private boolean mExpanded;
+
+    private View.OnClickListener mConditionClickListener = new View.OnClickListener() {
+
+        @Override
+        public void onClick(View v) {
+            //TODO: get rid of setTag/getTag
+            Condition condition = (Condition) v.getTag();
+            mMetricsFeatureProvider.action(mContext,
+                MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
+                condition.getMetricsConstant());
+            condition.onPrimaryClick();
+        }
+    };
+
+    @VisibleForTesting
+    ItemTouchHelper.SimpleCallback mSwipeCallback = new ItemTouchHelper.SimpleCallback(0,
+            ItemTouchHelper.START | ItemTouchHelper.END) {
+        @Override
+        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
+                RecyclerView.ViewHolder target) {
+            return true;
+        }
+
+        @Override
+        public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+            return viewHolder.getItemViewType() == R.layout.condition_tile
+                    ? super.getSwipeDirs(recyclerView, viewHolder) : 0;
+        }
+
+        @Override
+        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
+            Object item = getItem(viewHolder.getItemId());
+            // item can become null when running monkey
+            if (item != null) {
+                ((Condition) item).silence();
+            }
+        }
+    };
+
+    public ConditionAdapterV2(Context context, List<Condition> conditions, boolean expanded) {
+        mContext = context;
+        mConditions = conditions;
+        mExpanded = expanded;
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+
+        setHasStableIds(true);
+    }
+
+    public Object getItem(long itemId) {
+        for (Condition condition : mConditions) {
+            if (Objects.hash(condition.getTitle()) == itemId) {
+                return condition;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
+                viewType, parent, false));
+    }
+
+    @Override
+    public void onBindViewHolder(DashboardItemHolder holder, int position) {
+        bindViews(mConditions.get(position), holder,
+            position == mConditions.size() - 1, mConditionClickListener);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return Objects.hash(mConditions.get(position).getTitle());
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return R.layout.condition_tile;
+    }
+
+    @Override
+    public int getItemCount() {
+        if (mExpanded) {
+            return mConditions.size();
+        }
+        return 0;
+    }
+
+    public void addDismissHandling(final RecyclerView recyclerView) {
+        final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mSwipeCallback);
+        itemTouchHelper.attachToRecyclerView(recyclerView);
+    }
+
+    private void bindViews(final Condition condition,
+            DashboardItemHolder view, boolean isLastItem,
+            View.OnClickListener onClickListener) {
+        if (condition instanceof AirplaneModeCondition) {
+            Log.d(TAG, "Airplane mode condition has been bound with "
+                    + "isActive=" + condition.isActive() + ". Airplane mode is currently " +
+                    WirelessUtils.isAirplaneModeOn(condition.mManager.getContext()));
+        }
+        View card = view.itemView.findViewById(R.id.content);
+        card.setTag(condition);
+        card.setOnClickListener(onClickListener);
+        view.icon.setImageIcon(condition.getIcon());
+        view.title.setText(condition.getTitle());
+
+        CharSequence[] actions = condition.getActions();
+        final boolean hasButtons = actions.length > 0;
+        setViewVisibility(view.itemView, R.id.buttonBar, hasButtons);
+
+        view.summary.setText(condition.getSummary());
+        for (int i = 0; i < 2; i++) {
+            Button button = (Button) view.itemView.findViewById(i == 0
+                    ? R.id.first_action : R.id.second_action);
+            if (actions.length > i) {
+                button.setVisibility(View.VISIBLE);
+                button.setText(actions[i]);
+                final int index = i;
+                button.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        Context context = v.getContext();
+                        FeatureFactory.getFactory(context).getMetricsFeatureProvider()
+                                .action(context, MetricsEvent.ACTION_SETTINGS_CONDITION_BUTTON,
+                                        condition.getMetricsConstant());
+                        condition.onActionClick(index);
+                    }
+                });
+            } else {
+                button.setVisibility(View.GONE);
+            }
+        }
+        setViewVisibility(view.itemView, R.id.divider, !isLastItem);
+    }
+
+    private void setViewVisibility(View containerView, int viewId, boolean visible) {
+        View view = containerView.findViewById(viewId);
+        if (view != null) {
+            view.setVisibility(visible ? View.VISIBLE : View.GONE);
+        }
+    }
+}
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java b/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java
new file mode 100644
index 0000000..89c731f
--- /dev/null
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java
@@ -0,0 +1,231 @@
+/*
+ * 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.settings.dashboard.suggestions;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
+import com.android.settings.dashboard.DashboardAdapterV2.DashboardItemHolder;
+import com.android.settings.dashboard.DashboardAdapterV2.IconCache;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class SuggestionAdapterV2 extends RecyclerView.Adapter<DashboardItemHolder> implements
+        LifecycleObserver, OnSaveInstanceState {
+    public static final String TAG = "SuggestionAdapterV2";
+
+    private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
+    private static final String STATE_SUGGESTION_LIST = "suggestion_list";
+
+    private final Context mContext;
+    private final MetricsFeatureProvider mMetricsFeatureProvider;
+    private final IconCache mCache;
+    private final ArrayList<String> mSuggestionsShownLogged;
+    private final SuggestionControllerMixin mSuggestionControllerMixin;
+    private final Callback mCallback;
+    private final int mMultipleCardsMarginEnd;
+    private final int mWidthSingleCard;
+    private final int mWidthTwoCards;
+    private final int mWidthMultipleCards;
+
+    private List<Suggestion> mSuggestions;
+
+    public interface Callback {
+        /**
+         * Called when the close button of the suggestion card is clicked.
+         */
+        void onSuggestionClosed(Suggestion suggestion);
+    }
+
+    public SuggestionAdapterV2(Context context, SuggestionControllerMixin suggestionControllerMixin,
+            Bundle savedInstanceState, Callback callback, Lifecycle lifecycle) {
+        mContext = context;
+        mSuggestionControllerMixin = suggestionControllerMixin;
+        mCache = new IconCache(context);
+        final FeatureFactory factory = FeatureFactory.getFactory(context);
+        mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
+        mCallback = callback;
+        if (savedInstanceState != null) {
+            mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
+            mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
+                STATE_SUGGESTIONS_SHOWN_LOGGED);
+        } else {
+            mSuggestionsShownLogged = new ArrayList<>();
+        }
+
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
+
+        final Resources res = mContext.getResources();
+        mMultipleCardsMarginEnd = res.getDimensionPixelOffset(R.dimen.suggestion_card_margin_end);
+        mWidthSingleCard = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_one_card);
+        mWidthTwoCards = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_two_cards);
+        mWidthMultipleCards =
+            res.getDimensionPixelOffset(R.dimen.suggestion_card_width_multiple_cards);
+
+        setHasStableIds(true);
+    }
+
+    @Override
+    public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
+                viewType, parent, false));
+    }
+
+    @Override
+    public void onBindViewHolder(DashboardItemHolder holder, int position) {
+        final Suggestion suggestion = mSuggestions.get(position);
+        final String id = suggestion.getId();
+        final int suggestionCount = mSuggestions.size();
+        if (!mSuggestionsShownLogged.contains(id)) {
+            mMetricsFeatureProvider.action(
+                    mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, id);
+            mSuggestionsShownLogged.add(id);
+        }
+        setCardWidthAndMargin(holder, suggestionCount);
+        holder.icon.setImageDrawable(mCache.getIcon(suggestion.getIcon()));
+        holder.title.setText(suggestion.getTitle());
+        holder.title.setSingleLine(suggestionCount == 1);
+
+        if (suggestionCount == 1) {
+            final CharSequence summary = suggestion.getSummary();
+            if (!TextUtils.isEmpty(summary)) {
+                holder.summary.setText(summary);
+                holder.summary.setVisibility(View.VISIBLE);
+            } else {
+                holder.summary.setVisibility(View.GONE);
+            }
+        } else {
+            // Do not show summary if there are more than 1 suggestions
+            holder.summary.setVisibility(View.GONE);
+            holder.title.setMaxLines(3);
+        }
+
+        final ImageView closeButton = holder.itemView.findViewById(R.id.close_button);
+        if (closeButton != null) {
+            if (mCallback != null) {
+                closeButton.setOnClickListener(v -> {
+                    mCallback.onSuggestionClosed(suggestion);
+                });
+            } else {
+                closeButton.setOnClickListener(null);
+            }
+        }
+
+        View clickHandler = holder.itemView;
+        // If a view with @android:id/primary is defined, use that as the click handler
+        // instead.
+        final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
+        if (primaryAction != null) {
+            clickHandler = primaryAction;
+        }
+        clickHandler.setOnClickListener(v -> {
+            mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION, id);
+            try {
+                suggestion.getPendingIntent().send();
+                mSuggestionControllerMixin.launchSuggestion(suggestion);
+            } catch (PendingIntent.CanceledException e) {
+                Log.w(TAG, "Failed to start suggestion " + suggestion.getTitle());
+            }
+        });
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return Objects.hash(mSuggestions.get(position).getId());
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        final Suggestion suggestion = getSuggestion(position);
+        if ((suggestion.getFlags() & Suggestion.FLAG_HAS_BUTTON) != 0) {
+            return R.layout.suggestion_tile_with_button_v2;
+        } else {
+            return R.layout.suggestion_tile_v2;
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSuggestions.size();
+    }
+
+    public Suggestion getSuggestion(int position) {
+        final long itemId = getItemId(position);
+        if (mSuggestions == null) {
+            return null;
+        }
+        for (Suggestion suggestion : mSuggestions) {
+            if (Objects.hash(suggestion.getId()) == itemId) {
+                return suggestion;
+            }
+        }
+        return null;
+    }
+
+    public void removeSuggestion(Suggestion suggestion) {
+        final int position = mSuggestions.indexOf(suggestion);
+        mSuggestions.remove(suggestion);
+        notifyItemRemoved(position);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        if (mSuggestions != null) {
+            outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
+                new ArrayList<>(mSuggestions));
+        }
+        outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
+    }
+
+    public void setSuggestions(List<Suggestion> suggestions) {
+        mSuggestions = suggestions;
+    }
+
+    public List<Suggestion> getSuggestions() {
+        return mSuggestions;
+    }
+
+    private void setCardWidthAndMargin(DashboardItemHolder holder, int suggestionCount) {
+        final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+            suggestionCount == 1
+                ? mWidthSingleCard : suggestionCount == 2 ? mWidthTwoCards : mWidthMultipleCards,
+            LinearLayout.LayoutParams.WRAP_CONTENT);
+        params.setMarginEnd(suggestionCount == 1 ? 0 : mMultipleCardsMarginEnd);
+        holder.itemView.setLayoutParams(params);
+    }
+}
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
index de0c129..db2d0bb 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
@@ -24,6 +24,10 @@
 import com.android.settings.R;
 import com.android.settings.overlay.FeatureFactory;
 
+/**
+ * Deprecated as a close button is provided to dismiss the suggestion.
+ */
+@Deprecated
 public class SuggestionDismissController extends ItemTouchHelper.SimpleCallback {
 
     public interface Callback {
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterV2Test.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterV2Test.java
new file mode 100644
index 0000000..801a8e4
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterV2Test.java
@@ -0,0 +1,263 @@
+/*
+ * 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.settings.dashboard;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Icon;
+import android.service.settings.suggestions.Suggestion;
+import android.support.v7.widget.RecyclerView;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.TestConfig;
+import com.android.settings.dashboard.conditional.Condition;
+import com.android.settings.dashboard.suggestions.SuggestionAdapterV2;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH,
+        sdk = TestConfig.SDK_VERSION,
+        shadows = {
+                SettingsShadowResources.class,
+                SettingsShadowResources.SettingsShadowTheme.class,
+        })
+public class DashboardAdapterV2Test {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private SettingsActivity mContext;
+    @Mock
+    private View mView;
+    @Mock
+    private Condition mCondition;
+    @Mock
+    private Resources mResources;
+    private FakeFeatureFactory mFactory;
+    private DashboardAdapterV2 mDashboardAdapter;
+    private List<Condition> mConditionList;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mFactory = FakeFeatureFactory.setupForTest();
+        when(mFactory.dashboardFeatureProvider.shouldTintIcon()).thenReturn(true);
+
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getQuantityString(any(int.class), any(int.class), any()))
+                .thenReturn("");
+
+        mConditionList = new ArrayList<>();
+        mConditionList.add(mCondition);
+        when(mCondition.shouldShow()).thenReturn(true);
+        mDashboardAdapter = new DashboardAdapterV2(mContext, null /* savedInstanceState */,
+            mConditionList, null /* suggestionControllerMixin */, null /* lifecycle */);
+        when(mView.getTag()).thenReturn(mCondition);
+    }
+
+    @Test
+    public void testSuggestionDismissed_notOnlySuggestion_updateSuggestionOnly() {
+        final DashboardAdapterV2 adapter =
+                spy(new DashboardAdapterV2(mContext, null /* savedInstanceState */,
+            null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */));
+        final List<Suggestion> suggestions = makeSuggestionsV2("pkg1", "pkg2", "pkg3");
+        adapter.setSuggestions(suggestions);
+
+        final RecyclerView data = mock(RecyclerView.class);
+        when(data.getResources()).thenReturn(mResources);
+        when(data.getContext()).thenReturn(mContext);
+        when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
+        final View itemView = mock(View.class);
+        when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
+        when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class));
+        final DashboardAdapterV2.SuggestionContainerHolder holder =
+                new DashboardAdapterV2.SuggestionContainerHolder(itemView);
+
+        adapter.onBindSuggestion(holder, 0);
+
+        final DashboardDataV2 dashboardData = adapter.mDashboardData;
+        reset(adapter); // clear interactions tracking
+
+        final Suggestion suggestionToRemove = suggestions.get(1);
+        adapter.onSuggestionClosed(suggestionToRemove);
+
+        assertThat(adapter.mDashboardData).isEqualTo(dashboardData);
+        assertThat(suggestions.size()).isEqualTo(2);
+        assertThat(suggestions.contains(suggestionToRemove)).isFalse();
+        verify(adapter, never()).notifyDashboardDataChanged(any());
+    }
+
+    @Test
+    public void testSuggestionDismissed_moreThanTwoSuggestions_shouldNotCrash() {
+        final RecyclerView data = new RecyclerView(RuntimeEnvironment.application);
+        final View itemView = mock(View.class);
+        when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
+        when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class));
+        final DashboardAdapterV2.SuggestionContainerHolder holder =
+                new DashboardAdapterV2.SuggestionContainerHolder(itemView);
+        final List<Suggestion> suggestions = makeSuggestionsV2("pkg1", "pkg2", "pkg3", "pkg4");
+        final DashboardAdapterV2 adapter = spy(new DashboardAdapterV2(mContext,
+            null /*savedInstance */, null /* conditions */, null /* suggestionControllerMixin */,
+            null /* lifecycle */));
+        adapter.setSuggestions(suggestions);
+        adapter.onBindSuggestion(holder, 0);
+
+        adapter.onSuggestionClosed(suggestions.get(1));
+
+        // verify operations that access the lists will not cause ConcurrentModificationException
+        assertThat(holder.data.getAdapter().getItemCount()).isEqualTo(3);
+        adapter.setSuggestions(suggestions);
+        // should not crash
+    }
+
+    @Test
+    public void testSuggestionDismissed_onlySuggestion_updateDashboardData() {
+        DashboardAdapterV2 adapter =
+                spy(new DashboardAdapterV2(mContext, null /* savedInstanceState */,
+            null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */));
+        final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
+        adapter.setSuggestions(suggestions);
+        final DashboardDataV2 dashboardData = adapter.mDashboardData;
+        reset(adapter); // clear interactions tracking
+
+        adapter.onSuggestionClosed(suggestions.get(0));
+
+        assertThat(adapter.mDashboardData).isNotEqualTo(dashboardData);
+        verify(adapter).notifyDashboardDataChanged(any());
+    }
+
+    @Test
+    public void testSetCategories_iconTinted() {
+        TypedArray mockTypedArray = mock(TypedArray.class);
+        doReturn(mockTypedArray).when(mContext).obtainStyledAttributes(any(int[].class));
+        doReturn(0x89000000).when(mockTypedArray).getColor(anyInt(), anyInt());
+
+        final DashboardCategory category = new DashboardCategory();
+        final Icon mockIcon = mock(Icon.class);
+        final Tile tile = new Tile();
+        tile.isIconTintable = true;
+        tile.icon = mockIcon;
+        category.addTile(tile);
+
+        mDashboardAdapter.setCategory(category);
+
+        verify(mockIcon).setTint(eq(0x89000000));
+    }
+
+    @Test
+    public void testBindSuggestion_shouldSetSuggestionAdapterAndNoCrash() {
+        mDashboardAdapter = new DashboardAdapterV2(mContext, null /* savedInstanceState */,
+            null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
+        final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
+
+        mDashboardAdapter.setSuggestions(suggestions);
+
+        final RecyclerView data = mock(RecyclerView.class);
+        when(data.getResources()).thenReturn(mResources);
+        when(data.getContext()).thenReturn(mContext);
+        when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
+        final View itemView = mock(View.class);
+        when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
+        when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class));
+        final DashboardAdapterV2.SuggestionContainerHolder holder =
+                new DashboardAdapterV2.SuggestionContainerHolder(itemView);
+
+        mDashboardAdapter.onBindSuggestion(holder, 0);
+
+        verify(data).setAdapter(any(SuggestionAdapterV2.class));
+        // should not crash
+    }
+
+    @Test
+    public void testBindSuggestion_shouldSetSummary() {
+        mDashboardAdapter = new DashboardAdapterV2(mContext, null /* savedInstanceState */,
+            null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
+        final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
+
+        mDashboardAdapter.setSuggestions(suggestions);
+
+        final RecyclerView data = mock(RecyclerView.class);
+        when(data.getResources()).thenReturn(mResources);
+        when(data.getContext()).thenReturn(mContext);
+        when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
+        final View itemView = mock(View.class);
+        when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
+        final TextView summary = mock(TextView.class);
+        when(itemView.findViewById(android.R.id.summary)).thenReturn(summary);
+        final DashboardAdapterV2.SuggestionContainerHolder holder =
+            new DashboardAdapterV2.SuggestionContainerHolder(itemView);
+
+        mDashboardAdapter.onBindSuggestion(holder, 0);
+
+        verify(summary).setText("1");
+
+        suggestions.addAll(makeSuggestionsV2("pkg2", "pkg3", "pkg4"));
+        mDashboardAdapter.setSuggestions(suggestions);
+
+        mDashboardAdapter.onBindSuggestion(holder, 0);
+
+        verify(summary).setText("4");
+    }
+
+    private List<Suggestion> makeSuggestionsV2(String... pkgNames) {
+        final List<Suggestion> suggestions = new ArrayList<>();
+        for (String pkgName : pkgNames) {
+            final Suggestion suggestion = new Suggestion.Builder(pkgName)
+                    .setPendingIntent(mock(PendingIntent.class))
+                    .build();
+            suggestions.add(suggestion);
+        }
+        return suggestions;
+    }
+
+    private void setupSuggestions(List<Suggestion> suggestions) {
+        final Context context = RuntimeEnvironment.application;
+        mDashboardAdapter.setSuggestions(suggestions);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionAdapterV2Test.java b/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionAdapterV2Test.java
new file mode 100644
index 0000000..5e0ecec
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionAdapterV2Test.java
@@ -0,0 +1,135 @@
+/*
+ * 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.settings.dashboard.conditional;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.dashboard.DashboardAdapterV2;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ConditionAdapterV2Test {
+    @Mock
+    private Condition mCondition1;
+    @Mock
+    private Condition mCondition2;
+
+    private Context mContext;
+    private ConditionAdapterV2 mConditionAdapter;
+    private List<Condition> mOneCondition;
+    private List<Condition> mTwoConditions;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        final CharSequence[] actions = new CharSequence[2];
+        when(mCondition1.getActions()).thenReturn(actions);
+        when(mCondition1.shouldShow()).thenReturn(true);
+        mOneCondition = new ArrayList<>();
+        mOneCondition.add(mCondition1);
+        mTwoConditions = new ArrayList<>();
+        mTwoConditions.add(mCondition1);
+        mTwoConditions.add(mCondition2);
+    }
+
+    @Test
+    public void getItemCount_notExpanded_shouldReturn0() {
+        mConditionAdapter = new ConditionAdapterV2(mContext, mOneCondition, false);
+        assertThat(mConditionAdapter.getItemCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void getItemCount_expanded_shouldReturnListSize() {
+        mConditionAdapter = new ConditionAdapterV2(mContext, mOneCondition, true);
+        assertThat(mConditionAdapter.getItemCount()).isEqualTo(1);
+
+        mConditionAdapter = new ConditionAdapterV2(mContext, mTwoConditions, true);
+        assertThat(mConditionAdapter.getItemCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void getItemViewType_shouldReturnConditionTile() {
+        mConditionAdapter = new ConditionAdapterV2(mContext, mTwoConditions, true);
+        assertThat(mConditionAdapter.getItemViewType(0)).isEqualTo(R.layout.condition_tile);
+    }
+
+    @Test
+    public void onBindViewHolder_shouldSetListener() {
+        final View view = LayoutInflater.from(mContext).inflate(
+            R.layout.condition_tile, new LinearLayout(mContext), true);
+        final DashboardAdapterV2.DashboardItemHolder viewHolder =
+            new DashboardAdapterV2.DashboardItemHolder(view);
+        mConditionAdapter = new ConditionAdapterV2(mContext, mOneCondition, true);
+
+        mConditionAdapter.onBindViewHolder(viewHolder, 0);
+        final View card = view.findViewById(R.id.content);
+        assertThat(card.hasOnClickListeners()).isTrue();
+    }
+
+    @Test
+    public void viewClick_shouldInvokeConditionPrimaryClick() {
+        final View view = LayoutInflater.from(mContext).inflate(
+            R.layout.condition_tile, new LinearLayout(mContext), true);
+        final DashboardAdapterV2.DashboardItemHolder viewHolder =
+            new DashboardAdapterV2.DashboardItemHolder(view);
+        mConditionAdapter = new ConditionAdapterV2(mContext, mOneCondition, true);
+
+        mConditionAdapter.onBindViewHolder(viewHolder, 0);
+        final View card = view.findViewById(R.id.content);
+        card.performClick();
+        verify(mCondition1).onPrimaryClick();
+    }
+
+    @Test
+    public void onSwiped_nullCondition_shouldNotCrash() {
+        final RecyclerView recyclerView = new RecyclerView(mContext);
+        final View view = LayoutInflater.from(mContext).inflate(
+                R.layout.condition_tile, new LinearLayout(mContext), true);
+        final DashboardAdapterV2.DashboardItemHolder viewHolder =
+                new DashboardAdapterV2.DashboardItemHolder(view);
+        mConditionAdapter = new ConditionAdapterV2(mContext, mOneCondition, true);
+        mConditionAdapter.addDismissHandling(recyclerView);
+
+        // do not bind viewholder to simulate the null condition scenario
+        mConditionAdapter.mSwipeCallback.onSwiped(viewHolder, 0);
+        // no crash
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java
index 26940d6..2ecab8d 100644
--- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java
@@ -36,7 +36,6 @@
 import com.android.settings.dashboard.DashboardAdapter;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
-import com.android.settingslib.drawer.Tile;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -62,10 +61,8 @@
     private Context mContext;
     private SuggestionAdapter mSuggestionAdapter;
     private DashboardAdapter.DashboardItemHolder mSuggestionHolder;
-    private List<Tile> mOneSuggestion;
-    private List<Tile> mTwoSuggestions;
-    private List<Suggestion> mOneSuggestionV2;
-    private List<Suggestion> mTwoSuggestionsV2;
+    private List<Suggestion> mOneSuggestion;
+    private List<Suggestion> mTwoSuggestions;
 
     @Before
     public void setUp() {
@@ -73,45 +70,34 @@
         mContext = RuntimeEnvironment.application;
         mFeatureFactory = FakeFeatureFactory.setupForTest();
 
-        final Tile suggestion1 = new Tile();
-        final Tile suggestion2 = new Tile();
-        final Suggestion suggestion1V2 = new Suggestion.Builder("id1")
+        final Suggestion suggestion1 = new Suggestion.Builder("id1")
                 .setTitle("Test suggestion 1")
                 .build();
-        final Suggestion suggestion2V2 = new Suggestion.Builder("id2")
+        final Suggestion suggestion2 = new Suggestion.Builder("id2")
                 .setTitle("Test suggestion 2")
                 .build();
-        suggestion1.title = "Test Suggestion 1";
-        suggestion1.icon = mock(Icon.class);
-        suggestion2.title = "Test Suggestion 2";
-        suggestion2.icon = mock(Icon.class);
         mOneSuggestion = new ArrayList<>();
         mOneSuggestion.add(suggestion1);
         mTwoSuggestions = new ArrayList<>();
         mTwoSuggestions.add(suggestion1);
         mTwoSuggestions.add(suggestion2);
-        mOneSuggestionV2 = new ArrayList<>();
-        mOneSuggestionV2.add(suggestion1V2);
-        mTwoSuggestionsV2 = new ArrayList<>();
-        mTwoSuggestionsV2.add(suggestion1V2);
-        mTwoSuggestionsV2.add(suggestion2V2);
     }
 
     @Test
     public void getItemCount_shouldReturnListSize() {
         mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
-                mOneSuggestionV2, new ArrayList<>());
+                mOneSuggestion, new ArrayList<>());
         assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1);
 
         mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
-                mTwoSuggestionsV2, new ArrayList<>());
+                mTwoSuggestions, new ArrayList<>());
         assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2);
     }
 
     @Test
     public void getItemViewType_shouldReturnSuggestionTile() {
         mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
-                mOneSuggestionV2, new ArrayList<>());
+                mOneSuggestion, new ArrayList<>());
         assertThat(mSuggestionAdapter.getItemViewType(0))
                 .isEqualTo(R.layout.suggestion_tile);
     }
@@ -137,7 +123,7 @@
                 R.layout.suggestion_tile, new LinearLayout(mContext), true));
         mSuggestionHolder = new DashboardAdapter.DashboardItemHolder(view);
         mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
-                mOneSuggestionV2, new ArrayList<>());
+                mOneSuggestion, new ArrayList<>());
 
         // Bind twice
         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
@@ -146,13 +132,13 @@
         // Log once
         verify(mFeatureFactory.metricsFeatureProvider).action(
                 mContext, MetricsProto.MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION,
-                mOneSuggestionV2.get(0).getId());
+                mOneSuggestion.get(0).getId());
     }
 
     @Test
     public void onBindViewHolder_itemViewShouldHandleClick()
             throws PendingIntent.CanceledException {
-        final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
+        final List<Suggestion> suggestions = makeSuggestions("pkg1");
         setupSuggestions(mActivity, suggestions);
 
         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
@@ -164,21 +150,21 @@
 
     @Test
     public void getSuggestions_shouldReturnSuggestionWhenMatch() {
-        final List<Suggestion> suggestionsV2 = makeSuggestionsV2("pkg1");
-        setupSuggestions(mActivity, suggestionsV2);
+        final List<Suggestion> suggestions = makeSuggestions("pkg1");
+        setupSuggestions(mActivity, suggestions);
 
         assertThat(mSuggestionAdapter.getSuggestion(0)).isNotNull();
     }
 
-    private void setupSuggestions(Context context, List<Suggestion> suggestionsV2) {
+    private void setupSuggestions(Context context, List<Suggestion> suggestions) {
         mSuggestionAdapter = new SuggestionAdapter(context, mSuggestionControllerMixin,
-                suggestionsV2, new ArrayList<>());
+                suggestions, new ArrayList<>());
         mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
                 new FrameLayout(RuntimeEnvironment.application),
                 mSuggestionAdapter.getItemViewType(0));
     }
 
-    private List<Suggestion> makeSuggestionsV2(String... pkgNames) {
+    private List<Suggestion> makeSuggestions(String... pkgNames) {
         final List<Suggestion> suggestions = new ArrayList<>();
         for (String pkgName : pkgNames) {
             final Suggestion suggestion = new Suggestion.Builder(pkgName)
diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2Test.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2Test.java
new file mode 100644
index 0000000..2297f07
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2Test.java
@@ -0,0 +1,224 @@
+/*
+ * 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.settings.dashboard.suggestions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.TestConfig;
+import com.android.settings.dashboard.DashboardAdapterV2;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SuggestionAdapterV2Test {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private SettingsActivity mActivity;
+    @Mock
+    private SuggestionControllerMixin mSuggestionControllerMixin;
+    private FakeFeatureFactory mFeatureFactory;
+    private Context mContext;
+    private SuggestionAdapterV2 mSuggestionAdapter;
+    private DashboardAdapterV2.DashboardItemHolder mSuggestionHolder;
+    private List<Suggestion> mOneSuggestion;
+    private List<Suggestion> mTwoSuggestions;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
+
+        final Suggestion suggestion1 = new Suggestion.Builder("id1")
+                .setTitle("Test suggestion 1")
+                .build();
+        final Suggestion suggestion2 = new Suggestion.Builder("id2")
+                .setTitle("Test suggestion 2")
+                .build();
+        mOneSuggestion = new ArrayList<>();
+        mOneSuggestion.add(suggestion1);
+        mTwoSuggestions = new ArrayList<>();
+        mTwoSuggestions.add(suggestion1);
+        mTwoSuggestions.add(suggestion2);
+    }
+
+    @Test
+    public void getItemCount_shouldReturnListSize() {
+        mSuggestionAdapter = new SuggestionAdapterV2(mContext, mSuggestionControllerMixin,
+                null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
+        mSuggestionAdapter.setSuggestions(mOneSuggestion);
+        assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1);
+
+        mSuggestionAdapter.setSuggestions(mTwoSuggestions);
+        assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void getItemViewType_shouldReturnSuggestionTile() {
+        mSuggestionAdapter = new SuggestionAdapterV2(mContext, mSuggestionControllerMixin,
+                null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
+        mSuggestionAdapter.setSuggestions(mOneSuggestion);
+        assertThat(mSuggestionAdapter.getItemViewType(0))
+                .isEqualTo(R.layout.suggestion_tile_v2);
+    }
+
+    @Test
+    public void getItemType_hasButton_shouldReturnSuggestionWithButton() {
+        final List<Suggestion> suggestions = new ArrayList<>();
+        suggestions.add(new Suggestion.Builder("id")
+                .setFlags(Suggestion.FLAG_HAS_BUTTON)
+                .setTitle("123")
+                .setSummary("456")
+                .build());
+        mSuggestionAdapter = new SuggestionAdapterV2(mContext, mSuggestionControllerMixin,
+                null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
+        mSuggestionAdapter.setSuggestions(suggestions);
+
+        assertThat(mSuggestionAdapter.getItemViewType(0))
+                .isEqualTo(R.layout.suggestion_tile_with_button_v2);
+    }
+
+    @Test
+    public void onBindViewHolder_shouldLog() {
+        final View view = spy(LayoutInflater.from(mContext).inflate(
+                R.layout.suggestion_tile, new LinearLayout(mContext), true));
+        mSuggestionHolder = new DashboardAdapterV2.DashboardItemHolder(view);
+        mSuggestionAdapter = new SuggestionAdapterV2(mContext, mSuggestionControllerMixin,
+                null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
+        mSuggestionAdapter.setSuggestions(mOneSuggestion);
+
+        // Bind twice
+        mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
+        mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
+
+        // Log once
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                mContext, MetricsProto.MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION,
+                mOneSuggestion.get(0).getId());
+    }
+
+    @Test
+    public void onBindViewHolder_itemViewShouldHandleClick()
+            throws PendingIntent.CanceledException {
+        final List<Suggestion> suggestions = makeSuggestions("pkg1");
+        setupSuggestions(mActivity, suggestions);
+
+        mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
+        mSuggestionHolder.itemView.performClick();
+
+        verify(mSuggestionControllerMixin).launchSuggestion(suggestions.get(0));
+        verify(suggestions.get(0).getPendingIntent()).send();
+    }
+
+    @Test
+    public void onBindViewHolder_hasButton_buttonShouldHandleClick()
+        throws PendingIntent.CanceledException {
+        final List<Suggestion> suggestions = new ArrayList<>();
+        final PendingIntent pendingIntent = mock(PendingIntent.class);
+        suggestions.add(new Suggestion.Builder("id")
+            .setFlags(Suggestion.FLAG_HAS_BUTTON)
+            .setTitle("123")
+            .setSummary("456")
+            .setPendingIntent(pendingIntent)
+            .build());
+        mSuggestionAdapter = new SuggestionAdapterV2(mContext, mSuggestionControllerMixin,
+            null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
+        mSuggestionAdapter.setSuggestions(suggestions);
+        mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
+            new FrameLayout(RuntimeEnvironment.application),
+            mSuggestionAdapter.getItemViewType(0));
+
+        mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
+        mSuggestionHolder.itemView.findViewById(android.R.id.primary).performClick();
+
+        verify(mSuggestionControllerMixin).launchSuggestion(suggestions.get(0));
+        verify(pendingIntent).send();
+    }
+
+    @Test
+    public void getSuggestions_shouldReturnSuggestionWhenMatch() {
+        final List<Suggestion> suggestions = makeSuggestions("pkg1");
+        setupSuggestions(mActivity, suggestions);
+
+        assertThat(mSuggestionAdapter.getSuggestion(0)).isNotNull();
+    }
+
+    @Test
+    public void onBindViewHolder_closeButtonShouldHandleClick()
+        throws PendingIntent.CanceledException {
+        final List<Suggestion> suggestions = makeSuggestions("pkg1");
+        final SuggestionAdapterV2.Callback callback = mock(SuggestionAdapterV2.Callback.class);
+        mSuggestionAdapter = new SuggestionAdapterV2(mActivity, mSuggestionControllerMixin,
+            null /* savedInstanceState */, callback, null /* lifecycle */);
+        mSuggestionAdapter.setSuggestions(suggestions);
+        mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
+            new FrameLayout(RuntimeEnvironment.application),
+            mSuggestionAdapter.getItemViewType(0));
+
+        mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
+        mSuggestionHolder.itemView.findViewById(R.id.close_button).performClick();
+
+        verify(callback).onSuggestionClosed(suggestions.get(0));
+    }
+
+    private void setupSuggestions(Context context, List<Suggestion> suggestions) {
+        mSuggestionAdapter = new SuggestionAdapterV2(context, mSuggestionControllerMixin,
+                null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
+        mSuggestionAdapter.setSuggestions(suggestions);
+        mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
+                new FrameLayout(RuntimeEnvironment.application),
+                mSuggestionAdapter.getItemViewType(0));
+    }
+
+    private List<Suggestion> makeSuggestions(String... pkgNames) {
+        final List<Suggestion> suggestions = new ArrayList<>();
+        for (String pkgName : pkgNames) {
+            final Suggestion suggestion = new Suggestion.Builder(pkgName)
+                    .setPendingIntent(mock(PendingIntent.class))
+                    .build();
+            suggestions.add(suggestion);
+        }
+        return suggestions;
+    }
+}