New ConditionManager

- Create a new ConditionManager that loads data and filters displayable
  condtionals in memory
- Separete conditional controller logic and ui model
- Plumb new ui model into DashboardAdapater, and create a new
  ConditionAdapter to manage UI.

Bug: 112485407
Test: robotests
Change-Id: If56d141d135341e9b8c2dc80e43c3d40b1de1340
diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java
index c6b9d83..c8ae82f 100644
--- a/src/com/android/settings/core/FeatureFlags.java
+++ b/src/com/android/settings/core/FeatureFlags.java
@@ -25,4 +25,5 @@
     public static final String AUDIO_SWITCHER_SETTINGS = "settings_audio_switcher";
     public static final String DYNAMIC_HOMEPAGE = "settings_dynamic_homepage";
     public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
+    public static final String CONDITION_MANAGER_V2 = "settings_condition_manager_v2";
 }
diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java
index a2d810b..488396f 100644
--- a/src/com/android/settings/dashboard/DashboardAdapter.java
+++ b/src/com/android/settings/dashboard/DashboardAdapter.java
@@ -42,6 +42,8 @@
 import com.android.settings.dashboard.suggestions.SuggestionAdapter;
 import com.android.settings.homepage.conditional.Condition;
 import com.android.settings.homepage.conditional.ConditionAdapter;
+import com.android.settings.homepage.conditional.v2.ConditionManager;
+import com.android.settings.homepage.conditional.v2.ConditionalCard;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.widget.RoundedHomepageIcon;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
@@ -71,6 +73,7 @@
     private boolean mFirstFrameDrawn;
     private RecyclerView mRecyclerView;
     private SuggestionAdapter mSuggestionAdapter;
+    private ConditionManager mConditionManager;
 
     @VisibleForTesting
     DashboardData mDashboardData;
@@ -85,8 +88,8 @@
     };
 
     public DashboardAdapter(Context context, Bundle savedInstanceState,
-            List<Condition> conditions, SuggestionControllerMixinCompat suggestionControllerMixin,
-            Lifecycle lifecycle) {
+            List<Condition> conditions, ConditionManager conditionManager,
+            SuggestionControllerMixinCompat suggestionControllerMixin, Lifecycle lifecycle) {
 
         DashboardCategory category = null;
         boolean conditionExpanded = false;
@@ -96,6 +99,7 @@
         mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
         mDashboardFeatureProvider = factory.getDashboardFeatureProvider(context);
         mCache = new IconCache(context);
+        mConditionManager = conditionManager;
         mSuggestionAdapter = new SuggestionAdapter(mContext, suggestionControllerMixin,
                 savedInstanceState, this /* callback */, lifecycle);
 
@@ -113,6 +117,8 @@
 
         mDashboardData = new DashboardData.Builder()
                 .setConditions(conditions)
+                .setConditionsV2(
+                        conditionManager == null ? null : conditionManager.getDisplayableCards())
                 .setSuggestions(mSuggestionAdapter.getSuggestions())
                 .setCategory(category)
                 .setConditionExpanded(conditionExpanded)
@@ -145,6 +151,15 @@
         notifyDashboardDataChanged(prevData);
     }
 
