Combine settings suggestion and condition.
- Add a flag in dashboard feature provider to specify whether to use the
combined UI for suggestions and conditions.
- Move Conditions below Suggestions.
- Add dashboard entity for condition and suggestion container, and
wrap the condition and suggestion list inside the container. The
container itself will be a single dashboard item, and within it will
be the list of suggestion or condition.
- Add suggestion/condition header that will show the combined info for
the conditions and suggestion data, and have the expand button to
control expanding both the suggestion and condition list.
- Change the individual condition card to be always expanded, and
remove the logic to collapse/expand individual condition card.
- Remove the divider between the action button and condition detail
within each condition card.
- Add suggestion/condition footer for collapsing the whole suggestion and
condition list.
Bug: 37645754
Test: make RunSettingsRoboTests
Change-Id: I86df75f7e4551778f79d730851c03121fd0dcbdf
diff --git a/res/layout/condition_header_icon.xml b/res/layout/condition_header_icon.xml
new file mode 100644
index 0000000..4f93f54
--- /dev/null
+++ b/res/layout/condition_header_icon.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/icon"
+ android:layout_width="@dimen/dashboard_tile_image_size"
+ android:layout_height="@dimen/dashboard_tile_image_size"
+ android:layout_marginStart="0dp"
+ android:layout_marginEnd="24dp"
+ android:tint="?android:attr/colorAccent"
+ android:scaleType="centerInside"/>
diff --git a/res/layout/condition_tile_new_ui.xml b/res/layout/condition_tile_new_ui.xml
new file mode 100644
index 0000000..d8d2284
--- /dev/null
+++ b/res/layout/condition_tile_new_ui.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/colorSecondary"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="@dimen/dashboard_tile_image_size"
+ android:layout_height="@dimen/dashboard_tile_image_size"
+ android:layout_marginStart="14dp"
+ android:layout_marginEnd="24dp"
+ android:tint="?android:attr/colorAccent" />
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/colorAccent" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="62dp"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingBottom="8dp"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:alpha=".7"
+ android:textColor="?android:attr/textColorPrimary" />
+
+ <android.support.v7.widget.ButtonBarLayout
+ android:id="@+id/buttonBar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="62dp"
+ android:paddingBottom="8dp"
+ style="?android:attr/buttonBarStyle"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <Button
+ android:id="@+id/first_action"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="0dp"
+ android:alpha=".8"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/colorAccent"
+ style="?android:attr/buttonBarButtonStyle" />
+
+ <Button
+ android:id="@+id/second_action"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:alpha=".8"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/colorAccent"
+ style="?android:attr/buttonBarButtonStyle" />
+
+ </android.support.v7.widget.ButtonBarLayout>
+
+ <include layout="@layout/horizontal_divider" />
+
+</LinearLayout>
diff --git a/res/layout/horizontal_divider.xml b/res/layout/horizontal_divider.xml
new file mode 100644
index 0000000..e4a277d
--- /dev/null
+++ b/res/layout/horizontal_divider.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<View
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height=".75dp"
+ android:background="?android:attr/dividerHorizontal" />
\ No newline at end of file
diff --git a/res/layout/suggestion_condition_container.xml b/res/layout/suggestion_condition_container.xml
new file mode 100644
index 0000000..089c2c8
--- /dev/null
+++ b/res/layout/suggestion_condition_container.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ style="@style/SuggestionConditionStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:paddingBottom="@dimen/dashboard_padding_bottom">
+
+ <android.support.v7.widget.CardView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:cardUseCompatPadding="true"
+ app:cardElevation="2dp">
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/data"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="none"/>
+
+ </android.support.v7.widget.CardView>
+
+</FrameLayout>
diff --git a/res/layout/suggestion_condition_footer.xml b/res/layout/suggestion_condition_footer.xml
new file mode 100644
index 0000000..4e1bbc8
--- /dev/null
+++ b/res/layout/suggestion_condition_footer.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/SuggestionConditionStyle"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:orientation="horizontal"
+ android:gravity="center|end">
+
+ <ImageView
+ android:id="@+id/collapse_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:padding="16dp"
+ android:src="@drawable/ic_expand_less"/>
+
+</LinearLayout>
diff --git a/res/layout/suggestion_condition_header.xml b/res/layout/suggestion_condition_header.xml
new file mode 100644
index 0000000..a0f73ae
--- /dev/null
+++ b/res/layout/suggestion_condition_header.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/SuggestionConditionStyle"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:layout_centerHorizontal="true">
+
+ <FrameLayout
+ android:id="@android:id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="24dp"
+ android:layout_centerVertical="true">
+
+ <include layout="@layout/condition_header_icon" />
+
+ </FrameLayout>
+
+ <ImageView
+ android:id="@+id/expand_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ android:padding="16dp"
+ android:src="@drawable/ic_expand_more"/>
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_toStartOf="@id/expand_indicator"
+ android:layout_centerVertical="true"
+ android:gravity="end"
+ android:textAppearance="@style/TextAppearance.SuggestionTitle"
+ android:textColor="?android:attr/colorAccent" />
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@android:id/icon_frame"
+ android:layout_toStartOf="@android:id/summary"
+ android:layout_centerVertical="true"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAppearance="@style/TextAppearance.SuggestionTitle"
+ android:textColor="?android:attr/colorAccent" />
+
+ <LinearLayout
+ android:id="@+id/additional_icons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_toStartOf="@android:id/summary"
+ android:layout_toEndOf="@android:id/icon_frame"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"/>
+
+</RelativeLayout>
diff --git a/res/layout/suggestion_tile_new_ui.xml b/res/layout/suggestion_tile_new_ui.xml
new file mode 100644
index 0000000..e2dd13a
--- /dev/null
+++ b/res/layout/suggestion_tile_new_ui.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/white"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:minHeight="@dimen/dashboard_tile_minimum_height">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="@dimen/dashboard_tile_image_size"
+ android:layout_height="@dimen/dashboard_tile_image_size"
+ android:layout_marginStart="14dp"
+ android:layout_marginEnd="24dp"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.TileTitle"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"/>
+
+ <TextView android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Small"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <include layout="@layout/horizontal_divider" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 051bd68..e00ac81 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7999,12 +7999,30 @@
<!-- Summary of condition that night display is on (renamed "Night Light" with title caps). [CHAR LIMIT=NONE] -->
<string name="condition_night_display_summary">Screen is tinted amber. This may help you fall asleep.</string>
+ <!-- Summary for the condition section on the dashboard, representing number of conditions. [CHAR LIMIT=10] -->
+ <string name="condition_summary" translatable="false"><xliff:g name="count" example="3">%1$d</xliff:g></string>
+
<!-- Title for the suggestions section on the dashboard [CHAR LIMIT=30] -->
<string name="suggestions_title">Suggestions</string>
<!-- Summary for the suggestions section on the dashboard, representing number of suggestions. [CHAR LIMIT=10] -->
<string name="suggestions_summary">+<xliff:g name="count" example="3">%1$d</xliff:g></string>
+ <!-- Title for the suggestions section on the dashboard, representing number of suggestions to show when expanded. [CHAR LIMIT=10] -->
+ <string name="suggestions_more_title">+<xliff:g name="count" example="3">%1$d</xliff:g> more</string>
+
+ <!-- Title for the collapsed suggestions section on the dashboard, representing number of suggestions. [CHAR LIMIT=30] -->
+ <plurals name="suggestions_collapsed_title">
+ <item quantity="one">1 suggestion</item>
+ <item quantity="other"><xliff:g id="count" example="10">%1$d</xliff:g> suggestions</item>
+ </plurals>
+
+ <!-- Summary for the collapsed suggestions section on the dashboard, representing number of suggestions. [CHAR LIMIT=30] -->
+ <plurals name="suggestions_collapsed_summary">
+ <item quantity="one">+1 suggestion</item>
+ <item quantity="other">+<xliff:g id="count" example="10">%1$d</xliff:g> suggestions</item>
+ </plurals>
+
<!-- Name of option to remove a suggestion from the list [CHAR LIMIT=30] -->
<string name="suggestion_remove">Remove</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 31c965b..398bdbd 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -454,4 +454,8 @@
<item name="android:visibility">gone</item>
</style>
+ <style name="SuggestionConditionStyle">
+ <item name="android:background">@color/material_grey_300</item>
+ </style>
+
</resources>
diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java
index eb74690..3a768cc 100644
--- a/src/com/android/settings/dashboard/DashboardAdapter.java
+++ b/src/com/android/settings/dashboard/DashboardAdapter.java
@@ -20,11 +20,13 @@
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import android.support.v7.util.DiffUtil;
+import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -33,19 +35,28 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
+import com.android.settings.R.id;
import com.android.settings.SettingsActivity;
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
+import com.android.settings.dashboard.DashboardData.SuggestionConditionHeaderData;
import com.android.settings.dashboard.conditional.Condition;
+import com.android.settings.dashboard.conditional.ConditionAdapter;
import com.android.settings.dashboard.conditional.ConditionAdapterUtils;
+import com.android.settings.dashboard.conditional.FocusRecyclerView;
+import com.android.settings.dashboard.suggestions.SuggestionAdapter;
+import com.android.settings.dashboard.suggestions.SuggestionDismissController;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.Utils;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.suggestions.SuggestionParser;
import java.util.ArrayList;
import java.util.List;
@@ -57,6 +68,7 @@
private static final String STATE_SUGGESTION_MODE = "suggestion_mode";
private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
private static final int DONT_SET_BACKGROUND_ATTR = -1;
+ private static final String STATE_SUGGESTION_CONDITION_MODE = "suggestion_condition_mode";
private final IconCache mCache;
private final Context mContext;
@@ -65,6 +77,12 @@
private final SuggestionFeatureProvider mSuggestionFeatureProvider;
private final ArrayList<String> mSuggestionsShownLogged;
private boolean mFirstFrameDrawn;
+ private boolean mCombineSuggestionAndCondition;
+ private RecyclerView mRecyclerView;
+ private SuggestionParser mSuggestionParser;
+ private SuggestionAdapter mSuggestionAdapter;
+ private SuggestionDismissController mSuggestionDismissHandler;
+ private SuggestionDismissController.Callback mCallback;
@VisibleForTesting
DashboardData mDashboardData;
@@ -81,43 +99,65 @@
@Override
public void onClick(View v) {
- Condition expandedCondition = mDashboardData.getExpandedCondition();
-
- //TODO: get rid of setTag/getTag
- if (v.getTag() == expandedCondition) {
+ if (mCombineSuggestionAndCondition) {
+ Condition condition = (Condition) v.getTag();
+ //TODO: get rid of setTag/getTag
mMetricsFeatureProvider.action(mContext,
+ MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
+ condition.getMetricsConstant());
+ condition.onPrimaryClick();
+ } else {
+ Condition expandedCondition = mDashboardData.getExpandedCondition();
+
+ //TODO: get rid of setTag/getTag
+ if (v.getTag() == expandedCondition) {
+ mMetricsFeatureProvider.action(mContext,
MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
expandedCondition.getMetricsConstant());
- expandedCondition.onPrimaryClick();
- } else {
- expandedCondition = (Condition) v.getTag();
- mMetricsFeatureProvider.action(mContext,
+ expandedCondition.onPrimaryClick();
+ } else {
+ expandedCondition = (Condition) v.getTag();
+ mMetricsFeatureProvider.action(mContext,
MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
expandedCondition.getMetricsConstant());
- updateExpandedCondition(expandedCondition);
+ updateExpandedCondition(expandedCondition);
+ }
}
}
};
+ @Deprecated
public DashboardAdapter(Context context, Bundle savedInstanceState,
- List<Condition> conditions) {
+ List<Condition> conditions) {
+ this(context, savedInstanceState, conditions, null, null);
+ }
+
+ public DashboardAdapter(Context context, Bundle savedInstanceState,
+ List<Condition> conditions, SuggestionParser suggestionParser,
+ SuggestionDismissController.Callback callback) {
List<Tile> suggestions = null;
List<DashboardCategory> categories = null;
int suggestionMode = DashboardData.SUGGESTION_MODE_DEFAULT;
+ int suggestionConditionMode = DashboardData.HEADER_MODE_DEFAULT;
mContext = context;
final FeatureFactory factory = FeatureFactory.getFactory(context);
mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
mDashboardFeatureProvider = factory.getDashboardFeatureProvider(context);
mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);
+ mCombineSuggestionAndCondition = mDashboardFeatureProvider.combineSuggestionAndCondition();
mCache = new IconCache(context);
+ mSuggestionParser = suggestionParser;
+ mCallback = callback;
setHasStableIds(true);
if (savedInstanceState != null) {
suggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
categories = savedInstanceState.getParcelableArrayList(STATE_CATEGORY_LIST);
+ suggestionConditionMode = savedInstanceState.getInt(
+ STATE_SUGGESTION_CONDITION_MODE, suggestionConditionMode);
suggestionMode = savedInstanceState.getInt(
STATE_SUGGESTION_MODE, DashboardData.SUGGESTION_MODE_DEFAULT);
mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
@@ -131,6 +171,8 @@
.setSuggestions(suggestions)
.setCategories(categories)
.setSuggestionMode(suggestionMode)
+ .setCombineSuggestionAndCondition(mCombineSuggestionAndCondition)
+ .setSuggestionConditionMode(suggestionConditionMode)
.build();
}
@@ -167,14 +209,24 @@
.build();
notifyDashboardDataChanged(prevData);
List<Tile> shownSuggestions = null;
- switch (mDashboardData.getSuggestionMode()) {
- case DashboardData.SUGGESTION_MODE_DEFAULT:
+ if (mCombineSuggestionAndCondition) {
+ final int mode = mDashboardData.getSuggestionConditionMode();
+ if (mode == DashboardData.HEADER_MODE_DEFAULT) {
shownSuggestions = suggestions.subList(0,
- Math.min(suggestions.size(), DashboardData.DEFAULT_SUGGESTION_COUNT));
- break;
- case DashboardData.SUGGESTION_MODE_EXPANDED:
+ Math.min(suggestions.size(), DashboardData.DEFAULT_SUGGESTION_COUNT));
+ } else if (mode != DashboardData.HEADER_MODE_COLLAPSED) {
shownSuggestions = suggestions;
- break;
+ }
+ } else {
+ switch (mDashboardData.getSuggestionMode()) {
+ case DashboardData.SUGGESTION_MODE_DEFAULT:
+ shownSuggestions = suggestions.subList(0,
+ Math.min(suggestions.size(), DashboardData.DEFAULT_SUGGESTION_COUNT));
+ break;
+ case DashboardData.SUGGESTION_MODE_EXPANDED:
+ shownSuggestions = suggestions;
+ break;
+ }
}
if (shownSuggestions != null) {
for (Tile suggestion : shownSuggestions) {
@@ -199,10 +251,16 @@
public void setConditions(List<Condition> conditions) {
final DashboardData prevData = mDashboardData;
Log.d(TAG, "adapter setConditions called");
- mDashboardData = new DashboardData.Builder(prevData)
+ if (mCombineSuggestionAndCondition) {
+ mDashboardData = new DashboardData.Builder(prevData)
+ .setConditions(conditions)
+ .build();
+ } else {
+ mDashboardData = new DashboardData.Builder(prevData)
.setConditions(conditions)
.setExpandedCondition(null)
.build();
+ }
notifyDashboardDataChanged(prevData);
}
@@ -218,8 +276,14 @@
@Override
public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
- viewType, parent, false));
+ final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
+ if (viewType == R.layout.suggestion_condition_header) {
+ return new SuggestionAndConditionHeaderHolder(view);
+ }
+ if (viewType == R.layout.suggestion_condition_container) {
+ return new SuggestionAndConditionContainerHolder(view);
+ }
+ return new DashboardItemHolder(view);
}
@Override
@@ -277,6 +341,43 @@
(Condition) mDashboardData.getItemEntityByPosition(position),
holder, isExpanded, mConditionClickListener, v -> onExpandClick(v));
break;
+ case R.layout.suggestion_condition_container:
+ onBindConditionAndSuggestion(
+ (SuggestionAndConditionContainerHolder) holder, position);
+ break;
+ case R.layout.suggestion_condition_header:
+ /* There are 2 different headers for the suggestions/conditions section. To minimize
+ visual animation when expanding and collapsing the suggestions/conditions, we are
+ using the same layout to represent the 2 headers:
+ 1. Suggestion header - when there is any suggestion shown, the suggestion header
+ will be the first item on the section. It only has the text "Suggestion", and
+ do nothing when clicked. This header will not be shown when the section is
+ collapsed, in which case, the SuggestionCondition header will be
+ shown instead to show the summary info.
+ 2. SuggestionCondition header - the header that shows the summary info for the
+ suggestion/condition that is currently hidden. It has the expand button to
+ expand the section. */
+ if (mDashboardData.getDisplayableSuggestionCount() > 0 && position == 1
+ && mDashboardData.getSuggestionConditionMode()
+ != DashboardData.HEADER_MODE_COLLAPSED) {
+ onBindSuggestionHeader((SuggestionAndConditionHeaderHolder) holder);
+ } else {
+ onBindSuggestionConditionHeader((SuggestionAndConditionHeaderHolder) holder,
+ (SuggestionConditionHeaderData)
+ mDashboardData.getItemEntityByPosition(position));
+ }
+ break;
+ case R.layout.suggestion_condition_footer:
+ holder.itemView.setOnClickListener(v -> {
+ mMetricsFeatureProvider.action(mContext,
+ MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);
+ DashboardData prevData = mDashboardData;
+ mDashboardData = new DashboardData.Builder(prevData).setSuggestionConditionMode(
+ DashboardData.HEADER_MODE_COLLAPSED).build();
+ notifyDashboardDataChanged(prevData);
+ mRecyclerView.scrollToPosition(1);
+ });
+ break;
}
}
@@ -295,6 +396,14 @@
return mDashboardData.size();
}
+ @Override
+ public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+ super.onAttachedToRecyclerView(recyclerView);
+ // save the view so that we can scroll it when expanding/collapsing the suggestion and
+ // conditions.
+ mRecyclerView = recyclerView;
+ }
+
public void onPause() {
if (mDashboardData.getSuggestions() == null) {
return;
@@ -310,6 +419,9 @@
mSuggestionsShownLogged.clear();
}
+ // condition card is always expanded in new suggestion/condition UI.
+ // TODO: Remove when completely move to new suggestion/condition UI
+ @Deprecated
public void onExpandClick(View v) {
Condition expandedCondition = mDashboardData.getExpandedCondition();
if (v.getTag() == expandedCondition) {
@@ -330,6 +442,13 @@
return mDashboardData.getItemEntityById(itemId);
}
+ public Tile getSuggestion(int position) {
+ if (mCombineSuggestionAndCondition) {
+ return mSuggestionAdapter.getSuggestion(position);
+ }
+ return (Tile) getItem(getItemId(position));
+ }
+
private void notifyDashboardDataChanged(DashboardData prevData) {
if (mFirstFrameDrawn && prevData != null) {
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DashboardData
@@ -405,17 +524,7 @@
final int suggestionMode;
if (moreSuggestions) {
suggestionMode = DashboardData.SUGGESTION_MODE_EXPANDED;
-
- for (Tile suggestion : mDashboardData.getSuggestions()) {
- final String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
- mContext, suggestion);
- if (!mSuggestionsShownLogged.contains(suggestionId)) {
- mMetricsFeatureProvider.action(
- mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION,
- suggestionId);
- mSuggestionsShownLogged.add(suggestionId);
- }
- }
+ logSuggestions();
} else {
suggestionMode = DashboardData.SUGGESTION_MODE_COLLAPSED;
}
@@ -428,6 +537,130 @@
});
}
+ private void logSuggestions() {
+ for (Tile suggestion : mDashboardData.getSuggestions()) {
+ final String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
+ mContext, suggestion);
+ if (!mSuggestionsShownLogged.contains(suggestionId)) {
+ mMetricsFeatureProvider.action(
+ mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION,
+ suggestionId);
+ mSuggestionsShownLogged.add(suggestionId);
+ }
+ }
+ }
+
+ private void onBindSuggestionHeader(final SuggestionAndConditionHeaderHolder holder) {
+ holder.title.setText(R.string.suggestions_title);
+ holder.title.setTextColor(Color.BLACK);
+ holder.icon.setVisibility(View.INVISIBLE);
+ holder.icons.removeAllViews();
+ holder.icons.setVisibility(View.INVISIBLE);
+ holder.summary.setVisibility(View.INVISIBLE);
+ holder.expandIndicator.setVisibility(View.INVISIBLE);
+ holder.itemView.setOnClickListener(null);
+ }
+
+ private void onBindSuggestionConditionHeader(final SuggestionAndConditionHeaderHolder holder,
+ SuggestionConditionHeaderData data) {
+ final int curMode = mDashboardData.getSuggestionConditionMode();
+ final int nextMode = data.hiddenSuggestionCount > 0 && data.conditionCount > 0
+ && curMode != DashboardData.HEADER_MODE_SUGGESTION_EXPANDED
+ ? DashboardData.HEADER_MODE_SUGGESTION_EXPANDED
+ : DashboardData.HEADER_MODE_FULLY_EXPANDED;
+ final boolean moreSuggestions = data.hiddenSuggestionCount > 0;
+ final boolean hasConditions = data.conditionCount > 0;
+ if (data.conditionCount > 0) {
+ holder.icon.setImageIcon(data.conditionIcons.get(0));
+ holder.icon.setVisibility(View.VISIBLE);
+ if (data.conditionCount == 1) {
+ holder.title.setText(data.title);
+ holder.title.setTextColor(Utils.getColorAccent(mContext));
+ holder.icons.setVisibility(View.INVISIBLE);
+ } else {
+ holder.title.setText(null);
+ updateConditionIcons(data.conditionIcons, holder.icons);
+ holder.icons.setVisibility(View.VISIBLE);
+ }
+ } else {
+ holder.icon.setVisibility(View.INVISIBLE);
+ holder.icons.setVisibility(View.INVISIBLE);
+ }
+
+ if (data.hiddenSuggestionCount > 0) {
+ holder.summary.setTextColor(Color.BLACK);
+ if (curMode == DashboardData.HEADER_MODE_COLLAPSED) {
+ if (data.conditionCount > 0) {
+ holder.summary.setText(mContext.getResources().getQuantityString(
+ R.plurals.suggestions_collapsed_summary,
+ data.hiddenSuggestionCount, data.hiddenSuggestionCount));
+ } else {
+ holder.title.setText(mContext.getResources().getQuantityString(
+ R.plurals.suggestions_collapsed_title,
+ data.hiddenSuggestionCount, data.hiddenSuggestionCount));
+ holder.title.setTextColor(Color.BLACK);
+ holder.summary.setText(null);
+ }
+ } else if (curMode == DashboardData.HEADER_MODE_DEFAULT) {
+ if (data.conditionCount > 0) {
+ holder.summary.setText(mContext.getString(
+ R.string.suggestions_summary, data.hiddenSuggestionCount));
+ } else {
+ holder.title.setText(mContext.getString(
+ R.string.suggestions_more_title, data.hiddenSuggestionCount));
+ holder.title.setTextColor(Color.BLACK);
+ holder.summary.setText(null);
+ }
+ }
+ } else if (data.conditionCount > 1) {
+ holder.summary.setTextColor(Utils.getColorAccent(mContext));
+ holder.summary.setText(
+ mContext.getString(R.string.condition_summary, data.conditionCount));
+ } else {
+ holder.summary.setText(null);
+ }
+ holder.summary.setVisibility(View.VISIBLE);
+ holder.expandIndicator.setVisibility(View.VISIBLE);
+
+ holder.itemView.setOnClickListener(v -> {
+ if (moreSuggestions ) {
+ logSuggestions();
+ } else if (hasConditions) {
+ mMetricsFeatureProvider.action(mContext,
+ MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true);
+ }
+ DashboardData prevData = mDashboardData;
+ final boolean wasCollapsed = curMode == DashboardData.HEADER_MODE_COLLAPSED;
+ mDashboardData = new DashboardData.Builder(prevData)
+ .setSuggestionConditionMode(nextMode).build();
+ notifyDashboardDataChanged(prevData);
+ if (wasCollapsed) {
+ mRecyclerView.scrollToPosition(1);
+ }
+ });
+ }
+
+ private void onBindConditionAndSuggestion(final SuggestionAndConditionContainerHolder holder,
+ int position) {
+ RecyclerView.Adapter<DashboardItemHolder> adapter;
+ // If there is suggestions to show, it will be at position 2 (position 0 = header spacer,
+ // position 1 is suggestion header.
+ if (position == 2 && mDashboardData.getSuggestions() != null) {
+ mSuggestionAdapter = new SuggestionAdapter(mContext, (List<Tile>)
+ mDashboardData.getItemEntityByPosition(position), mSuggestionsShownLogged);
+ adapter = mSuggestionAdapter;
+ mSuggestionDismissHandler = new SuggestionDismissController(mContext,
+ holder.data, mSuggestionParser, mCallback);
+ } else {
+ ConditionAdapterUtils.addDismiss(holder.data);
+ adapter = new ConditionAdapter(mContext,
+ (List<Condition>) mDashboardData.getItemEntityByPosition(position),
+ mDashboardData.getSuggestionConditionMode());
+ }
+ holder.data.setLayoutManager(new LinearLayoutManager(mContext));
+ holder.data.setAdapter(adapter);
+ }
+
private void onBindTile(DashboardItemHolder holder, Tile tile) {
if (tile.remoteViews != null) {
final ViewGroup itemView = (ViewGroup) holder.itemView;
@@ -460,9 +693,27 @@
}
outState.putInt(STATE_SUGGESTION_MODE, mDashboardData.getSuggestionMode());
outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
+ outState.putInt(STATE_SUGGESTION_CONDITION_MODE,
+ mDashboardData.getSuggestionConditionMode());
}
- private static class IconCache {
+ private void updateConditionIcons(List<Icon> icons, ViewGroup parent) {
+ if (icons == null || icons.size() < 2) {
+ parent.setVisibility(View.INVISIBLE);
+ return;
+ }
+ final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ parent.removeAllViews();
+ for (int i = 1, size = icons.size(); i < size; i++) {
+ ImageView icon = (ImageView) inflater.inflate(
+ R.layout.condition_header_icon, parent, false);
+ icon.setImageIcon(icons.get(i));
+ parent.addView(icon);
+ }
+ parent.setVisibility(View.VISIBLE);
+ }
+
+ public static class IconCache {
private final Context mContext;
private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
@@ -492,4 +743,25 @@
summary = itemView.findViewById(android.R.id.summary);
}
}
+
+ public static class SuggestionAndConditionHeaderHolder extends DashboardItemHolder {
+ public final LinearLayout icons;
+ public final ImageView expandIndicator;
+
+ public SuggestionAndConditionHeaderHolder(View itemView) {
+ super(itemView);
+ icons = itemView.findViewById(id.additional_icons);
+ expandIndicator = itemView.findViewById(id.expand_indicator);
+ }
+ }
+
+ public static class SuggestionAndConditionContainerHolder extends DashboardItemHolder {
+ public final RecyclerView data;
+
+ public SuggestionAndConditionContainerHolder(View itemView) {
+ super(itemView);
+ data = itemView.findViewById(id.data);
+ }
+ }
+
}
diff --git a/src/com/android/settings/dashboard/DashboardData.java b/src/com/android/settings/dashboard/DashboardData.java
index 60d7d8d..64e8af2 100644
--- a/src/com/android/settings/dashboard/DashboardData.java
+++ b/src/com/android/settings/dashboard/DashboardData.java
@@ -16,7 +16,9 @@
package com.android.settings.dashboard;
import android.annotation.IntDef;
+import android.graphics.drawable.Icon;
import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
import android.support.v7.util.DiffUtil;
import android.text.TextUtils;
@@ -38,9 +40,22 @@
* ItemsData has inner class Item, which represents the Item in data list.
*/
public class DashboardData {
+ @Deprecated
public static final int SUGGESTION_MODE_DEFAULT = 0;
+ @Deprecated
public static final int SUGGESTION_MODE_COLLAPSED = 1;
+ @Deprecated
public static final int SUGGESTION_MODE_EXPANDED = 2;
+
+ public static final int HEADER_MODE_DEFAULT = 0;
+ public static final int HEADER_MODE_SUGGESTION_EXPANDED = 1;
+ public static final int HEADER_MODE_FULLY_EXPANDED = 2;
+ public static final int HEADER_MODE_COLLAPSED = 3;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({HEADER_MODE_DEFAULT, HEADER_MODE_SUGGESTION_EXPANDED, HEADER_MODE_FULLY_EXPANDED,
+ HEADER_MODE_COLLAPSED})
+ public @interface HeaderMode{}
+
public static final int POSITION_NOT_FOUND = -1;
public static final int DEFAULT_SUGGESTION_COUNT = 2;
@@ -49,14 +64,19 @@
private static final int NS_SPACER = 1000;
private static final int NS_ITEMS = 2000;
private static final int NS_CONDITION = 3000;
+ private static final int NS_SUGGESTION_CONDITION = 4000;
private final List<Item> mItems;
private final List<DashboardCategory> mCategories;
private final List<Condition> mConditions;
private final List<Tile> mSuggestions;
+ @Deprecated
private final int mSuggestionMode;
+ @Deprecated
private final Condition mExpandedCondition;
+ private final @HeaderMode int mSuggestionConditionMode;
private int mId;
+ private boolean mCombineSuggestionAndCondition;
private DashboardData(Builder builder) {
mCategories = builder.mCategories;
@@ -64,6 +84,8 @@
mSuggestions = builder.mSuggestions;
mSuggestionMode = builder.mSuggestionMode;
mExpandedCondition = builder.mExpandedCondition;
+ mSuggestionConditionMode = builder.mSuggestionConditionMode;
+ mCombineSuggestionAndCondition = builder.mCombineSuggestionAndCondition;
mItems = new ArrayList<>();
mId = 0;
@@ -116,6 +138,11 @@
return mSuggestionMode;
}
+ public int getSuggestionConditionMode() {
+ return mSuggestionConditionMode;
+ }
+
+ @Deprecated
public Condition getExpandedCondition() {
return mExpandedCondition;
}
@@ -177,14 +204,31 @@
* @return the count of suggestions to display
*/
public int getDisplayableSuggestionCount() {
- final int suggestionSize = mSuggestions.size();
- return mSuggestionMode == SUGGESTION_MODE_DEFAULT
- ? Math.min(DEFAULT_SUGGESTION_COUNT, suggestionSize)
- : mSuggestionMode == SUGGESTION_MODE_EXPANDED
- ? suggestionSize : 0;
+ final int suggestionSize = sizeOf(mSuggestions);
+ if (mCombineSuggestionAndCondition) {
+ if (mSuggestionConditionMode == HEADER_MODE_COLLAPSED) {
+ return 0;
+ }
+ if (mSuggestionConditionMode == HEADER_MODE_DEFAULT) {
+ return Math.min(DEFAULT_SUGGESTION_COUNT, suggestionSize);
+ }
+ return suggestionSize;
+ }
+ if (mSuggestionMode == SUGGESTION_MODE_DEFAULT) {
+ return Math.min(DEFAULT_SUGGESTION_COUNT, suggestionSize);
+ }
+ if (mSuggestionMode == SUGGESTION_MODE_EXPANDED) {
+ return suggestionSize;
+ }
+ return 0;
}
public boolean hasMoreSuggestions() {
+ if (mCombineSuggestionAndCondition) {
+ return mSuggestionConditionMode == HEADER_MODE_COLLAPSED && mSuggestions.size() > 0
+ || mSuggestionConditionMode == HEADER_MODE_DEFAULT
+ && mSuggestions.size() > DEFAULT_SUGGESTION_COUNT;
+ }
return mSuggestionMode == SUGGESTION_MODE_COLLAPSED
|| (mSuggestionMode == SUGGESTION_MODE_DEFAULT
&& mSuggestions.size() > DEFAULT_SUGGESTION_COUNT);
@@ -208,7 +252,11 @@
*/
private void countItem(Object object, int type, boolean add, int nameSpace) {
if (add) {
- mItems.add(new Item(object, type, mId + nameSpace, object == mExpandedCondition));
+ if (mCombineSuggestionAndCondition) {
+ mItems.add(new Item(object, type, mId + nameSpace));
+ } else {
+ mItems.add(new Item(object, type, mId + nameSpace, object == mExpandedCondition));
+ }
}
mId++;
}
@@ -238,26 +286,75 @@
// add the view that goes under the search bar
countItem(null, R.layout.dashboard_header_spacer, true, NS_HEADER_SPACER);
resetCount();
- boolean hasConditions = false;
- for (int i = 0; mConditions != null && i < mConditions.size(); i++) {
- boolean shouldShow = mConditions.get(i).shouldShow();
- hasConditions |= shouldShow;
- countItem(mConditions.get(i), R.layout.condition_card, shouldShow, NS_CONDITION);
- }
+ final boolean hasSuggestions = sizeOf(mSuggestions) > 0;
+ if (!mCombineSuggestionAndCondition) {
+ boolean hasConditions = false;
+ for (int i = 0; mConditions != null && i < mConditions.size(); i++) {
+ boolean shouldShow = mConditions.get(i).shouldShow();
+ hasConditions |= shouldShow;
+ countItem(mConditions.get(i), R.layout.condition_card, shouldShow, NS_CONDITION);
+ }
- resetCount();
- final boolean hasSuggestions = mSuggestions != null && mSuggestions.size() != 0;
- countItem(null, R.layout.dashboard_spacer, hasConditions && hasSuggestions, NS_SPACER);
- countItem(buildSuggestionHeaderData(), R.layout.suggestion_header, hasSuggestions,
+ resetCount();
+ countItem(null, R.layout.dashboard_spacer, hasConditions && hasSuggestions, NS_SPACER);
+ countItem(buildSuggestionHeaderData(), R.layout.suggestion_header, hasSuggestions,
NS_SPACER);
- resetCount();
- if (mSuggestions != null) {
- int maxSuggestions = getDisplayableSuggestionCount();
- for (int i = 0; i < mSuggestions.size(); i++) {
- countSuggestion(mSuggestions.get(i), i < maxSuggestions);
+ resetCount();
+ if (mSuggestions != null) {
+ int maxSuggestions = getDisplayableSuggestionCount();
+ for (int i = 0; i < mSuggestions.size(); i++) {
+ countSuggestion(mSuggestions.get(i), i < maxSuggestions);
+ }
}
+ } else {
+ final List<Condition> conditions = getConditionsToShow(mConditions);
+ final boolean hasConditions = sizeOf(conditions) > 0;
+
+ final List<Tile> suggestions = getSuggestionsToShow(mSuggestions);
+ final int hiddenSuggestion =
+ hasSuggestions ? sizeOf(mSuggestions) - sizeOf(suggestions) : 0;
+
+ resetCount();
+ /* Top suggestion/condition header. This will be present when there is any suggestion or
+ * condition to show, except in the case that there is only conditions to show and the
+ * mode is fully expanded. */
+ countItem(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
+ R.layout.suggestion_condition_header, hasSuggestions
+ || hasConditions && mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED,
+ NS_SUGGESTION_CONDITION);
+
+ /* Suggestion container. This is the card view that contains the list of suggestions.
+ * This will be added whenever the suggestion list is not empty */
+ countItem(suggestions, R.layout.suggestion_condition_container, sizeOf(suggestions) > 0,
+ NS_SUGGESTION_CONDITION);
+
+ /* Second suggestion/condition header. This will be added when there is at least one
+ * suggestion or condition that is not currently displayed, and the user can expand the
+ * section to view more items. */
+ countItem(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
+ R.layout.suggestion_condition_header,
+ mSuggestionConditionMode != HEADER_MODE_COLLAPSED
+ && mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED
+ && (hiddenSuggestion > 0
+ || hasConditions && hasSuggestions),
+ NS_SUGGESTION_CONDITION);
+
+ /* Condition container. This is the card view that contains the list of conditions.
+ * This will be added whenever the condition list is not empty */
+ countItem(conditions, R.layout.suggestion_condition_container,
+ hasConditions && mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED,
+ NS_SUGGESTION_CONDITION);
+
+ /* Suggestion/condition footer. This will be present when the section is fully expanded
+ * or when there is no conditions and no hidden suggestions */
+ countItem(null, R.layout.suggestion_condition_footer,
+ (hasConditions || hasSuggestions) &&
+ mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED
+ || hasSuggestions && !hasConditions && hiddenSuggestion == 0,
+ NS_SUGGESTION_CONDITION);
}
+
resetCount();
for (int i = 0; mCategories != null && i < mCategories.size(); i++) {
DashboardCategory category = mCategories.get(i);
@@ -270,6 +367,10 @@
}
}
+ private static int sizeOf(List<?> list) {
+ return list == null ? 0 : list.size();
+ }
+
private SuggestionHeaderData buildSuggestionHeaderData() {
SuggestionHeaderData data;
if (mSuggestions == null) {
@@ -285,19 +386,49 @@
return data;
}
+ private List<Condition> getConditionsToShow(List<Condition> conditions) {
+ if (conditions == null) {
+ return null;
+ }
+ List<Condition> result = new ArrayList<Condition>();
+ final int size = conditions == null ? 0 : conditions.size();
+ for (int i = 0; i < size; i++) {
+ final Condition condition = conditions.get(i);
+ if (condition.shouldShow()) {
+ result.add(condition);
+ }
+ }
+ return result;
+ }
+
+ private List<Tile> getSuggestionsToShow(List<Tile> suggestions) {
+ if (suggestions == null || mSuggestionConditionMode == HEADER_MODE_COLLAPSED) {
+ return null;
+ }
+ if (mSuggestionConditionMode != HEADER_MODE_DEFAULT
+ || suggestions.size() <= DEFAULT_SUGGESTION_COUNT) {
+ return suggestions;
+ }
+ return suggestions.subList(0, DEFAULT_SUGGESTION_COUNT);
+ }
+
/**
* Builder used to build the ItemsData
* <p>
- * {@link #mExpandedCondition} and {@link #mSuggestionMode} have default value
- * while others are not.
+ * {@link #mExpandedCondition}, {@link #mSuggestionConditionMode} and {@link #mSuggestionMode}
+ * have default value while others are not.
*/
public static class Builder {
+ @Deprecated
private int mSuggestionMode = SUGGESTION_MODE_DEFAULT;
+ @Deprecated
private Condition mExpandedCondition = null;
+ private @HeaderMode int mSuggestionConditionMode = HEADER_MODE_DEFAULT;
private List<DashboardCategory> mCategories;
private List<Condition> mConditions;
private List<Tile> mSuggestions;
+ private boolean mCombineSuggestionAndCondition;
public Builder() {
}
@@ -308,6 +439,8 @@
mSuggestions = dashboardData.mSuggestions;
mSuggestionMode = dashboardData.mSuggestionMode;
mExpandedCondition = dashboardData.mExpandedCondition;
+ mSuggestionConditionMode = dashboardData.mSuggestionConditionMode;
+ mCombineSuggestionAndCondition = dashboardData.mCombineSuggestionAndCondition;
}
public Builder setCategories(List<DashboardCategory> categories) {
@@ -330,11 +463,22 @@
return this;
}
+ @Deprecated
public Builder setExpandedCondition(Condition expandedCondition) {
this.mExpandedCondition = expandedCondition;
return this;
}
+ public Builder setSuggestionConditionMode(@HeaderMode int mode) {
+ this.mSuggestionConditionMode = mode;
+ return this;
+ }
+
+ public Builder setCombineSuggestionAndCondition(boolean combine) {
+ this.mCombineSuggestionAndCondition = combine;
+ return this;
+ }
+
public DashboardData build() {
return new DashboardData(this);
}
@@ -373,6 +517,8 @@
return mOldItems.get(oldItemPosition).equals(mNewItems.get(newItemPosition));
}
+ // not needed in combined UI
+ @Deprecated
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
@@ -390,13 +536,24 @@
// valid types in field type
private static final int TYPE_DASHBOARD_CATEGORY = R.layout.dashboard_category;
private static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile;
+ @Deprecated
private static final int TYPE_SUGGESTION_HEADER = R.layout.suggestion_header;
+ @Deprecated
private static final int TYPE_SUGGESTION_TILE = R.layout.suggestion_tile;
+ private static final int TYPE_SUGGESTION_CONDITION_CONTAINER =
+ R.layout.suggestion_condition_container;
+ private static final int TYPE_SUGGESTION_CONDITION_HEADER =
+ R.layout.suggestion_condition_header;
+ @Deprecated
private static final int TYPE_CONDITION_CARD = R.layout.condition_card;
+ private static final int TYPE_SUGGESTION_CONDITION_FOOTER =
+ R.layout.suggestion_condition_footer;
private static final int TYPE_DASHBOARD_SPACER = R.layout.dashboard_spacer;
@IntDef({TYPE_DASHBOARD_CATEGORY, TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_HEADER,
- TYPE_SUGGESTION_TILE, TYPE_CONDITION_CARD, TYPE_DASHBOARD_SPACER})
+ TYPE_SUGGESTION_TILE, TYPE_SUGGESTION_CONDITION_CONTAINER,
+ TYPE_SUGGESTION_CONDITION_HEADER, TYPE_CONDITION_CARD,
+ TYPE_SUGGESTION_CONDITION_FOOTER, TYPE_DASHBOARD_SPACER})
@Retention(RetentionPolicy.SOURCE)
public @interface ItemTypes{}
@@ -422,8 +579,10 @@
* To store whether the condition is expanded, useless when {@link #type} is not
* {@link #TYPE_CONDITION_CARD}
*/
+ @Deprecated
public final boolean conditionExpanded;
+ @Deprecated
public Item(Object entity, @ItemTypes int type, int id, boolean conditionExpanded) {
this.entity = entity;
this.type = type;
@@ -431,6 +590,10 @@
this.conditionExpanded = conditionExpanded;
}
+ public Item(Object entity, @ItemTypes int type, int id) {
+ this(entity, type, id, false);
+ }
+
/**
* Override it to make comparision in the {@link ItemsDataDiffCallback}
* @param obj object to compared with
@@ -516,4 +679,27 @@
}
}
+ /**
+ * This class contains the data needed to build the suggestion/condition header. The data can
+ * also be used to check the diff in DiffUtil.Callback
+ */
+ public static class SuggestionConditionHeaderData {
+ public final List<Icon> conditionIcons;
+ public final CharSequence title;
+ public final int conditionCount;
+ public final int hiddenSuggestionCount;
+
+ public SuggestionConditionHeaderData(List<Condition> conditions,
+ int hiddenSuggestionCount) {
+ conditionCount = sizeOf(conditions);
+ this.hiddenSuggestionCount = hiddenSuggestionCount;
+ title = conditionCount > 0 ? conditions.get(0).getTitle() : null;
+ conditionIcons = new ArrayList<Icon>();
+ for (int i = 0; conditions != null && i < conditions.size(); i++) {
+ final Condition condition = conditions.get(i);
+ conditionIcons.add(condition.getIcon());
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/com/android/settings/dashboard/DashboardFeatureProvider.java b/src/com/android/settings/dashboard/DashboardFeatureProvider.java
index 15608a2..939a5d6 100644
--- a/src/com/android/settings/dashboard/DashboardFeatureProvider.java
+++ b/src/com/android/settings/dashboard/DashboardFeatureProvider.java
@@ -95,4 +95,9 @@
*/
void openTileIntent(Activity activity, Tile tile);
+ /**
+ * Whether or not we should use new UI that combines the settings suggestions and conditions.
+ */
+ boolean combineSuggestionAndCondition();
+
}
diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
index 88cf666..dcae322 100644
--- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
+++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
@@ -206,6 +206,11 @@
launchIntentOrSelectProfile(activity, tile, intent, MetricsEvent.DASHBOARD_SUMMARY);
}
+ @Override
+ public boolean combineSuggestionAndCondition() {
+ return false;
+ }
+
private void launchIntentOrSelectProfile(Activity activity, Tile tile, Intent intent,
int sourceMetricCategory) {
if (!isIntentResolvable(intent)) {
diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java
index 9f6c61c..7bd11f5 100644
--- a/src/com/android/settings/dashboard/DashboardSummary.java
+++ b/src/com/android/settings/dashboard/DashboardSummary.java
@@ -196,13 +196,16 @@
mDashboard.addItemDecoration(new DashboardDecorator(getContext()));
mDashboard.setListener(this);
Log.d(TAG, "adapter created");
- mAdapter = new DashboardAdapter(getContext(), bundle, mConditionManager.getConditions());
+ mAdapter = new DashboardAdapter(getContext(), bundle, mConditionManager.getConditions(),
+ mSuggestionParser, this /* SuggestionDismissController.Callback */);
mDashboard.setAdapter(mAdapter);
- mSuggestionDismissHandler = new SuggestionDismissController(
+ if (!mDashboardFeatureProvider.combineSuggestionAndCondition()) {
+ mSuggestionDismissHandler = new SuggestionDismissController(
getContext(), mDashboard, mSuggestionParser, this);
+ ConditionAdapterUtils.addDismiss(mDashboard);
+ }
mDashboard.setItemAnimator(new DashboardItemAnimator());
mSummaryLoader.setSummaryConsumer(mAdapter);
- ConditionAdapterUtils.addDismiss(mDashboard);
if (DEBUG_TIMING) {
Log.d(TAG, "onViewCreated took "
+ (System.currentTimeMillis() - startTime) + " ms");
@@ -242,7 +245,7 @@
@Override
public Tile getSuggestionForPosition(int position) {
- return (Tile) mAdapter.getItem(mAdapter.getItemId(position));
+ return mAdapter.getSuggestion(position);
}
@Override
diff --git a/src/com/android/settings/dashboard/conditional/ConditionAdapter.java b/src/com/android/settings/dashboard/conditional/ConditionAdapter.java
new file mode 100644
index 0000000..5827d14
--- /dev/null
+++ b/src/com/android/settings/dashboard/conditional/ConditionAdapter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.dashboard.conditional;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
+import com.android.settings.dashboard.DashboardAdapter.DashboardItemHolder;
+import com.android.settings.dashboard.DashboardData;
+import com.android.settings.dashboard.DashboardData.HeaderMode;
+import com.android.settings.overlay.FeatureFactory;
+import java.util.List;
+import java.util.Objects;
+
+public class ConditionAdapter extends RecyclerView.Adapter<DashboardItemHolder> {
+ public static final String TAG = "ConditionAdapter";
+
+ private final Context mContext;
+ private final MetricsFeatureProvider mMetricsFeatureProvider;
+ private List<Condition> mConditions;
+ private @HeaderMode int mMode;
+
+ private View.OnClickListener mConditionClickListener = new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ //TODO: get rid of setTag/getTag
+ Condition condition = (Condition) v.getTag();
+ mMetricsFeatureProvider.action(mContext,
+ MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
+ condition.getMetricsConstant());
+ condition.onPrimaryClick();
+ }
+ };
+
+ public ConditionAdapter(Context context, List<Condition> conditions, @HeaderMode int mode) {
+ mContext = context;
+ mConditions = conditions;
+ mMode = mode;
+ mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+
+ setHasStableIds(true);
+ }
+
+ public Object getItem(long itemId) {
+ for (Condition condition : mConditions) {
+ if (Objects.hash(condition.getTitle()) == itemId) {
+ return condition;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
+ viewType, parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(DashboardItemHolder holder, int position) {
+ // TODO: merge methods from ConditionAdapterUtils into this class
+ ConditionAdapterUtils.bindViews(mConditions.get(position), holder,
+ position == mConditions.size() - 1, mConditionClickListener);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return Objects.hash(mConditions.get(position).getTitle());
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return R.layout.condition_tile_new_ui;
+ }
+
+ @Override
+ public int getItemCount() {
+ if (mMode == DashboardData.HEADER_MODE_FULLY_EXPANDED) {
+ return mConditions.size();
+ }
+ return 0;
+ }
+
+}
diff --git a/src/com/android/settings/dashboard/conditional/ConditionAdapterUtils.java b/src/com/android/settings/dashboard/conditional/ConditionAdapterUtils.java
index aebbf93..0e25723 100644
--- a/src/com/android/settings/dashboard/conditional/ConditionAdapterUtils.java
+++ b/src/com/android/settings/dashboard/conditional/ConditionAdapterUtils.java
@@ -44,22 +44,28 @@
@Override
public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return viewHolder.getItemViewType() == R.layout.condition_card
+ || viewHolder.getItemViewType() == R.layout.condition_tile_new_ui
? super.getSwipeDirs(recyclerView, viewHolder) : 0;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
- DashboardAdapter adapter = (DashboardAdapter) recyclerView.getAdapter();
- Object item = adapter.getItem(viewHolder.getItemId());
- if (item instanceof Condition) {
- ((Condition) item).silence();
+ Object item;
+ if (viewHolder.getItemViewType() == R.layout.condition_card) {
+ DashboardAdapter adapter = (DashboardAdapter) recyclerView.getAdapter();
+ item = adapter.getItem(viewHolder.getItemId());
+ } else {
+ ConditionAdapter adapter = (ConditionAdapter) recyclerView.getAdapter();
+ item = adapter.getItem(viewHolder.getItemId());
}
+ ((Condition) item).silence();
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(recyclerView);
}
+ @Deprecated
public static void bindViews(final Condition condition,
DashboardAdapter.DashboardItemHolder view, boolean isExpanded,
View.OnClickListener onClickListener, View.OnClickListener onExpandListener) {
@@ -121,6 +127,49 @@
}
}
+ public static void bindViews(final Condition condition,
+ DashboardAdapter.DashboardItemHolder view, boolean isLastItem,
+ View.OnClickListener onClickListener) {
+ if (condition instanceof AirplaneModeCondition) {
+ Log.d(TAG, "Airplane mode condition has been bound with "
+ + "isActive=" + condition.isActive() + ". Airplane mode is currently " +
+ WirelessUtils.isAirplaneModeOn(condition.mManager.getContext()));
+ }
+ View card = view.itemView.findViewById(R.id.content);
+ card.setTag(condition);
+ card.setOnClickListener(onClickListener);
+ view.icon.setImageIcon(condition.getIcon());
+ view.title.setText(condition.getTitle());
+
+ CharSequence[] actions = condition.getActions();
+ final boolean hasButtons = actions.length > 0;
+ setViewVisibility(view.itemView, R.id.buttonBar, hasButtons);
+
+ view.summary.setText(condition.getSummary());
+ for (int i = 0; i < 2; i++) {
+ Button button = (Button) view.itemView.findViewById(i == 0
+ ? R.id.first_action : R.id.second_action);
+ if (actions.length > i) {
+ button.setVisibility(View.VISIBLE);
+ button.setText(actions[i]);
+ final int index = i;
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Context context = v.getContext();
+ FeatureFactory.getFactory(context).getMetricsFeatureProvider()
+ .action(context, MetricsEvent.ACTION_SETTINGS_CONDITION_BUTTON,
+ condition.getMetricsConstant());
+ condition.onActionClick(index);
+ }
+ });
+ } else {
+ button.setVisibility(View.GONE);
+ }
+ }
+ setViewVisibility(view.itemView, R.id.divider, !isLastItem);
+ }
+
private static void setViewVisibility(View containerView, int viewId, boolean visible) {
View view = containerView.findViewById(viewId);
if (view != null) {
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java
new file mode 100644
index 0000000..3335950
--- /dev/null
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.dashboard.suggestions;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.R.layout;
+import com.android.settings.SettingsActivity;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
+import com.android.settings.dashboard.DashboardAdapter.DashboardItemHolder;
+import com.android.settings.dashboard.DashboardAdapter.IconCache;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.drawer.Tile;
+import java.util.List;
+import java.util.Objects;
+
+public class SuggestionAdapter extends RecyclerView.Adapter<DashboardItemHolder> {
+ public static final String TAG = "SuggestionAdapter";
+
+ private final Context mContext;
+ private final MetricsFeatureProvider mMetricsFeatureProvider;
+ private final SuggestionFeatureProvider mSuggestionFeatureProvider;
+ private List<Tile> mSuggestions;
+ private final IconCache mCache;
+ private final List<String> mSuggestionsShownLogged;
+
+ public SuggestionAdapter(Context context, List<Tile> suggestions,
+ List<String> suggestionsShownLogged) {
+ mContext = context;
+ mSuggestions = suggestions;
+ mSuggestionsShownLogged = suggestionsShownLogged;
+ mCache = new IconCache(context);
+ final FeatureFactory factory = FeatureFactory.getFactory(context);
+ mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
+ mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);
+
+ setHasStableIds(true);
+ }
+
+ @Override
+ public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
+ viewType, parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(DashboardItemHolder holder, int position) {
+ final Tile suggestion = (Tile) mSuggestions.get(position);
+ final String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
+ mContext, suggestion);
+ // This is for cases when a suggestion is dismissed and the next one comes to view
+ if (!mSuggestionsShownLogged.contains(suggestionId)) {
+ mMetricsFeatureProvider.action(
+ mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, suggestionId);
+ mSuggestionsShownLogged.add(suggestionId);
+ }
+ if (suggestion.remoteViews != null) {
+ final ViewGroup itemView = (ViewGroup) holder.itemView;
+ itemView.removeAllViews();
+ itemView.addView(suggestion.remoteViews.apply(itemView.getContext(), itemView));
+ } else {
+ holder.icon.setImageDrawable(mCache.getIcon(suggestion.icon));
+ holder.title.setText(suggestion.title);
+ if (!TextUtils.isEmpty(suggestion.summary)) {
+ holder.summary.setText(suggestion.summary);
+ holder.summary.setVisibility(View.VISIBLE);
+ } else {
+ holder.summary.setVisibility(View.GONE);
+ }
+ }
+ final View divider = holder.itemView.findViewById(R.id.divider);
+ if (divider != null) {
+ divider.setVisibility(position < mSuggestions.size() - 1 ? View.VISIBLE : View.GONE);
+ }
+ View clickHandler = holder.itemView;
+ // If a view with @android:id/primary is defined, use that as the click handler
+ // instead.
+ final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
+ if (primaryAction != null) {
+ clickHandler = primaryAction;
+ // set the item view to disabled to remove any touch effects
+ holder.itemView.setEnabled(false);
+ }
+ clickHandler.setOnClickListener(v -> {
+ mMetricsFeatureProvider.action(mContext,
+ MetricsEvent.ACTION_SETTINGS_SUGGESTION, suggestionId);
+ ((SettingsActivity) mContext).startSuggestion(suggestion.intent);
+ });
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return Objects.hash(mSuggestions.get(position).title);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return layout.suggestion_tile_new_ui;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mSuggestions.size();
+ }
+
+ public Tile getSuggestion(int position) {
+ final long itemId = getItemId(position);
+ for (Tile tile: mSuggestions) {
+ if (Objects.hash(tile.title) == itemId) {
+ return tile;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
index 776bd1e..f0a65f6 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java
@@ -67,6 +67,7 @@
public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
final int layoutId = viewHolder.getItemViewType();
if (layoutId == R.layout.suggestion_tile
+ || layoutId == R.layout.suggestion_tile_new_ui
|| layoutId == R.layout.suggestion_tile_card) {
// Only return swipe direction for suggestion tiles. All other types are not swipeable.
return super.getSwipeDirs(recyclerView, viewHolder);
diff --git a/tests/robotests/src/com/android/settings/conditional/ConditionAdapterUtilsTest.java b/tests/robotests/src/com/android/settings/conditional/ConditionAdapterUtilsTest.java
index dde291d..bf28d97 100644
--- a/tests/robotests/src/com/android/settings/conditional/ConditionAdapterUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/conditional/ConditionAdapterUtilsTest.java
@@ -36,6 +36,8 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
+// Not needed in new UI as the view is always expanded
+@Deprecated
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class ConditionAdapterUtilsTest{
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
index 8eae8bc..5799b22 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
@@ -164,14 +164,6 @@
}
@Test
- public void testSetConditions_AfterSetConditions_ExpandedConditionNull() {
- mDashboardAdapter.onExpandClick(mView);
- assertThat(mDashboardAdapter.mDashboardData.getExpandedCondition()).isEqualTo(mCondition);
- mDashboardAdapter.setConditions(null);
- assertThat(mDashboardAdapter.mDashboardData.getExpandedCondition()).isNull();
- }
-
- @Test
public void testSuggestionsLogs_NotExpanded() {
setupSuggestions(makeSuggestions("pkg1", "pkg2", "pkg3"));
verify(mFactory.metricsFeatureProvider, times(2)).action(
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardDataTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardDataTest.java
index 8bbb15b..23681bc 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardDataTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardDataTest.java
@@ -88,17 +88,21 @@
mDashboardCategory.tiles.add(mTestCategoryTile);
categories.add(mDashboardCategory);
- // Build DashboardData
+ // Build DashboardData
mDashboardDataWithOneConditions = new DashboardData.Builder()
.setConditions(oneItemConditions)
.setCategories(categories)
.setSuggestions(suggestions)
+ .setSuggestionConditionMode(DashboardData.HEADER_MODE_FULLY_EXPANDED)
+ .setCombineSuggestionAndCondition(true)
.build();
mDashboardDataWithTwoConditions = new DashboardData.Builder()
.setConditions(twoItemsConditions)
.setCategories(categories)
.setSuggestions(suggestions)
+ .setSuggestionConditionMode(DashboardData.HEADER_MODE_FULLY_EXPANDED)
+ .setCombineSuggestionAndCondition(true)
.build();
mDashboardDataWithNoItems = new DashboardData.Builder()
@@ -110,23 +114,33 @@
@Test
public void testBuildItemsData_containsAllData() {
- final DashboardData.SuggestionHeaderData data =
- new DashboardData.SuggestionHeaderData(false, 1, 0);
- final Object[] expectedObjects = {null, mTestCondition, null, data, mTestSuggestion,
- mDashboardCategory, mTestCategoryTile};
+ final DashboardData.SuggestionConditionHeaderData data =
+ new DashboardData.SuggestionConditionHeaderData(
+ mDashboardDataWithOneConditions.getConditions(), 0);
+ final Object[] expectedObjects = {null, data,
+ mDashboardDataWithOneConditions.getSuggestions(),
+ mDashboardDataWithOneConditions.getConditions(),
+ null, mDashboardCategory, mTestCategoryTile};
final int expectedSize = expectedObjects.length;
assertThat(mDashboardDataWithOneConditions.getItemList().size())
.isEqualTo(expectedSize);
for (int i = 0; i < expectedSize; i++) {
- if (mDashboardDataWithOneConditions.getItemEntityByPosition(i)
- instanceof DashboardData.SuggestionHeaderData) {
+ final Object item = mDashboardDataWithOneConditions.getItemEntityByPosition(i);
+ if (item instanceof DashboardData.SuggestionHeaderData
+ || item instanceof List) {
// SuggestionHeaderData is created inside when build, we can only use isEqualTo
- assertThat(mDashboardDataWithOneConditions.getItemEntityByPosition(i))
- .isEqualTo(expectedObjects[i]);
+ assertThat(item).isEqualTo(expectedObjects[i]);
+ } else if (item instanceof DashboardData.SuggestionConditionHeaderData) {
+ DashboardData.SuggestionConditionHeaderData i1 =
+ (DashboardData.SuggestionConditionHeaderData)item;
+ DashboardData.SuggestionConditionHeaderData i2 =
+ (DashboardData.SuggestionConditionHeaderData)expectedObjects[i];
+ assertThat(i1.title).isEqualTo(i2.title);
+ assertThat(i1.conditionCount).isEqualTo(i2.conditionCount);
+ assertThat(i1.hiddenSuggestionCount).isEqualTo(i2.hiddenSuggestionCount);
} else {
- assertThat(mDashboardDataWithOneConditions.getItemEntityByPosition(i))
- .isSameAs(expectedObjects[i]);
+ assertThat(item).isSameAs(expectedObjects[i]);
}
}
}
@@ -134,7 +148,7 @@
@Test
public void testGetPositionByEntity_selfInstance_returnPositionFound() {
final int position = mDashboardDataWithOneConditions
- .getPositionByEntity(mTestCondition);
+ .getPositionByEntity(mDashboardDataWithOneConditions.getConditions());
assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
}
@@ -176,11 +190,17 @@
}
@Test
- public void testDiffUtil_InsertOneCondition_ResultDataOneInserted() {
+ public void testDiffUtil_InsertOneCondition_ResultDataTwoChanged() {
//Build testResultData
final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
+ // Item in position 1 is the header, which contains the number of conditions, changed from
+ // 1 to 2
testResultData.add(new ListUpdateResult.ResultData(
- ListUpdateResult.ResultData.TYPE_OPERATION_INSERT, 2, 1));
+ ListUpdateResult.ResultData.TYPE_OPERATION_CHANGE, 1, 1));
+ // Item in position 3 is the condition container containing the list of conditions, which
+ // gets 1 more item
+ testResultData.add(new ListUpdateResult.ResultData(
+ ListUpdateResult.ResultData.TYPE_OPERATION_CHANGE, 3, 1));
testDiffUtil(mDashboardDataWithOneConditions,
mDashboardDataWithTwoConditions, testResultData);
@@ -196,31 +216,6 @@
testDiffUtil(mDashboardDataWithOneConditions, mDashboardDataWithNoItems, testResultData);
}
- @Test
- public void testPayload_ItemConditionCard_returnNotNull() {
- final DashboardData.ItemsDataDiffCallback callback = new DashboardData
- .ItemsDataDiffCallback(
- mDashboardDataWithOneConditions.getItemList(),
- mDashboardDataWithOneConditions.getItemList());
-
- // Item in position 1 is condition card, which payload should not be null
- assertThat(callback.getChangePayload(1, 1)).isNotNull();
- }
-
- @Test
- public void testPayload_ItemNotConditionCard_returnNull() {
- final DashboardData.ItemsDataDiffCallback callback = new DashboardData
- .ItemsDataDiffCallback(
- mDashboardDataWithOneConditions.getItemList(),
- mDashboardDataWithOneConditions.getItemList());
-
- // Position 0 is spacer, 1 is condition card, so others' payload should be null
- for (int i = 2; i < mDashboardDataWithOneConditions.getItemList().size(); i++) {
- assertThat(callback.getChangePayload(i, i)).isNull();
- }
-
- }
-
/**
* Test when using the
* {@link com.android.settings.dashboard.DashboardData.ItemsDataDiffCallback}
diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionAdapterTest.java
new file mode 100644
index 0000000..141ef6e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionAdapterTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.dashboard.conditional;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.dashboard.DashboardAdapter;
+import com.android.settings.dashboard.DashboardData;
+import com.android.settings.dashboard.conditional.Condition;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ConditionAdapterTest {
+ @Mock
+ private Condition mCondition1;
+ @Mock
+ private Condition mCondition2;
+
+ private Context mContext;
+ private ConditionAdapter mConditionAdapter;
+ private List<Condition> mOneCondition;
+ private List<Condition> mTwoConditions;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ final CharSequence[] actions = new CharSequence[2];
+ when(mCondition1.getActions()).thenReturn(actions);
+ when(mCondition1.shouldShow()).thenReturn(true);
+ mOneCondition = new ArrayList<>();
+ mOneCondition.add(mCondition1);
+ mTwoConditions = new ArrayList<>();
+ mTwoConditions.add(mCondition1);
+ mTwoConditions.add(mCondition2);
+ }
+
+ @Test
+ public void getItemCount_notFullyExpanded_shouldReturn0() {
+ mConditionAdapter = new ConditionAdapter(
+ mContext, mOneCondition, DashboardData.HEADER_MODE_DEFAULT);
+ assertThat(mConditionAdapter.getItemCount()).isEqualTo(0);
+
+ mConditionAdapter = new ConditionAdapter(
+ mContext, mOneCondition, DashboardData.HEADER_MODE_SUGGESTION_EXPANDED);
+ assertThat(mConditionAdapter.getItemCount()).isEqualTo(0);
+
+ mConditionAdapter = new ConditionAdapter(
+ mContext, mOneCondition, DashboardData.HEADER_MODE_COLLAPSED);
+ assertThat(mConditionAdapter.getItemCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void getItemCount_fullyExpanded_shouldReturnListSize() {
+ mConditionAdapter = new ConditionAdapter(
+ mContext, mOneCondition, DashboardData.HEADER_MODE_FULLY_EXPANDED);
+ assertThat(mConditionAdapter.getItemCount()).isEqualTo(1);
+
+ mConditionAdapter = new ConditionAdapter(
+ mContext, mTwoConditions, DashboardData.HEADER_MODE_FULLY_EXPANDED);
+ assertThat(mConditionAdapter.getItemCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void getItemViewType_shouldReturnConditionTile() {
+ mConditionAdapter = new ConditionAdapter(
+ mContext, mTwoConditions, DashboardData.HEADER_MODE_FULLY_EXPANDED);
+ assertThat(mConditionAdapter.getItemViewType(0)).isEqualTo(R.layout.condition_tile_new_ui);
+ }
+
+ @Test
+ public void onBindViewHolder_shouldSetListener() {
+ final View view = LayoutInflater.from(mContext).inflate(
+ R.layout.condition_tile_new_ui, new LinearLayout(mContext), true);
+ final DashboardAdapter.DashboardItemHolder viewHolder =
+ new DashboardAdapter.DashboardItemHolder(view);
+ mConditionAdapter = new ConditionAdapter(
+ mContext, mOneCondition, DashboardData.HEADER_MODE_SUGGESTION_EXPANDED);
+
+ mConditionAdapter.onBindViewHolder(viewHolder, 0);
+ final View card = view.findViewById(R.id.content);
+ assertThat(card.hasOnClickListeners()).isTrue();
+ }
+
+ @Test
+ public void viewClick_shouldInvokeConditionPrimaryClick() {
+ final View view = LayoutInflater.from(mContext).inflate(
+ R.layout.condition_tile_new_ui, new LinearLayout(mContext), true);
+ final DashboardAdapter.DashboardItemHolder viewHolder =
+ new DashboardAdapter.DashboardItemHolder(view);
+ mConditionAdapter = new ConditionAdapter(
+ mContext, mOneCondition, DashboardData.HEADER_MODE_SUGGESTION_EXPANDED);
+
+ mConditionAdapter.onBindViewHolder(viewHolder, 0);
+ final View card = view.findViewById(R.id.content);
+ card.performClick();
+ verify(mCondition1).onPrimaryClick();
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java
new file mode 100644
index 0000000..cf45c01
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.dashboard.suggestions;
+
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.dashboard.DashboardAdapter;
+import com.android.settingslib.drawer.Tile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SuggestionAdapterTest {
+ @Mock
+ private Tile mSuggestion1;
+ @Mock
+ private Tile mSuggestion2;
+
+ private Context mContext;
+ private SuggestionAdapter mSuggestionAdapter;
+ private List<Tile> mOneSuggestion;
+ private List<Tile> mTwoSuggestions;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mSuggestion1.title = "Test Suggestion 1";
+ mSuggestion1.icon = mock(Icon.class);
+ mSuggestion2.title = "Test Suggestion 2";
+ mSuggestion2.icon = mock(Icon.class);
+ mOneSuggestion = new ArrayList<>();
+ mOneSuggestion.add(mSuggestion1);
+ mTwoSuggestions = new ArrayList<>();
+ mTwoSuggestions.add(mSuggestion1);
+ mTwoSuggestions.add(mSuggestion2);
+ }
+
+ @Test
+ public void getItemCount_shouldReturnListSize() {
+ mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>());
+ assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1);
+
+ mSuggestionAdapter = new SuggestionAdapter(mContext, mTwoSuggestions, new ArrayList<>());
+ assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void getItemViewType_shouldReturnSuggestionTile() {
+ mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>());
+ assertThat(mSuggestionAdapter.getItemViewType(0))
+ .isEqualTo(R.layout.suggestion_tile_new_ui);
+ }
+
+ @Test
+ public void onBindViewHolder_shouldSetListener() {
+ final View view = spy(LayoutInflater.from(mContext).inflate(
+ R.layout.suggestion_tile_new_ui, new LinearLayout(mContext), true));
+ final DashboardAdapter.DashboardItemHolder viewHolder =
+ new DashboardAdapter.DashboardItemHolder(view);
+ mSuggestionAdapter = new SuggestionAdapter(mContext, mOneSuggestion, new ArrayList<>());
+
+ mSuggestionAdapter.onBindViewHolder(viewHolder, 0);
+
+ verify(view).setOnClickListener(any(View.OnClickListener.class));
+ }
+
+}