OSDN Git Service

Dashboard search bar polish
[android-x86/packages-apps-Settings.git] / src / com / android / settings / dashboard / DashboardAdapter.java
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.settings.dashboard;
17
18 import android.annotation.ColorInt;
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.res.TypedArray;
22 import android.graphics.Color;
23 import android.graphics.drawable.Drawable;
24 import android.graphics.drawable.Icon;
25 import android.os.Bundle;
26 import android.support.annotation.VisibleForTesting;
27 import android.support.v7.util.DiffUtil;
28 import android.support.v7.widget.LinearLayoutManager;
29 import android.support.v7.widget.RecyclerView;
30 import android.text.TextUtils;
31 import android.util.ArrayMap;
32 import android.util.Log;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.ImageView;
37 import android.widget.LinearLayout;
38 import android.widget.TextView;
39
40 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
41 import com.android.settings.R;
42 import com.android.settings.R.id;
43 import com.android.settings.SettingsActivity;
44 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
45 import com.android.settings.dashboard.DashboardData.SuggestionConditionHeaderData;
46 import com.android.settings.dashboard.conditional.Condition;
47 import com.android.settings.dashboard.conditional.ConditionAdapter;
48 import com.android.settings.dashboard.conditional.ConditionAdapterUtils;
49 import com.android.settings.dashboard.suggestions.SuggestionAdapter;
50 import com.android.settings.dashboard.suggestions.SuggestionDismissController;
51 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
52 import com.android.settings.overlay.FeatureFactory;
53 import com.android.settingslib.Utils;
54 import com.android.settingslib.drawer.DashboardCategory;
55 import com.android.settingslib.drawer.Tile;
56 import com.android.settingslib.suggestions.SuggestionParser;
57
58 import java.util.ArrayList;
59 import java.util.List;
60
61 public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder>
62         implements SummaryLoader.SummaryConsumer {
63     public static final String TAG = "DashboardAdapter";
64     private static final String STATE_SUGGESTION_LIST = "suggestion_list";
65     private static final String STATE_CATEGORY_LIST = "category_list";
66     private static final String STATE_SUGGESTION_MODE = "suggestion_mode";
67     private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
68     private static final String STATE_SUGGESTION_CONDITION_MODE = "suggestion_condition_mode";
69
70     private final IconCache mCache;
71     private final Context mContext;
72     private final MetricsFeatureProvider mMetricsFeatureProvider;
73     private final DashboardFeatureProvider mDashboardFeatureProvider;
74     private final SuggestionFeatureProvider mSuggestionFeatureProvider;
75     private final ArrayList<String> mSuggestionsShownLogged;
76     private boolean mFirstFrameDrawn;
77     private boolean mCombineSuggestionAndCondition;
78     private RecyclerView mRecyclerView;
79     private SuggestionParser mSuggestionParser;
80     private SuggestionAdapter mSuggestionAdapter;
81     private SuggestionDismissController mSuggestionDismissHandler;
82     private SuggestionDismissController.Callback mCallback;
83
84     @VisibleForTesting
85     DashboardData mDashboardData;
86
87     private View.OnClickListener mTileClickListener = new View.OnClickListener() {
88         @Override
89         public void onClick(View v) {
90             //TODO: get rid of setTag/getTag
91             mDashboardFeatureProvider.openTileIntent((Activity) mContext, (Tile) v.getTag());
92         }
93     };
94
95     private View.OnClickListener mConditionClickListener = new View.OnClickListener() {
96
97         @Override
98         public void onClick(View v) {
99             if (mCombineSuggestionAndCondition) {
100                 Condition condition = (Condition) v.getTag();
101                 //TODO: get rid of setTag/getTag
102                 mMetricsFeatureProvider.action(mContext,
103                     MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
104                     condition.getMetricsConstant());
105                 condition.onPrimaryClick();
106             } else {
107                 Condition expandedCondition = mDashboardData.getExpandedCondition();
108
109                 //TODO: get rid of setTag/getTag
110                 if (v.getTag() == expandedCondition) {
111                     mMetricsFeatureProvider.action(mContext,
112                         MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
113                         expandedCondition.getMetricsConstant());
114                     expandedCondition.onPrimaryClick();
115                 } else {
116                     expandedCondition = (Condition) v.getTag();
117                     mMetricsFeatureProvider.action(mContext,
118                         MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
119                         expandedCondition.getMetricsConstant());
120
121                     updateExpandedCondition(expandedCondition);
122                 }
123             }
124         }
125     };
126
127     @Deprecated
128     public DashboardAdapter(Context context, Bundle savedInstanceState,
129         List<Condition> conditions) {
130         this(context, savedInstanceState, conditions, null, null);
131     }
132
133     public DashboardAdapter(Context context, Bundle savedInstanceState,
134             List<Condition> conditions, SuggestionParser suggestionParser,
135             SuggestionDismissController.Callback callback) {
136         List<Tile> suggestions = null;
137         List<DashboardCategory> categories = null;
138         int suggestionMode = DashboardData.SUGGESTION_MODE_DEFAULT;
139         int suggestionConditionMode = DashboardData.HEADER_MODE_DEFAULT;
140
141         mContext = context;
142         final FeatureFactory factory = FeatureFactory.getFactory(context);
143         mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
144         mDashboardFeatureProvider = factory.getDashboardFeatureProvider(context);
145         mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);
146         mCombineSuggestionAndCondition = mDashboardFeatureProvider.combineSuggestionAndCondition();
147         mCache = new IconCache(context);
148         mSuggestionParser = suggestionParser;
149         mCallback = callback;
150
151         setHasStableIds(true);
152
153         if (savedInstanceState != null) {
154             suggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
155             categories = savedInstanceState.getParcelableArrayList(STATE_CATEGORY_LIST);
156             suggestionConditionMode = savedInstanceState.getInt(
157                 STATE_SUGGESTION_CONDITION_MODE, suggestionConditionMode);
158             suggestionMode = savedInstanceState.getInt(
159                     STATE_SUGGESTION_MODE, DashboardData.SUGGESTION_MODE_DEFAULT);
160             mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
161                     STATE_SUGGESTIONS_SHOWN_LOGGED);
162         } else {
163             mSuggestionsShownLogged = new ArrayList<>();
164         }
165
166         mDashboardData = new DashboardData.Builder()
167                 .setConditions(conditions)
168                 .setSuggestions(suggestions)
169                 .setCategories(categories)
170                 .setSuggestionMode(suggestionMode)
171                 .setCombineSuggestionAndCondition(mCombineSuggestionAndCondition)
172                 .setSuggestionConditionMode(suggestionConditionMode)
173                 .build();
174     }
175
176     public List<Tile> getSuggestions() {
177         return mDashboardData.getSuggestions();
178     }
179
180     public void setCategoriesAndSuggestions(List<DashboardCategory> categories,
181             List<Tile> suggestions) {
182         if (mDashboardFeatureProvider.shouldTintIcon()) {
183             // TODO: Better place for tinting?
184             final TypedArray a = mContext.obtainStyledAttributes(new int[]{
185                     android.R.attr.colorControlNormal});
186             final int tintColor = a.getColor(0, mContext.getColor(R.color.fallback_tintColor));
187             a.recycle();
188             for (int i = 0; i < categories.size(); i++) {
189                 for (int j = 0; j < categories.get(i).tiles.size(); j++) {
190                     final Tile tile = categories.get(i).tiles.get(j);
191
192                     if (tile.isIconTintable) {
193                         // If this drawable is tintable, tint it to match the color.
194                         tile.icon.setTint(tintColor);
195                     }
196                 }
197             }
198
199             for (Tile suggestion : suggestions) {
200                 if (suggestion.isIconTintable) {
201                     suggestion.icon.setTint(tintColor);
202                 }
203             }
204         }
205
206         final DashboardData prevData = mDashboardData;
207         mDashboardData = new DashboardData.Builder(prevData)
208                 .setSuggestions(suggestions)
209                 .setCategories(categories)
210                 .build();
211         notifyDashboardDataChanged(prevData);
212         List<Tile> shownSuggestions = null;
213         if (mCombineSuggestionAndCondition) {
214             final int mode = mDashboardData.getSuggestionConditionMode();
215             if (mode == DashboardData.HEADER_MODE_DEFAULT) {
216                 shownSuggestions = suggestions.subList(0,
217                     Math.min(suggestions.size(), DashboardData.DEFAULT_SUGGESTION_COUNT));
218             } else if (mode != DashboardData.HEADER_MODE_COLLAPSED) {
219                 shownSuggestions = suggestions;
220             }
221         } else {
222             switch (mDashboardData.getSuggestionMode()) {
223                 case DashboardData.SUGGESTION_MODE_DEFAULT:
224                     shownSuggestions = suggestions.subList(0,
225                         Math.min(suggestions.size(), DashboardData.DEFAULT_SUGGESTION_COUNT));
226                     break;
227                 case DashboardData.SUGGESTION_MODE_EXPANDED:
228                     shownSuggestions = suggestions;
229                     break;
230             }
231         }
232         if (shownSuggestions != null) {
233             for (Tile suggestion : shownSuggestions) {
234                 final String identifier = mSuggestionFeatureProvider.getSuggestionIdentifier(
235                         mContext, suggestion);
236                 mMetricsFeatureProvider.action(
237                         mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, identifier);
238                 mSuggestionsShownLogged.add(identifier);
239             }
240         }
241     }
242
243     public void setCategory(List<DashboardCategory> category) {
244         final DashboardData prevData = mDashboardData;
245         Log.d(TAG, "adapter setCategory called");
246         mDashboardData = new DashboardData.Builder(prevData)
247                 .setCategories(category)
248                 .build();
249         notifyDashboardDataChanged(prevData);
250     }
251
252     public void setConditions(List<Condition> conditions) {
253         final DashboardData prevData = mDashboardData;
254         Log.d(TAG, "adapter setConditions called");
255         if (mCombineSuggestionAndCondition) {
256             mDashboardData = new DashboardData.Builder(prevData)
257                 .setConditions(conditions)
258                 .build();
259         } else {
260             mDashboardData = new DashboardData.Builder(prevData)
261                 .setConditions(conditions)
262                 .setExpandedCondition(null)
263                 .build();
264         }
265         notifyDashboardDataChanged(prevData);
266     }
267
268     public void onSuggestionDismissed() {
269         final List<Tile> suggestions = mDashboardData.getSuggestions();
270         if (suggestions != null && suggestions.size() == 1) {
271             // The only suggestion is dismissed, and the the empty suggestion container will
272             // remain as the dashboard item. Need to refresh the dashboard list.
273             final DashboardData prevData = mDashboardData;
274             mDashboardData = new DashboardData.Builder(prevData)
275                     .setSuggestions(null)
276                     .build();
277             notifyDashboardDataChanged(prevData);
278         }
279     }
280
281     @Override
282     public void notifySummaryChanged(Tile tile) {
283         final int position = mDashboardData.getPositionByTile(tile);
284         if (position != DashboardData.POSITION_NOT_FOUND) {
285             // Since usually tile in parameter and tile in mCategories are same instance,
286             // which is hard to be detected by DiffUtil, so we notifyItemChanged directly.
287             notifyItemChanged(position, mDashboardData.getItemTypeByPosition(position));
288         }
289     }
290
291     @Override
292     public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
293         final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
294         if (viewType == R.layout.suggestion_condition_header) {
295             return new SuggestionAndConditionHeaderHolder(view);
296         }
297         if (viewType == R.layout.suggestion_condition_container) {
298             return new SuggestionAndConditionContainerHolder(view);
299         }
300         return new DashboardItemHolder(view);
301     }
302
303     @Override
304     public void onBindViewHolder(DashboardItemHolder holder, int position) {
305         final int type = mDashboardData.getItemTypeByPosition(position);
306         switch (type) {
307             case R.layout.dashboard_category:
308                 onBindCategory(holder,
309                         (DashboardCategory) mDashboardData.getItemEntityByPosition(position));
310                 break;
311             case R.layout.dashboard_tile:
312                 final Tile tile = (Tile) mDashboardData.getItemEntityByPosition(position);
313                 onBindTile(holder, tile);
314                 holder.itemView.setTag(tile);
315                 holder.itemView.setOnClickListener(mTileClickListener);
316                 break;
317             case R.layout.suggestion_header:
318                 onBindSuggestionHeader(holder, (DashboardData.SuggestionHeaderData)
319                         mDashboardData.getItemEntityByPosition(position));
320                 break;
321             case R.layout.suggestion_tile:
322             case R.layout.suggestion_tile_card:
323                 final Tile suggestion = (Tile) mDashboardData.getItemEntityByPosition(position);
324                 final String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
325                         mContext, suggestion);
326                 // This is for cases when a suggestion is dismissed and the next one comes to view
327                 if (!mSuggestionsShownLogged.contains(suggestionId)) {
328                     mMetricsFeatureProvider.action(
329                             mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, suggestionId);
330                     mSuggestionsShownLogged.add(suggestionId);
331                 }
332                 onBindTile(holder, suggestion);
333                 View clickHandler = holder.itemView;
334                 // If a view with @android:id/primary is defined, use that as the click handler
335                 // instead.
336                 final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
337                 if (primaryAction != null) {
338                     clickHandler = primaryAction;
339                     // set the item view to disabled to remove any touch effects
340                     holder.itemView.setEnabled(false);
341                 }
342                 clickHandler.setOnClickListener(v -> {
343                     mMetricsFeatureProvider.action(mContext,
344                             MetricsEvent.ACTION_SETTINGS_SUGGESTION, suggestionId);
345                     ((SettingsActivity) mContext).startSuggestion(suggestion.intent);
346                 });
347                 break;
348             case R.layout.condition_card:
349                 final boolean isExpanded = mDashboardData.getItemEntityByPosition(position)
350                         == mDashboardData.getExpandedCondition();
351                 ConditionAdapterUtils.bindViews(
352                         (Condition) mDashboardData.getItemEntityByPosition(position),
353                         holder, isExpanded, mConditionClickListener, v -> onExpandClick(v));
354                 break;
355             case R.layout.suggestion_condition_container:
356                 onBindConditionAndSuggestion(
357                     (SuggestionAndConditionContainerHolder) holder, position);
358                 break;
359             case R.layout.suggestion_condition_header:
360                 /* There are 2 different headers for the suggestions/conditions section. To minimize
361                    visual animation when expanding and collapsing the suggestions/conditions, we are
362                    using the same layout to represent the 2 headers:
363                    1. Suggestion header - when there is any suggestion shown, the suggestion header
364                       will be the first item on the section. It only has the text "Suggestion", and
365                       do nothing when clicked. This header will not be shown when the section is
366                       collapsed, in which case, the SuggestionCondition header will be
367                       shown instead to show the summary info.
368                    2. SuggestionCondition header - the header that shows the summary info for the
369                       suggestion/condition that is currently hidden. It has the expand button to
370                       expand the section. */
371                 if (mDashboardData.getDisplayableSuggestionCount() > 0 && position == 1
372                         && mDashboardData.getSuggestionConditionMode()
373                             != DashboardData.HEADER_MODE_COLLAPSED) {
374                     onBindSuggestionHeader((SuggestionAndConditionHeaderHolder) holder);
375                 } else {
376                     onBindSuggestionConditionHeader((SuggestionAndConditionHeaderHolder) holder,
377                         (SuggestionConditionHeaderData)
378                             mDashboardData.getItemEntityByPosition(position));
379                 }
380                 break;
381             case R.layout.suggestion_condition_footer:
382                 holder.itemView.setOnClickListener(v -> {
383                     mMetricsFeatureProvider.action(mContext,
384                             MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);
385                     DashboardData prevData = mDashboardData;
386                     mDashboardData = new DashboardData.Builder(prevData).setSuggestionConditionMode(
387                         DashboardData.HEADER_MODE_COLLAPSED).build();
388                     notifyDashboardDataChanged(prevData);
389                     mRecyclerView.scrollToPosition(1);
390                 });
391                 break;
392         }
393     }
394
395     @Override
396     public long getItemId(int position) {
397         return mDashboardData.getItemIdByPosition(position);
398     }
399
400     @Override
401     public int getItemViewType(int position) {
402         return mDashboardData.getItemTypeByPosition(position);
403     }
404
405     @Override
406     public int getItemCount() {
407         return mDashboardData.size();
408     }
409
410     @Override
411     public void onAttachedToRecyclerView(RecyclerView recyclerView) {
412         super.onAttachedToRecyclerView(recyclerView);
413         // save the view so that we can scroll it when expanding/collapsing the suggestion and
414         // conditions.
415         mRecyclerView = recyclerView;
416     }
417
418     public void onPause() {
419         if (mDashboardData.getSuggestions() == null) {
420             return;
421         }
422         for (Tile suggestion : mDashboardData.getSuggestions()) {
423             String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
424                     mContext, suggestion);
425             if (mSuggestionsShownLogged.contains(suggestionId)) {
426                 mMetricsFeatureProvider.action(
427                         mContext, MetricsEvent.ACTION_HIDE_SETTINGS_SUGGESTION, suggestionId);
428             }
429         }
430         mSuggestionsShownLogged.clear();
431     }
432
433     // condition card is always expanded in new suggestion/condition UI.
434     // TODO: Remove when completely move to new suggestion/condition UI
435     @Deprecated
436     public void onExpandClick(View v) {
437         Condition expandedCondition = mDashboardData.getExpandedCondition();
438         if (v.getTag() == expandedCondition) {
439             mMetricsFeatureProvider.action(mContext,
440                     MetricsEvent.ACTION_SETTINGS_CONDITION_COLLAPSE,
441                     expandedCondition.getMetricsConstant());
442             expandedCondition = null;
443         } else {
444             expandedCondition = (Condition) v.getTag();
445             mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
446                     expandedCondition.getMetricsConstant());
447         }
448
449         updateExpandedCondition(expandedCondition);
450     }
451
452     public Object getItem(long itemId) {
453         return mDashboardData.getItemEntityById(itemId);
454     }
455
456     public Tile getSuggestion(int position) {
457         if (mCombineSuggestionAndCondition) {
458             return mSuggestionAdapter.getSuggestion(position);
459         }
460         return (Tile) getItem(getItemId(position));
461     }
462
463     @VisibleForTesting
464     void notifyDashboardDataChanged(DashboardData prevData) {
465         if (mFirstFrameDrawn && prevData != null) {
466             final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DashboardData
467                     .ItemsDataDiffCallback(prevData.getItemList(), mDashboardData.getItemList()));
468             diffResult.dispatchUpdatesTo(this);
469         } else {
470             mFirstFrameDrawn = true;
471             notifyDataSetChanged();
472         }
473     }
474
475     private void updateExpandedCondition(Condition condition) {
476         final DashboardData prevData = mDashboardData;
477         mDashboardData = new DashboardData.Builder(prevData)
478                 .setExpandedCondition(condition)
479                 .build();
480         notifyDashboardDataChanged(prevData);
481     }
482
483     @VisibleForTesting
484     void onBindSuggestionHeader(final DashboardItemHolder holder, DashboardData
485             .SuggestionHeaderData data) {
486         final boolean moreSuggestions = data.hasMoreSuggestions;
487         final int undisplayedSuggestionCount = data.undisplayedSuggestionCount;
488
489         holder.icon.setImageResource(moreSuggestions ? R.drawable.ic_expand_more
490                 : R.drawable.ic_expand_less);
491         holder.title.setText(mContext.getString(R.string.suggestions_title, data.suggestionSize));
492         String summaryContentDescription;
493         if (moreSuggestions) {
494             summaryContentDescription = mContext.getResources().getQuantityString(
495                     R.plurals.settings_suggestion_header_summary_hidden_items,
496                     undisplayedSuggestionCount, undisplayedSuggestionCount);
497         } else {
498             summaryContentDescription = mContext.getString(R.string.condition_expand_hide);
499         }
500         holder.summary.setContentDescription(summaryContentDescription);
501
502         if (undisplayedSuggestionCount == 0) {
503             holder.summary.setText(null);
504         } else {
505             holder.summary.setText(
506                     mContext.getString(R.string.suggestions_summary, undisplayedSuggestionCount));
507         }
508         holder.itemView.setOnClickListener(v -> {
509             final int suggestionMode;
510             if (moreSuggestions) {
511                 suggestionMode = DashboardData.SUGGESTION_MODE_EXPANDED;
512                 logSuggestions();
513             } else {
514                 suggestionMode = DashboardData.SUGGESTION_MODE_COLLAPSED;
515             }
516
517             DashboardData prevData = mDashboardData;
518             mDashboardData = new DashboardData.Builder(prevData)
519                     .setSuggestionMode(suggestionMode)
520                     .build();
521             notifyDashboardDataChanged(prevData);
522         });
523     }
524
525     private void logSuggestions() {
526         for (Tile suggestion : mDashboardData.getSuggestions()) {
527             final String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
528                 mContext, suggestion);
529             if (!mSuggestionsShownLogged.contains(suggestionId)) {
530                 mMetricsFeatureProvider.action(
531                     mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION,
532                     suggestionId);
533                 mSuggestionsShownLogged.add(suggestionId);
534             }
535         }
536     }
537
538     private void onBindSuggestionHeader(final SuggestionAndConditionHeaderHolder holder) {
539         holder.title.setText(R.string.suggestions_title);
540         holder.title.setTextColor(Color.BLACK);
541         holder.icon.setVisibility(View.INVISIBLE);
542         holder.icons.removeAllViews();
543         holder.icons.setVisibility(View.INVISIBLE);
544         holder.summary.setVisibility(View.INVISIBLE);
545         holder.expandIndicator.setVisibility(View.INVISIBLE);
546         holder.itemView.setOnClickListener(null);
547     }
548
549     private void onBindSuggestionConditionHeader(final SuggestionAndConditionHeaderHolder holder,
550             SuggestionConditionHeaderData data) {
551         final int curMode = mDashboardData.getSuggestionConditionMode();
552         final int nextMode = data.hiddenSuggestionCount > 0 && data.conditionCount > 0
553             && curMode != DashboardData.HEADER_MODE_SUGGESTION_EXPANDED
554             ? DashboardData.HEADER_MODE_SUGGESTION_EXPANDED
555             : DashboardData.HEADER_MODE_FULLY_EXPANDED;
556         final boolean moreSuggestions = data.hiddenSuggestionCount > 0;
557         final boolean hasConditions = data.conditionCount > 0;
558         if (data.conditionCount > 0) {
559             holder.icon.setImageIcon(data.conditionIcons.get(0));
560             holder.icon.setVisibility(View.VISIBLE);
561             if (data.conditionCount == 1) {
562                 holder.title.setText(data.title);
563                 holder.title.setTextColor(Utils.getColorAccent(mContext));
564                 holder.icons.setVisibility(View.INVISIBLE);
565             } else {
566                 holder.title.setText(null);
567                 updateConditionIcons(data.conditionIcons, holder.icons);
568                 holder.icons.setVisibility(View.VISIBLE);
569             }
570         } else {
571             holder.icon.setVisibility(View.INVISIBLE);
572             holder.icons.setVisibility(View.INVISIBLE);
573         }
574
575         if (data.hiddenSuggestionCount > 0) {
576             holder.summary.setTextColor(Color.BLACK);
577             if (curMode == DashboardData.HEADER_MODE_COLLAPSED) {
578                 if (data.conditionCount > 0) {
579                     holder.summary.setText(mContext.getResources().getQuantityString(
580                         R.plurals.suggestions_collapsed_summary,
581                         data.hiddenSuggestionCount, data.hiddenSuggestionCount));
582                 } else {
583                     holder.title.setText(mContext.getResources().getQuantityString(
584                         R.plurals.suggestions_collapsed_title,
585                         data.hiddenSuggestionCount, data.hiddenSuggestionCount));
586                     holder.title.setTextColor(Color.BLACK);
587                     holder.summary.setText(null);
588                 }
589             } else if (curMode == DashboardData.HEADER_MODE_DEFAULT) {
590                 if (data.conditionCount > 0) {
591                     holder.summary.setText(mContext.getString(
592                         R.string.suggestions_summary, data.hiddenSuggestionCount));
593                 } else {
594                     holder.title.setText(mContext.getString(
595                         R.string.suggestions_more_title, data.hiddenSuggestionCount));
596                     holder.title.setTextColor(Color.BLACK);
597                     holder.summary.setText(null);
598                 }
599             }
600         } else if (data.conditionCount > 1) {
601             holder.summary.setTextColor(Utils.getColorAccent(mContext));
602             holder.summary.setText(
603                 mContext.getString(R.string.condition_summary, data.conditionCount));
604         } else {
605             holder.summary.setText(null);
606         }
607         holder.summary.setVisibility(View.VISIBLE);
608         holder.expandIndicator.setVisibility(View.VISIBLE);
609
610         holder.itemView.setOnClickListener(v -> {
611             if (moreSuggestions ) {
612                 logSuggestions();
613             } else if (hasConditions) {
614                 mMetricsFeatureProvider.action(mContext,
615                     MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true);
616             }
617             DashboardData prevData = mDashboardData;
618             final boolean wasCollapsed = curMode == DashboardData.HEADER_MODE_COLLAPSED;
619             mDashboardData = new DashboardData.Builder(prevData)
620                 .setSuggestionConditionMode(nextMode).build();
621             notifyDashboardDataChanged(prevData);
622             if (wasCollapsed) {
623                 mRecyclerView.scrollToPosition(1);
624             }
625         });
626     }
627
628     private void onBindConditionAndSuggestion(final SuggestionAndConditionContainerHolder holder,
629             int position) {
630         RecyclerView.Adapter<DashboardItemHolder> adapter;
631         // If there is suggestions to show, it will be at position 2 (position 0 = header spacer,
632         // position 1 is suggestion header.
633         if (position == 2 && mDashboardData.getSuggestions() != null) {
634             mSuggestionAdapter = new SuggestionAdapter(mContext, (List<Tile>)
635                 mDashboardData.getItemEntityByPosition(position), mSuggestionsShownLogged);
636             adapter = mSuggestionAdapter;
637             mSuggestionDismissHandler = new SuggestionDismissController(mContext,
638                 holder.data, mSuggestionParser, mCallback);
639         } else {
640             ConditionAdapterUtils.addDismiss(holder.data);
641             adapter = new ConditionAdapter(mContext,
642                 (List<Condition>) mDashboardData.getItemEntityByPosition(position),
643                     mDashboardData.getSuggestionConditionMode());
644         }
645         holder.data.setLayoutManager(new LinearLayoutManager(mContext));
646         holder.data.setAdapter(adapter);
647     }
648
649     private void onBindTile(DashboardItemHolder holder, Tile tile) {
650         if (tile.remoteViews != null) {
651             final ViewGroup itemView = (ViewGroup) holder.itemView;
652             itemView.removeAllViews();
653             itemView.addView(tile.remoteViews.apply(itemView.getContext(), itemView));
654         } else {
655             holder.icon.setImageDrawable(mCache.getIcon(tile.icon));
656             holder.title.setText(tile.title);
657             if (!TextUtils.isEmpty(tile.summary)) {
658                 holder.summary.setText(tile.summary);
659                 holder.summary.setVisibility(View.VISIBLE);
660             } else {
661                 holder.summary.setVisibility(View.GONE);
662             }
663         }
664     }
665
666     private void onBindCategory(DashboardItemHolder holder, DashboardCategory category) {
667         holder.title.setText(category.title);
668     }
669
670     void onSaveInstanceState(Bundle outState) {
671         final List<Tile> suggestions = mDashboardData.getSuggestions();
672         final List<DashboardCategory> categories = mDashboardData.getCategories();
673         if (suggestions != null) {
674             outState.putParcelableArrayList(STATE_SUGGESTION_LIST, new ArrayList<>(suggestions));
675         }
676         if (categories != null) {
677             outState.putParcelableArrayList(STATE_CATEGORY_LIST, new ArrayList<>(categories));
678         }
679         outState.putInt(STATE_SUGGESTION_MODE, mDashboardData.getSuggestionMode());
680         outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
681         outState.putInt(STATE_SUGGESTION_CONDITION_MODE,
682             mDashboardData.getSuggestionConditionMode());
683     }
684
685     private void updateConditionIcons(List<Icon> icons, ViewGroup parent) {
686         if (icons == null || icons.size() < 2) {
687             parent.setVisibility(View.INVISIBLE);
688             return;
689         }
690         final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
691         parent.removeAllViews();
692         for (int i = 1, size = icons.size(); i < size; i++) {
693             ImageView icon = (ImageView) inflater.inflate(
694                 R.layout.condition_header_icon, parent, false);
695             icon.setImageIcon(icons.get(i));
696             parent.addView(icon);
697         }
698         parent.setVisibility(View.VISIBLE);
699     }
700
701     public static class IconCache {
702         private final Context mContext;
703         private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
704
705         public IconCache(Context context) {
706             mContext = context;
707         }
708
709         public Drawable getIcon(Icon icon) {
710             Drawable drawable = mMap.get(icon);
711             if (drawable == null) {
712                 drawable = icon.loadDrawable(mContext);
713                 mMap.put(icon, drawable);
714             }
715             return drawable;
716         }
717     }
718
719     public static class DashboardItemHolder extends RecyclerView.ViewHolder {
720         public final ImageView icon;
721         public final TextView title;
722         public final TextView summary;
723
724         public DashboardItemHolder(View itemView) {
725             super(itemView);
726             icon = itemView.findViewById(android.R.id.icon);
727             title = itemView.findViewById(android.R.id.title);
728             summary = itemView.findViewById(android.R.id.summary);
729         }
730     }
731
732     public static class SuggestionAndConditionHeaderHolder extends DashboardItemHolder {
733         public final LinearLayout icons;
734         public final ImageView expandIndicator;
735
736         public SuggestionAndConditionHeaderHolder(View itemView) {
737             super(itemView);
738             icons = itemView.findViewById(id.additional_icons);
739             expandIndicator = itemView.findViewById(id.expand_indicator);
740         }
741     }
742
743     public static class SuggestionAndConditionContainerHolder extends DashboardItemHolder {
744         public final RecyclerView data;
745
746         public SuggestionAndConditionContainerHolder(View itemView) {
747             super(itemView);
748             data = itemView.findViewById(id.data);
749         }
750     }
751
752 }