+    public void setConditionsV2(List<ConditionalCard> conditions) {
+        final DashboardData prevData = mDashboardData;
+        Log.d(TAG, "adapter setConditions called");
+        mDashboardData = new DashboardData.Builder(prevData)
+                .setConditionsV2(conditions)
+                .build();
+        notifyDashboardDataChanged(prevData);
+    }
+
     @Override
     public void onSuggestionClosed(Suggestion suggestion) {
         final List<Suggestion> list = mDashboardData.getSuggestions();
@@ -286,11 +301,31 @@
 
     @VisibleForTesting
     void onBindCondition(final ConditionContainerHolder holder, int position) {
-        final ConditionAdapter adapter = new ConditionAdapter(mContext,
-                (List<Condition>) mDashboardData.getItemEntityByPosition(position),
-                mDashboardData.isConditionExpanded());
-        adapter.addDismissHandling(holder.data);
-        holder.data.setAdapter(adapter);
+        final List conditions = (List) mDashboardData.getItemEntityByPosition(position);
+        final List<Condition> conditionsV1;
+        final List<ConditionalCard> conditionsV2;
+        if (conditions == null || conditions.isEmpty()) {
+            conditionsV1 = null;
+            conditionsV2 = null;
+        } else if (conditions.get(0) instanceof Condition) {
+            conditionsV1 = conditions;
+            conditionsV2 = null;
+        } else {
+            conditionsV1 = null;
+            conditionsV2 = conditions;
+        }
+        if (conditionsV2 == null) {
+            final ConditionAdapter adapter = new ConditionAdapter(mContext,
+                    conditionsV1, mDashboardData.isConditionExpanded());
+            adapter.addDismissHandling(holder.data);
+            holder.data.setAdapter(adapter);
+        } else {
+            final com.android.settings.homepage.conditional.v2.ConditionAdapter adapter =
+                    new com.android.settings.homepage.conditional.v2.ConditionAdapter(
+                            mContext, mConditionManager, conditionsV2,
+                            mDashboardData.isConditionExpanded());
+            holder.data.setAdapter(adapter);
+        }
         holder.data.setLayoutManager(new LinearLayoutManager(mContext));
     }
 
diff --git a/src/com/android/settings/dashboard/DashboardData.java b/src/com/android/settings/dashboard/DashboardData.java
index 9716ae0..207cc16 100644
--- a/src/com/android/settings/dashboard/DashboardData.java
+++ b/src/com/android/settings/dashboard/DashboardData.java
@@ -25,6 +25,7 @@
 
 import com.android.settings.R;
 import com.android.settings.homepage.conditional.Condition;
+import com.android.settings.homepage.conditional.v2.ConditionalCard;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
 
@@ -58,12 +59,14 @@
     private final List<Item> mItems;
     private final DashboardCategory mCategory;
     private final List<Condition> mConditions;
+    private final List<ConditionalCard> mConditionsV2;
     private final List<Suggestion> mSuggestions;
     private final boolean mConditionExpanded;
 
     private DashboardData(Builder builder) {
         mCategory = builder.mCategory;
         mConditions = builder.mConditions;
+        mConditionsV2 = builder.mConditionsV2;
         mSuggestions = builder.mSuggestions;
         mConditionExpanded = builder.mConditionExpanded;
         mItems = new ArrayList<>();
@@ -182,8 +185,11 @@
      * and mIsShowingAll, mConditionExpanded flag.
      */
     private void buildItemsData() {
-        final List<Condition> conditions = getConditionsToShow(mConditions);
-        final boolean hasConditions = sizeOf(conditions) > 0;
+        final List<Condition> conditionsV1 = getConditionsToShow(mConditions);
+        final boolean hasConditionsV1 = sizeOf(conditionsV1) > 0;
+        final List<ConditionalCard> conditionsV2 = mConditionsV2;
+        final boolean hasConditionsV2 = sizeOf(conditionsV2) > 0;
+        final boolean hasConditions = hasConditionsV1 || hasConditionsV2;
 
         final List<Suggestion> suggestions = getSuggestionsToShow(mSuggestions);
         final boolean hasSuggestions = sizeOf(suggestions) > 0;
@@ -191,25 +197,31 @@
         /* 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);
+                STABLE_ID_SUGGESTION_CONTAINER, hasSuggestions);
 
         /* Divider between suggestion and conditions if both are present. */
         addToItemList(null /* item */, R.layout.horizontal_divider,
-            STABLE_ID_SUGGESTION_CONDITION_DIVIDER, hasSuggestions && hasConditions);
+                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.condition_header,
-            STABLE_ID_CONDITION_HEADER, hasConditions && !mConditionExpanded);
+        addToItemList(new ConditionHeaderData(conditionsV1, conditionsV2),
+                R.layout.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);
+        if (hasConditionsV1) {
+            addToItemList(conditionsV1, R.layout.condition_container,
+                    STABLE_ID_CONDITION_CONTAINER, hasConditionsV1 && mConditionExpanded);
+        }
+        if (hasConditionsV2) {
+            addToItemList(conditionsV2, R.layout.condition_container,
+                    STABLE_ID_CONDITION_CONTAINER, hasConditionsV2 && mConditionExpanded);
+        }
 
         /* Condition footer. This will be present when there is condition and it is expanded */
         addToItemList(null /* item */, R.layout.condition_footer,
-            STABLE_ID_CONDITION_FOOTER, hasConditions && mConditionExpanded);
+                STABLE_ID_CONDITION_FOOTER, hasConditions && mConditionExpanded);
 
         if (mCategory != null) {
             final List<Tile> tiles = mCategory.getTiles();
@@ -260,6 +272,7 @@
     public static class Builder {
         private DashboardCategory mCategory;
         private List<Condition> mConditions;
+        private List<ConditionalCard> mConditionsV2;
         private List<Suggestion> mSuggestions;
         private boolean mConditionExpanded;
 
@@ -269,6 +282,7 @@
         public Builder(DashboardData dashboardData) {
             mCategory = dashboardData.mCategory;
             mConditions = dashboardData.mConditions;
+            mConditionsV2 = dashboardData.mConditionsV2;
             mSuggestions = dashboardData.mSuggestions;
             mConditionExpanded = dashboardData.mConditionExpanded;
         }
@@ -283,6 +297,11 @@
             return this;
         }
 
+        public Builder setConditionsV2(List<ConditionalCard> conditions) {
+            this.mConditionsV2 = conditions;
+            return this;
+        }
+
         public Builder setSuggestions(List<Suggestion> suggestions) {
             this.mSuggestions = suggestions;
             return this;
@@ -340,17 +359,17 @@
         // 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;
+                R.layout.suggestion_container;
         private static final int TYPE_CONDITION_CONTAINER =
-            R.layout.condition_container;
+                R.layout.condition_container;
         private static final int TYPE_CONDITION_HEADER =
-            R.layout.condition_header;
+                R.layout.condition_header;
         private static final int TYPE_CONDITION_FOOTER =
-            R.layout.condition_footer;
+                R.layout.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})
+                TYPE_CONDITION_HEADER, TYPE_CONDITION_FOOTER, TYPE_SUGGESTION_CONDITION_DIVIDER})
         @Retention(RetentionPolicy.SOURCE)
         public @interface ItemTypes {
         }
