2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.settings.dashboard;
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;
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;
58 import java.util.ArrayList;
59 import java.util.List;
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";
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;
85 DashboardData mDashboardData;
87 private View.OnClickListener mTileClickListener = new View.OnClickListener() {
89 public void onClick(View v) {
90 //TODO: get rid of setTag/getTag
91 mDashboardFeatureProvider.openTileIntent((Activity) mContext, (Tile) v.getTag());
95 private View.OnClickListener mConditionClickListener = new View.OnClickListener() {
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();
107 Condition expandedCondition = mDashboardData.getExpandedCondition();
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();
116 expandedCondition = (Condition) v.getTag();
117 mMetricsFeatureProvider.action(mContext,
118 MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
119 expandedCondition.getMetricsConstant());
121 updateExpandedCondition(expandedCondition);
128 public DashboardAdapter(Context context, Bundle savedInstanceState,
129 List<Condition> conditions) {
130 this(context, savedInstanceState, conditions, null, null);
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;
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;
151 setHasStableIds(true);
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);
163 mSuggestionsShownLogged = new ArrayList<>();
166 mDashboardData = new DashboardData.Builder()
167 .setConditions(conditions)
168 .setSuggestions(suggestions)
169 .setCategories(categories)
170 .setSuggestionMode(suggestionMode)
171 .setCombineSuggestionAndCondition(mCombineSuggestionAndCondition)
172 .setSuggestionConditionMode(suggestionConditionMode)
176 public List<Tile> getSuggestions() {
177 return mDashboardData.getSuggestions();
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));
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);
192 if (tile.isIconTintable) {
193 // If this drawable is tintable, tint it to match the color.
194 tile.icon.setTint(tintColor);
199 for (Tile suggestion : suggestions) {
200 if (suggestion.isIconTintable) {
201 suggestion.icon.setTint(tintColor);
206 final DashboardData prevData = mDashboardData;
207 mDashboardData = new DashboardData.Builder(prevData)
208 .setSuggestions(suggestions)
209 .setCategories(categories)
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;
222 switch (mDashboardData.getSuggestionMode()) {
223 case DashboardData.SUGGESTION_MODE_DEFAULT:
224 shownSuggestions = suggestions.subList(0,
225 Math.min(suggestions.size(), DashboardData.DEFAULT_SUGGESTION_COUNT));
227 case DashboardData.SUGGESTION_MODE_EXPANDED:
228 shownSuggestions = suggestions;
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);
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)
249 notifyDashboardDataChanged(prevData);
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)
260 mDashboardData = new DashboardData.Builder(prevData)
261 .setConditions(conditions)
262 .setExpandedCondition(null)
265 notifyDashboardDataChanged(prevData);
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)
277 notifyDashboardDataChanged(prevData);
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));
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);
297 if (viewType == R.layout.suggestion_condition_container) {
298 return new SuggestionAndConditionContainerHolder(view);
300 return new DashboardItemHolder(view);
304 public void onBindViewHolder(DashboardItemHolder holder, int position) {
305 final int type = mDashboardData.getItemTypeByPosition(position);
307 case R.layout.dashboard_category:
308 onBindCategory(holder,
309 (DashboardCategory) mDashboardData.getItemEntityByPosition(position));
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);
317 case R.layout.suggestion_header:
318 onBindSuggestionHeader(holder, (DashboardData.SuggestionHeaderData)
319 mDashboardData.getItemEntityByPosition(position));
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);
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
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);
342 clickHandler.setOnClickListener(v -> {
343 mMetricsFeatureProvider.action(mContext,
344 MetricsEvent.ACTION_SETTINGS_SUGGESTION, suggestionId);
345 ((SettingsActivity) mContext).startSuggestion(suggestion.intent);
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));
355 case R.layout.suggestion_condition_container:
356 onBindConditionAndSuggestion(
357 (SuggestionAndConditionContainerHolder) holder, position);
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);
376 onBindSuggestionConditionHeader((SuggestionAndConditionHeaderHolder) holder,
377 (SuggestionConditionHeaderData)
378 mDashboardData.getItemEntityByPosition(position));
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);
396 public long getItemId(int position) {
397 return mDashboardData.getItemIdByPosition(position);
401 public int getItemViewType(int position) {
402 return mDashboardData.getItemTypeByPosition(position);
406 public int getItemCount() {
407 return mDashboardData.size();
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
415 mRecyclerView = recyclerView;
418 public void onPause() {
419 if (mDashboardData.getSuggestions() == null) {
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);
430 mSuggestionsShownLogged.clear();
433 // condition card is always expanded in new suggestion/condition UI.
434 // TODO: Remove when completely move to new suggestion/condition UI
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;
444 expandedCondition = (Condition) v.getTag();
445 mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
446 expandedCondition.getMetricsConstant());
449 updateExpandedCondition(expandedCondition);
452 public Object getItem(long itemId) {
453 return mDashboardData.getItemEntityById(itemId);
456 public Tile getSuggestion(int position) {
457 if (mCombineSuggestionAndCondition) {
458 return mSuggestionAdapter.getSuggestion(position);
460 return (Tile) getItem(getItemId(position));
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);
470 mFirstFrameDrawn = true;
471 notifyDataSetChanged();
475 private void updateExpandedCondition(Condition condition) {
476 final DashboardData prevData = mDashboardData;
477 mDashboardData = new DashboardData.Builder(prevData)
478 .setExpandedCondition(condition)
480 notifyDashboardDataChanged(prevData);
484 void onBindSuggestionHeader(final DashboardItemHolder holder, DashboardData
485 .SuggestionHeaderData data) {
486 final boolean moreSuggestions = data.hasMoreSuggestions;
487 final int undisplayedSuggestionCount = data.undisplayedSuggestionCount;
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);
498 summaryContentDescription = mContext.getString(R.string.condition_expand_hide);
500 holder.summary.setContentDescription(summaryContentDescription);
502 if (undisplayedSuggestionCount == 0) {
503 holder.summary.setText(null);
505 holder.summary.setText(
506 mContext.getString(R.string.suggestions_summary, undisplayedSuggestionCount));
508 holder.itemView.setOnClickListener(v -> {
509 final int suggestionMode;
510 if (moreSuggestions) {
511 suggestionMode = DashboardData.SUGGESTION_MODE_EXPANDED;
514 suggestionMode = DashboardData.SUGGESTION_MODE_COLLAPSED;
517 DashboardData prevData = mDashboardData;
518 mDashboardData = new DashboardData.Builder(prevData)
519 .setSuggestionMode(suggestionMode)
521 notifyDashboardDataChanged(prevData);
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,
533 mSuggestionsShownLogged.add(suggestionId);
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);
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);
566 holder.title.setText(null);
567 updateConditionIcons(data.conditionIcons, holder.icons);
568 holder.icons.setVisibility(View.VISIBLE);
571 holder.icon.setVisibility(View.INVISIBLE);
572 holder.icons.setVisibility(View.INVISIBLE);
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));
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);
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));
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);
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));
605 holder.summary.setText(null);
607 holder.summary.setVisibility(View.VISIBLE);
608 holder.expandIndicator.setVisibility(View.VISIBLE);
610 holder.itemView.setOnClickListener(v -> {
611 if (moreSuggestions ) {
613 } else if (hasConditions) {
614 mMetricsFeatureProvider.action(mContext,
615 MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true);
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);
623 mRecyclerView.scrollToPosition(1);
628 private void onBindConditionAndSuggestion(final SuggestionAndConditionContainerHolder holder,
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);
640 ConditionAdapterUtils.addDismiss(holder.data);
641 adapter = new ConditionAdapter(mContext,
642 (List<Condition>) mDashboardData.getItemEntityByPosition(position),
643 mDashboardData.getSuggestionConditionMode());
645 holder.data.setLayoutManager(new LinearLayoutManager(mContext));
646 holder.data.setAdapter(adapter);
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));
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);
661 holder.summary.setVisibility(View.GONE);
666 private void onBindCategory(DashboardItemHolder holder, DashboardCategory category) {
667 holder.title.setText(category.title);
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));
676 if (categories != null) {
677 outState.putParcelableArrayList(STATE_CATEGORY_LIST, new ArrayList<>(categories));
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());
685 private void updateConditionIcons(List<Icon> icons, ViewGroup parent) {
686 if (icons == null || icons.size() < 2) {
687 parent.setVisibility(View.INVISIBLE);
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);
698 parent.setVisibility(View.VISIBLE);
701 public static class IconCache {
702 private final Context mContext;
703 private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
705 public IconCache(Context context) {
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);
719 public static class DashboardItemHolder extends RecyclerView.ViewHolder {
720 public final ImageView icon;
721 public final TextView title;
722 public final TextView summary;
724 public DashboardItemHolder(View 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);
732 public static class SuggestionAndConditionHeaderHolder extends DashboardItemHolder {
733 public final LinearLayout icons;
734 public final ImageView expandIndicator;
736 public SuggestionAndConditionHeaderHolder(View itemView) {
738 icons = itemView.findViewById(id.additional_icons);
739 expandIndicator = itemView.findViewById(id.expand_indicator);
743 public static class SuggestionAndConditionContainerHolder extends DashboardItemHolder {
744 public final RecyclerView data;
746 public SuggestionAndConditionContainerHolder(View itemView) {
748 data = itemView.findViewById(id.data);