From 06e25e75a5b59a1ab49452cc9a096dc558955445 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Mon, 13 Aug 2018 12:10:19 -0700 Subject: [PATCH] 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 --- .../android/settings/core/FeatureFlags.java | 1 + .../settings/dashboard/DashboardAdapter.java | 49 +++++- .../settings/dashboard/DashboardData.java | 74 ++++++--- .../settings/dashboard/DashboardSummary.java | 79 +++++++--- .../conditional/v2/ConditionAdapter.java | 130 +++++++++++++++ .../conditional/v2/ConditionManager.java | 149 ++++++++++++++++++ .../conditional/v2/ConditionalCard.java | 48 ++++++ .../v2/ConditionalCardController.java | 51 ++++++ .../conditional/v2/DndConditionCard.java | 66 ++++++++ .../v2/DndConditionCardController.java | 113 +++++++++++++ .../dashboard/DashboardAdapterTest.java | 26 +-- .../conditional/v2/ConditionManagerTest.java | 134 ++++++++++++++++ .../v2/DndConditionalCardControllerTest.java | 60 +++++++ .../v2/DndConditionalCardTest.java | 62 ++++++++ 14 files changed, 983 insertions(+), 59 deletions(-) create mode 100644 src/com/android/settings/homepage/conditional/v2/ConditionAdapter.java create mode 100644 src/com/android/settings/homepage/conditional/v2/ConditionManager.java create mode 100644 src/com/android/settings/homepage/conditional/v2/ConditionalCard.java create mode 100644 src/com/android/settings/homepage/conditional/v2/ConditionalCardController.java create mode 100644 src/com/android/settings/homepage/conditional/v2/DndConditionCard.java create mode 100644 src/com/android/settings/homepage/conditional/v2/DndConditionCardController.java create mode 100644 tests/robotests/src/com/android/settings/homepage/conditional/v2/ConditionManagerTest.java create mode 100644 tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardTest.java diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java index c6b9d836828..c8ae82f4859 100644 --- a/src/com/android/settings/core/FeatureFlags.java +++ b/src/com/android/settings/core/FeatureFlags.java @@ -25,4 +25,5 @@ public class FeatureFlags { 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 a2d810b4e9e..488396f2a7c 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.DashboardData.ConditionHeaderData; 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 @@ public class DashboardAdapter extends RecyclerView.Adapter conditions, SuggestionControllerMixinCompat suggestionControllerMixin, - Lifecycle lifecycle) { + List conditions, ConditionManager conditionManager, + SuggestionControllerMixinCompat suggestionControllerMixin, Lifecycle lifecycle) { DashboardCategory category = null; boolean conditionExpanded = false; @@ -96,6 +99,7 @@ public class DashboardAdapter extends RecyclerView.Adapter 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 list = mDashboardData.getSuggestions(); @@ -286,11 +301,31 @@ public class DashboardAdapter extends RecyclerView.Adapter) mDashboardData.getItemEntityByPosition(position), - mDashboardData.isConditionExpanded()); - adapter.addDismissHandling(holder.data); - holder.data.setAdapter(adapter); + final List conditions = (List) mDashboardData.getItemEntityByPosition(position); + final List conditionsV1; + final List 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 9716ae01075..207cc16a705 100644 --- a/src/com/android/settings/dashboard/DashboardData.java +++ b/src/com/android/settings/dashboard/DashboardData.java @@ -25,6 +25,7 @@ import androidx.recyclerview.widget.DiffUtil; 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 @@ public class DashboardData { private final List mItems; private final DashboardCategory mCategory; private final List mConditions; + private final List mConditionsV2; private final List 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 @@ public class DashboardData { * and mIsShowingAll, mConditionExpanded flag. */ private void buildItemsData() { - final List conditions = getConditionsToShow(mConditions); - final boolean hasConditions = sizeOf(conditions) > 0; + final List conditionsV1 = getConditionsToShow(mConditions); + final boolean hasConditionsV1 = sizeOf(conditionsV1) > 0; + final List conditionsV2 = mConditionsV2; + final boolean hasConditionsV2 = sizeOf(conditionsV2) > 0; + final boolean hasConditions = hasConditionsV1 || hasConditionsV2; final List suggestions = getSuggestionsToShow(mSuggestions); final boolean hasSuggestions = sizeOf(suggestions) > 0; @@ -191,25 +197,31 @@ public class DashboardData { /* 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 tiles = mCategory.getTiles(); @@ -260,6 +272,7 @@ public class DashboardData { public static class Builder { private DashboardCategory mCategory; private List mConditions; + private List mConditionsV2; private List mSuggestions; private boolean mConditionExpanded; @@ -269,6 +282,7 @@ public class DashboardData { public Builder(DashboardData dashboardData) { mCategory = dashboardData.mCategory; mConditions = dashboardData.mConditions; + mConditionsV2 = dashboardData.mConditionsV2; mSuggestions = dashboardData.mSuggestions; mConditionExpanded = dashboardData.mConditionExpanded; } @@ -283,6 +297,11 @@ public class DashboardData { return this; } + public Builder setConditionsV2(List conditions) { + this.mConditionsV2 = conditions; + return this; + } + public Builder setSuggestions(List suggestions) { this.mSuggestions = suggestions; return this; @@ -340,17 +359,17 @@ public class DashboardData { // 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 @@ public class DashboardData { // 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 class DashboardData { public final CharSequence title; public final int conditionCount; - public ConditionHeaderData(List 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 conditions, List 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 d7595bf8634..a37ab5a246f 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; 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 @@ public class DashboardSummary extends InstrumentedFragment 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 @@ public class DashboardSummary extends InstrumentedFragment 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 @@ public class DashboardSummary extends InstrumentedFragment ((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 @@ public class DashboardSummary extends InstrumentedFragment ((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 @@ public class DashboardSummary extends InstrumentedFragment 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 @@ public class DashboardSummary extends InstrumentedFragment // 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 00000000000..51d8c47e18c --- /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 { + + private final Context mContext; + private final MetricsFeatureProvider mMetricsFeatureProvider; + private final ConditionManager mConditionManager; + private final List mConditions; + private final boolean mExpanded; + + public ConditionAdapter(Context context, ConditionManager conditionManager, + List 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 00000000000..e6530d31ca2 --- /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 mCandidates; + @VisibleForTesting + final List 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 getDisplayableCards() { + final List 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 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 00000000000..da832b7db86 --- /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 00000000000..7919d7356b4 --- /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 00000000000..8e0fdd59aaf --- /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 00000000000..9e302571c53 --- /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 d3288b6ce7a..a73f4a8700e 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 @@ public class DashboardAdapterTest { 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 class DashboardAdapterTest { 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 suggestions = makeSuggestions("pkg1", "pkg2", "pkg3"); adapter.setSuggestions(suggestions); @@ -144,8 +146,8 @@ public class DashboardAdapterTest { 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 suggestions = makeSuggestions("pkg1"); adapter.setSuggestions(suggestions); final DashboardData dashboardData = adapter.mDashboardData; @@ -161,8 +163,8 @@ public class DashboardAdapterTest { 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 suggestions = makeSuggestions("pkg1"); adapter.setSuggestions(suggestions); @@ -176,7 +178,8 @@ public class DashboardAdapterTest { @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 suggestions = makeSuggestions("pkg1"); mDashboardAdapter.setSuggestions(suggestions); @@ -212,7 +215,8 @@ public class DashboardAdapterTest { .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 @@ public class DashboardAdapterTest { 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 @@ public class DashboardAdapterTest { 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 00000000000..2f96eafb05b --- /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 00000000000..fe4c621cdd4 --- /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 00000000000..1dfbd294a87 --- /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()); + } + +}