@@ -408,7 +427,7 @@
 
                     // Only check title and summary for dashboard tile
                     return TextUtils.equals(localTile.title, targetTile.title)
-                        && TextUtils.equals(localTile.summary, targetTile.summary);
+                            && TextUtils.equals(localTile.summary, targetTile.summary);
                 case TYPE_SUGGESTION_CONTAINER:
                 case TYPE_CONDITION_CONTAINER:
                     // Fall through to default
@@ -428,13 +447,22 @@
         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());
+        public ConditionHeaderData(List<Condition> conditions, List<ConditionalCard> conditionsV2) {
+            if (conditionsV2 == null) {
+                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());
+                }
+            } else {
+                conditionCount = sizeOf(conditionsV2);
+                title = conditionCount > 0 ? conditionsV2.get(0).getTitle() : null;
+                conditionIcons = new ArrayList<>();
+                for (ConditionalCard card : conditionsV2) {
+                    conditionIcons.add(card.getIcon());
+                }
             }
         }
     }
diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java
index d7595bf..a37ab5a 100644
--- a/src/com/android/settings/dashboard/DashboardSummary.java
+++ b/src/com/android/settings/dashboard/DashboardSummary.java
@@ -38,8 +38,8 @@
 import com.android.settings.core.SettingsBaseActivity.CategoryListener;
 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
 import com.android.settings.homepage.conditional.Condition;
-import com.android.settings.homepage.conditional.ConditionManager;
 import com.android.settings.homepage.conditional.ConditionListener;
+import com.android.settings.homepage.conditional.ConditionManager;
 import com.android.settings.homepage.conditional.FocusRecyclerView;
 import com.android.settings.homepage.conditional.FocusRecyclerView.FocusListener;
 import com.android.settings.overlay.FeatureFactory;
@@ -74,6 +74,7 @@
     private DashboardAdapter mAdapter;
     private SummaryLoader mSummaryLoader;
     private ConditionManager mConditionManager;
+    private com.android.settings.homepage.conditional.v2.ConditionManager mConditionManager2;
     private LinearLayoutManager mLayoutManager;
     private SuggestionControllerMixinCompat mSuggestionControllerMixin;
     private DashboardFeatureProvider mDashboardFeatureProvider;
@@ -123,7 +124,18 @@
         mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE);
 
         mConditionManager = ConditionManager.get(activity, false);
-        getSettingsLifecycle().addObserver(mConditionManager);
+        if (com.android.settings.homepage.conditional.v2.ConditionManager.isEnabled(activity)) {
+            mConditionManager = null;
+            mConditionManager2 =
+                    new com.android.settings.homepage.conditional.v2.ConditionManager(
+                            activity, this /* listener */);
+        } else {
+            mConditionManager = ConditionManager.get(activity, false);
+            mConditionManager2 = null;
+        }
+        if (mConditionManager2 == null) {
+            getSettingsLifecycle().addObserver(mConditionManager);
+        }
         if (savedInstanceState != null) {
             mIsOnCategoriesChangedCalled =
                     savedInstanceState.getBoolean(STATE_CATEGORIES_CHANGE_CALLED);
@@ -147,11 +159,15 @@
         ((SettingsBaseActivity) getActivity()).addCategoryListener(this);
         mSummaryLoader.setListening(true);
         final int metricsCategory = getMetricsCategory();
-        for (Condition c : mConditionManager.getConditions()) {
-            if (c.shouldShow()) {
-                mMetricsFeatureProvider.visible(getContext(), metricsCategory,
-                        c.getMetricsConstant());
+        if (mConditionManager2 == null) {
+            for (Condition c : mConditionManager.getConditions()) {
+                if (c.shouldShow()) {
+                    mMetricsFeatureProvider.visible(getContext(), metricsCategory,
+                            c.getMetricsConstant());
+                }
             }
+        } else {
+            mConditionManager2.startMonitoringStateChange();
         }
         if (DEBUG_TIMING) {
             Log.d(TAG, "onResume took " + (System.currentTimeMillis() - startTime) + " ms");
@@ -164,24 +180,42 @@
 
         ((SettingsBaseActivity) getActivity()).remCategoryListener(this);
         mSummaryLoader.setListening(false);
-        for (Condition c : mConditionManager.getConditions()) {
-            if (c.shouldShow()) {
-                mMetricsFeatureProvider.hidden(getContext(), c.getMetricsConstant());
+        if (mConditionManager2 == null) {
+            for (Condition c : mConditionManager.getConditions()) {
+                if (c.shouldShow()) {
+                    mMetricsFeatureProvider.hidden(getContext(), c.getMetricsConstant());
+                }
             }
         }
+        // Unregister condition listeners.
+        if (mConditionManager != null) {
+            mConditionManager.remListener(this);
+        }
+        if (mConditionManager2 != null) {
+            mConditionManager2.stopMonitoringStateChange();
+        }
     }
 
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         long startTime = System.currentTimeMillis();
-        if (hasWindowFocus) {
-            Log.d(TAG, "Listening for condition changes");
-            mConditionManager.addListener(this);
-            Log.d(TAG, "conditions refreshed");
-            mConditionManager.refreshAll();
+        if (mConditionManager2 == null) {
+            if (hasWindowFocus) {
+                Log.d(TAG, "Listening for condition changes");
+                mConditionManager.addListener(this);
+                Log.d(TAG, "conditions refreshed");
+                mConditionManager.refreshAll();
+            } else {
+                Log.d(TAG, "Stopped listening for condition changes");
+                mConditionManager.remListener(this);
+            }
         } else {
-            Log.d(TAG, "Stopped listening for condition changes");
-            mConditionManager.remListener(this);
+            // TODO(b/112485407): Register monitoring for condition manager v2.
+            if (hasWindowFocus) {
+                mConditionManager2.startMonitoringStateChange();
+            } else {
+                mConditionManager2.stopMonitoringStateChange();
+            }
         }
         if (DEBUG_TIMING) {
             Log.d(TAG, "onWindowFocusChanged took "
@@ -215,7 +249,9 @@
         mDashboard.setListener(this);
         mDashboard.setItemAnimator(new DashboardItemAnimator());
         mAdapter = new DashboardAdapter(getContext(), bundle,
-                mConditionManager.getConditions(), mSuggestionControllerMixin,
+                mConditionManager == null ? null : mConditionManager.getConditions(),
+                mConditionManager2,
+                mSuggestionControllerMixin,
                 getSettingsLifecycle());
         mDashboard.setAdapter(mAdapter);
         mSummaryLoader.setSummaryConsumer(mAdapter);
@@ -255,10 +291,15 @@
         // constructor when we create the view, the first handling is not necessary.
         // But, on the subsequent calls we need to handle it because there might be real changes to
         // conditions.
-        if (mOnConditionsChangedCalled) {
+        if (mOnConditionsChangedCalled || mConditionManager2 != null) {
             final boolean scrollToTop =
                     mLayoutManager.findFirstCompletelyVisibleItemPosition() <= 1;
-            mAdapter.setConditions(mConditionManager.getConditions());
+            if (mConditionManager2 == null) {
+                mAdapter.setConditions(mConditionManager.getConditions());
+            } else {
+                mAdapter.setConditionsV2(mConditionManager2.getDisplayableCards());
+            }
+
             if (scrollToTop) {
                 mDashboard.scrollToPosition(0);
             }
diff --git a/src/com/android/settings/homepage/conditional/v2/ConditionAdapter.java b/src/com/android/settings/homepage/conditional/v2/ConditionAdapter.java
new file mode 100644
index 0000000..51d8c47
--- /dev/null
+++ b/src/com/android/settings/homepage/conditional/v2/ConditionAdapter.java
@@ -0,0 +1,130 @@
+/*
+ * 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.homepage.conditional.v2;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardAdapter;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+import java.util.List;
+
+public class ConditionAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder> {
+
+    private final Context mContext;
+    private final MetricsFeatureProvider mMetricsFeatureProvider;
+    private final ConditionManager mConditionManager;
+    private final List<ConditionalCard> mConditions;
+    private final boolean mExpanded;
+
+    public ConditionAdapter(Context context, ConditionManager conditionManager,
+            List<ConditionalCard> conditions, boolean expanded) {
+        mContext = context;
+        mConditionManager = conditionManager;
+        mConditions = conditions;
+        mExpanded = expanded;
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+
+        setHasStableIds(true);
+    }
+
+    @Override
+    public DashboardAdapter.DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        return new DashboardAdapter.DashboardItemHolder(LayoutInflater.from(parent.getContext())
+                .inflate(viewType, parent, false));
+    }
+
+    @Override
+    public void onBindViewHolder(DashboardAdapter.DashboardItemHolder holder, int position) {
+        final ConditionalCard condition = mConditions.get(position);
+        final boolean isLastItem = position == mConditions.size() - 1;
+        bindViews(condition, holder, isLastItem);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mConditions.get(position).getId();
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return R.layout.condition_tile;
+    }
+
+    @Override
+    public int getItemCount() {
+        if (mExpanded) {
+            return mConditions.size();
+        }
+        return 0;
+    }
+
+    private void bindViews(final ConditionalCard condition,
+            DashboardAdapter.DashboardItemHolder view, boolean isLastItem) {
+        mMetricsFeatureProvider.visible(mContext, MetricsProto.MetricsEvent.DASHBOARD_SUMMARY,
+                condition.getMetricsConstant());
+        view.itemView.findViewById(R.id.content).setOnClickListener(
+                v -> {
+                    mMetricsFeatureProvider.action(mContext,
+                            MetricsProto.MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
+                            condition.getMetricsConstant());
+                    mConditionManager.onPrimaryClick(mContext, condition.getId());
+                });
+        view.icon.setImageDrawable(condition.getIcon());
+        view.title.setText(condition.getTitle());
+        view.summary.setText(condition.getSummary());
+
+        setViewVisibility(view.itemView, R.id.divider, !isLastItem);
+
+        final CharSequence action = condition.getActionText();
+        final boolean hasButtons = !TextUtils.isEmpty(action);
+        setViewVisibility(view.itemView, R.id.buttonBar, hasButtons);
+
+        final Button button = view.itemView.findViewById(R.id.first_action);
+        if (hasButtons) {
+            button.setVisibility(View.VISIBLE);
+            button.setText(action);
+            button.setOnClickListener(v -> {
+                final Context context = v.getContext();
+                mMetricsFeatureProvider.action(
+                        context, MetricsProto.MetricsEvent.ACTION_SETTINGS_CONDITION_BUTTON,
+                        condition.getMetricsConstant());
+                mConditionManager.onActionClick(condition.getId());
+            });
+        } else {
+            button.setVisibility(View.GONE);
+        }
+
+    }
+
+    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/homepage/conditional/v2/ConditionManager.java b/src/com/android/settings/homepage/conditional/v2/ConditionManager.java
new file mode 100644
index 0000000..e6530d3
--- /dev/null
+++ b/src/com/android/settings/homepage/conditional/v2/ConditionManager.java
@@ -0,0 +1,149 @@
+/*
+ * 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.homepage.conditional.v2;
+
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.core.FeatureFlags;
+import com.android.settings.homepage.conditional.ConditionListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConditionManager {
+    private static final String TAG = "ConditionManager";
+
+    @VisibleForTesting
+    final List<ConditionalCard> mCandidates;
+    @VisibleForTesting
+    final List<ConditionalCardController> mCardControllers;
+
+    private final Context mAppContext;
+    private final ConditionListener mListener;
+
+    private boolean mIsListeningToStateChange;
+
+    /**
+     * Whether or not the new condition manager is should be used.
+     */
+    public static boolean isEnabled(Context context) {
+        return FeatureFlagUtils.isEnabled(context, FeatureFlags.CONDITION_MANAGER_V2);
+    }
+
+    public ConditionManager(Context context, ConditionListener listener) {
+        mAppContext = context.getApplicationContext();
+        mCandidates = new ArrayList<>();
+        mCardControllers = new ArrayList<>();
+        mListener = listener;
+        initCandidates();
+    }
+
+    /**
+     * Returns a list of {@link ConditionalCard}s eligible for display.
+     */
+    public List<ConditionalCard> getDisplayableCards() {
+        final List<ConditionalCard> cards = new ArrayList<>();
+        for (ConditionalCard card : mCandidates) {
+            if (getController(card.getId()).isDisplayable()) {
+                cards.add(card);
+            }
+        }
+        return cards;
+    }
+
+    /**
+     * Handler when the card is clicked.
+     *
+     * @see {@link ConditionalCardController#onPrimaryClick(Context)}
+     */
+    public void onPrimaryClick(Context context, long id) {
+        getController(id).onPrimaryClick(context);
+    }
+
+    /**
+     * Handler when the card action is clicked.
+     *
+     * @see {@link ConditionalCardController#onActionClick()}
+     */
+    public void onActionClick(long id) {
+        getController(id).onActionClick();
+        onConditionChanged();
+    }
+
+
+    /**
+     * Start monitoring state change for all conditions
+     */
+    public void startMonitoringStateChange() {
+        if (mIsListeningToStateChange) {
+            Log.d(TAG, "Already listening to condition state changes, skipping");
+            return;
+        }
+        mIsListeningToStateChange = true;
+        for (ConditionalCardController controller : mCardControllers) {
+            controller.startMonitoringStateChange();
+        }
+        // Force a refresh on listener
+        onConditionChanged();
+    }
+
+    /**
+     * Stop monitoring state change for all conditions
+     */
+    public void stopMonitoringStateChange() {
+        if (!mIsListeningToStateChange) {
+            Log.d(TAG, "Not listening to condition state changes, skipping");
+            return;
+        }
+        for (ConditionalCardController controller : mCardControllers) {
+            controller.stopMonitoringStateChange();
+        }
+        mIsListeningToStateChange = false;
+    }
+
+    /**
+     * Called when some conditional card's state has changed
+     */
+    void onConditionChanged() {
+        if (mListener != null) {
+            mListener.onConditionsChanged();
+        }
+    }
+
+    @NonNull
+    <T extends ConditionalCardController> T getController(long id) {
+        for (ConditionalCardController controller : mCardControllers) {
+            if (controller.getId() == id) {
+                return (T) controller;
+            }
+        }
+        throw new IllegalStateException("Cannot find controller for " + id);
+    }
+
+    private void initCandidates() {
+        // Initialize controllers first.
+        mCardControllers.add(new DndConditionCardController(mAppContext, this /* manager */));
+
+        // Initialize ui model later. UI model depends on controller.
+        mCandidates.add(new DndConditionCard(mAppContext, this /* manager */));
+    }
+}
diff --git a/src/com/android/settings/homepage/conditional/v2/ConditionalCard.java b/src/com/android/settings/homepage/conditional/v2/ConditionalCard.java
new file mode 100644
index 0000000..da832b7
--- /dev/null
+++ b/src/com/android/settings/homepage/conditional/v2/ConditionalCard.java
@@ -0,0 +1,48 @@
+/*
+ * 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.homepage.conditional.v2;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * UI Model for a conditional card displayed on homepage.
+ */
+public interface ConditionalCard {
+
+    /**
+     * A stable ID for this card.
+     *
+     * @see {@link ConditionalCardController#getId()}
+     */
+    long getId();
+
+    /**
+     * The text display on the card for click action.
+     */
+    CharSequence getActionText();
+
+    /**
+     * Metrics constant used for logging user interaction.
+     */
+    int getMetricsConstant();
+
+    Drawable getIcon();
+
+    CharSequence getTitle();
+
+    CharSequence getSummary();
+}
diff --git a/src/com/android/settings/homepage/conditional/v2/ConditionalCardController.java b/src/com/android/settings/homepage/conditional/v2/ConditionalCardController.java
new file mode 100644
index 0000000..7919d73
--- /dev/null
+++ b/src/com/android/settings/homepage/conditional/v2/ConditionalCardController.java
@@ -0,0 +1,51 @@
+/*
+ * 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.homepage.conditional.v2;
+
+import android.content.Context;
+
+/**
+ * Data controller for a {@link ConditionalCard}.
+ */
+public interface ConditionalCardController {
+
+    /**
+     * A stable ID for this card.
+     *
+     * @see {@link ConditionalCard#getId()}
+     */
+    long getId();
+
+    /**
+     * Whether or not the card is displayable on the ui.
+     */
+    boolean isDisplayable();
+
+    /**
+     * Handler when the card is clicked.
+     */
+    void onPrimaryClick(Context context);
+
+    /**
+     * Handler when the card action is clicked.
+     */
+    void onActionClick();
+
+    void startMonitoringStateChange();
+
+    void stopMonitoringStateChange();
+}
diff --git a/src/com/android/settings/homepage/conditional/v2/DndConditionCard.java b/src/com/android/settings/homepage/conditional/v2/DndConditionCard.java
new file mode 100644
index 0000000..8e0fdd5
--- /dev/null
+++ b/src/com/android/settings/homepage/conditional/v2/DndConditionCard.java
@@ -0,0 +1,66 @@
+/*
+ * 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.homepage.conditional.v2;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+
+public class DndConditionCard implements ConditionalCard {
+
+    private final Context mAppContext;
+    private final ConditionManager mManager;
+    private final DndConditionCardController mController;
+
+    public DndConditionCard(Context appContext, ConditionManager manager) {
+        mAppContext = appContext;
+        mManager = manager;
+        mController = manager.getController(getId());
+    }
+
+    @Override
+    public long getId() {
+        return DndConditionCardController.ID;
+    }
+
+    @Override
+    public Drawable getIcon() {
+        return mAppContext.getDrawable(R.drawable.ic_do_not_disturb_on_24dp);
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        return mAppContext.getText(R.string.condition_zen_title);
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        return mController.getSummary();
+    }
+
+    @Override
+    public CharSequence getActionText() {
+        return mAppContext.getText(R.string.condition_turn_off);
+    }
+
+    @Override
+    public int getMetricsConstant() {
+        return MetricsProto.MetricsEvent.SETTINGS_CONDITION_DND;
+    }
+}
diff --git a/src/com/android/settings/homepage/conditional/v2/DndConditionCardController.java b/src/com/android/settings/homepage/conditional/v2/DndConditionCardController.java
new file mode 100644
index 0000000..9e30257
--- /dev/null
+++ b/src/com/android/settings/homepage/conditional/v2/DndConditionCardController.java
@@ -0,0 +1,113 @@
+/*
+ * 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.homepage.conditional.v2;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.Settings;
+import android.service.notification.ZenModeConfig;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.notification.ZenModeSettings;
+
+import java.util.Objects;
+
+
+public class DndConditionCardController implements ConditionalCardController {
+    static final int ID = Objects.hash("DndConditionCardController");
+
+    @VisibleForTesting
+    static final IntentFilter DND_FILTER =
+            new IntentFilter(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL);
+
+    private static final String TAG = "DndCondition";
+    private final Context mAppContext;
+    private final ConditionManager mConditionManager;
+    private final NotificationManager mNotificationManager;
+    private final Receiver mReceiver;
+
+    public DndConditionCardController(Context appContext, ConditionManager conditionManager) {
+        mAppContext = appContext;
+        mConditionManager = conditionManager;
+        mNotificationManager = mAppContext.getSystemService(NotificationManager.class);
+        mReceiver = new Receiver();
+    }
+
+    @Override
+    public long getId() {
+        return ID;
+    }
+
+    @Override
+    public boolean isDisplayable() {
+        return mNotificationManager.getZenMode() != Settings.Global.ZEN_MODE_OFF;
+    }
+
+    @Override
+    public void startMonitoringStateChange() {
+        mAppContext.registerReceiver(mReceiver, DND_FILTER);
+    }
+
+    @Override
+    public void stopMonitoringStateChange() {
+        mAppContext.unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    public void onPrimaryClick(Context context) {
+        new SubSettingLauncher(context)
+                .setDestination(ZenModeSettings.class.getName())
+                .setSourceMetricsCategory(MetricsProto.MetricsEvent.DASHBOARD_SUMMARY)
+                .setTitleRes(R.string.zen_mode_settings_title)
+                .launch();
+    }
+
+    @Override
+    public void onActionClick() {
+        mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG);
+    }
+
+    public CharSequence getSummary() {
+        final int zen = mNotificationManager.getZenMode();
+        final ZenModeConfig config;
+        boolean zenModeEnabled = zen != Settings.Global.ZEN_MODE_OFF;
+        if (zenModeEnabled) {
+            config = mNotificationManager.getZenModeConfig();
+        } else {
+            config = null;
+        }
+        return ZenModeConfig.getDescription(mAppContext, zen != Settings.Global.ZEN_MODE_OFF,
+                config, true);
+    }
+
+    public class Receiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL
+                    .equals(intent.getAction())) {
+                mConditionManager.onConditionChanged();
+            }
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
index d3288b6..a73f4a8 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
@@ -104,7 +104,8 @@
         mConditionList.add(mCondition);
         when(mCondition.shouldShow()).thenReturn(true);
         mDashboardAdapter = new DashboardAdapter(mContext, null /* savedInstanceState */,
-                mConditionList, null /* suggestionControllerMixin */, null /* lifecycle */);
+                mConditionList, null /* conditionManager */,
+                null /* suggestionControllerMixin */, null /* lifecycle */);
         when(mView.getTag()).thenReturn(mCondition);
     }
 
@@ -112,7 +113,8 @@
     public void onSuggestionClosed_notOnlySuggestion_updateSuggestionOnly() {
         final DashboardAdapter adapter =
                 spy(new DashboardAdapter(mContext, null /* savedInstanceState */,
-                        null /* conditions */, null /* suggestionControllerMixin */,
+                        null /* conditions */, null /* conditionManager */,
+                        null /* suggestionControllerMixin */,
                         null /* lifecycle */));
         final List<Suggestion> suggestions = makeSuggestions("pkg1", "pkg2", "pkg3");
         adapter.setSuggestions(suggestions);
@@ -144,8 +146,8 @@
     public void onSuggestionClosed_onlySuggestion_updateDashboardData() {
         final DashboardAdapter adapter =
                 spy(new DashboardAdapter(mContext, null /* savedInstanceState */,
-                        null /* conditions */, null /* suggestionControllerMixin */,
-                        null /* lifecycle */));
+                        null /* conditions */, null /* conditionManager */,
+                        null /* suggestionControllerMixin */, null /* lifecycle */));
         final List<Suggestion> suggestions = makeSuggestions("pkg1");
         adapter.setSuggestions(suggestions);
         final DashboardData dashboardData = adapter.mDashboardData;
@@ -161,8 +163,8 @@
     public void onSuggestionClosed_notInSuggestionList_shouldNotUpdateSuggestionList() {
         final DashboardAdapter adapter =
                 spy(new DashboardAdapter(mContext, null /* savedInstanceState */,
-                        null /* conditions */, null /* suggestionControllerMixin */,
-                        null /* lifecycle */));
+                        null /* conditions */, null /* conditionManager */,
+                        null /* suggestionControllerMixin */, null /* lifecycle */));
         final List<Suggestion> suggestions = makeSuggestions("pkg1");
         adapter.setSuggestions(suggestions);
 
@@ -176,7 +178,8 @@
     @Test
     public void onBindSuggestion_shouldSetSuggestionAdapterAndNoCrash() {
         mDashboardAdapter = new DashboardAdapter(mContext, null /* savedInstanceState */,
-                null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
+                null /* conditions */, null /* conditionManager */,
+                null /* suggestionControllerMixin */, null /* lifecycle */);
         final List<Suggestion> suggestions = makeSuggestions("pkg1");
 
         mDashboardAdapter.setSuggestions(suggestions);
@@ -212,7 +215,8 @@
                 .thenReturn(context.getDrawable(R.drawable.ic_settings));
 
         mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */,
-                null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
+                null /* conditions */, null /* conditionManager */,
+                null /* suggestionControllerMixin */, null /* lifecycle */);
         ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache);
         mDashboardAdapter.onBindTile(holder, tile);
 
@@ -232,7 +236,8 @@
         final IconCache iconCache = new IconCache(context);
 
         mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */,
-                null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
+                null /* conditions */, null /* conditionManager */,
+                null /* suggestionControllerMixin */, null /* lifecycle */);
         ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache);
 
         doReturn("another.package").when(context).getPackageName();
@@ -256,7 +261,8 @@
         when(iconCache.getIcon(tile.getIcon(context))).thenReturn(mock(RoundedHomepageIcon.class));
 
         mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */,
-                null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
+                null /* conditions */, null /* conditionManager */,
+                null /* suggestionControllerMixin */, null /* lifecycle */);
         ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache);
 
         mDashboardAdapter.onBindTile(holder, tile);
diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/v2/ConditionManagerTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/v2/ConditionManagerTest.java
new file mode 100644
index 0000000..2f96eaf
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/conditional/v2/ConditionManagerTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.homepage.conditional.v2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import com.android.settings.homepage.conditional.ConditionListener;
+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;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class ConditionManagerTest {
+
+    private static final long ID = 123L;
+
+    @Mock
+    private ConditionalCard mCard;
+    @Mock
+    private ConditionalCardController mController;
+    @Mock
+    private ConditionListener mConditionListener;
+
+    private Context mContext;
+    private ConditionManager mManager;
+
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mManager = new ConditionManager(mContext, mConditionListener);
+
+        assertThat(mManager.mCandidates.size()).isEqualTo(mManager.mCardControllers.size());
+
+        when(mController.getId()).thenReturn(ID);
+        when(mCard.getId()).thenReturn(ID);
+
+        mManager.mCandidates.clear();
+        mManager.mCardControllers.clear();
+        mManager.mCandidates.add(mCard);
+        mManager.mCardControllers.add(mController);
+    }
+
+    @Test
+    public void getDisplayableCards_nothingDisplayable() {
+        assertThat(mManager.getDisplayableCards()).isEmpty();
+    }
+
+    @Test
+    public void getDisplayableCards_hasDisplayable() {
+        when(mController.isDisplayable()).thenReturn(true);
+
+        assertThat(mManager.getDisplayableCards()).hasSize(1);
+    }
+
+    @Test
+    public void onPrimaryClick_shouldRelayToController() {
+        mManager.onPrimaryClick(mContext, ID);
+
+        verify(mController).onPrimaryClick(mContext);
+    }
+
+    @Test
+    public void onActionClick_shouldRelayToController() {
+        mManager.onActionClick(ID);
+
+        verify(mController).onActionClick();
+    }
+
+    @Test
+    public void startMonitoringStateChange_multipleTimes_shouldRegisterOnce() {
+        mManager.startMonitoringStateChange();
+        mManager.startMonitoringStateChange();
+        mManager.startMonitoringStateChange();
+
+        verify(mController).startMonitoringStateChange();
+    }
+
+    @Test
+    public void stopMonitoringStateChange_beforeStart_shouldDoNothing() {
+        mManager.stopMonitoringStateChange();
+        mManager.stopMonitoringStateChange();
+        mManager.stopMonitoringStateChange();
+
+        verify(mController, never()).startMonitoringStateChange();
+        verify(mController, never()).stopMonitoringStateChange();
+    }
+
+    @Test
+    public void stopMonitoringStateChange_multipleTimes_shouldUnregisterOnce() {
+        mManager.startMonitoringStateChange();
+
+        mManager.stopMonitoringStateChange();
+        mManager.stopMonitoringStateChange();
+        mManager.stopMonitoringStateChange();
+
+        verify(mController).startMonitoringStateChange();
+        verify(mController).stopMonitoringStateChange();
+    }
+
+    @Test
+    public void onConditionChanged_shouldNotifyListener() {
+        mManager.onConditionChanged();
+
+        verify(mConditionListener).onConditionsChanged();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardControllerTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardControllerTest.java
new file mode 100644
index 0000000..fe4c621
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardControllerTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.homepage.conditional.v2;
+
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+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;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class DndConditionalCardControllerTest {
+
+    @Mock
+    private ConditionManager mConditionManager;
+    private Context mContext;
+    private DndConditionCardController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        mController = new DndConditionCardController(mContext, mConditionManager);
+    }
+
+    @Test
+    public void cycleMonitoring_shouldRegisterAndUnregisterReceiver() {
+        mController.startMonitoringStateChange();
+        mController.stopMonitoringStateChange();
+
+        verify(mContext).registerReceiver(any(DndConditionCardController.Receiver.class),
+                eq(DndConditionCardController.DND_FILTER));
+        verify(mContext).unregisterReceiver(any(DndConditionCardController.Receiver.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardTest.java
new file mode 100644
index 0000000..1dfbd29
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.homepage.conditional.v2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+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;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class DndConditionalCardTest {
+
+    @Mock
+    private ConditionManager mManager;
+    private DndConditionCardController mController;
+
+    private Context mContext;
+    private DndConditionCard mCard;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+
+        mController = new DndConditionCardController(mContext, mManager);
+        when(mManager.getController(anyLong())).thenReturn(mController);
+
+        mCard = new DndConditionCard(mContext, mManager);
+    }
+
+    @Test
+    public void getId_sameAsController() {
+        assertThat(mCard.getId()).isEqualTo(mController.getId());
+    }
+
+}