1 /* 2 * Copyright (C) 2018 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 17 package com.android.settings.homepage.contextualcards.conditional; 18 19 import android.content.Context; 20 import android.util.ArrayMap; 21 22 import androidx.annotation.VisibleForTesting; 23 24 import com.android.settings.homepage.contextualcards.ContextualCard; 25 import com.android.settings.homepage.contextualcards.ContextualCardController; 26 import com.android.settings.homepage.contextualcards.ContextualCardUpdateListener; 27 import com.android.settingslib.core.lifecycle.LifecycleObserver; 28 import com.android.settingslib.core.lifecycle.events.OnStart; 29 import com.android.settingslib.core.lifecycle.events.OnStop; 30 31 import java.util.ArrayList; 32 import java.util.Collections; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.stream.Collectors; 36 37 /** 38 * This controller triggers the loading of conditional cards and monitors state changes to 39 * update the homepage. 40 */ 41 public class ConditionContextualCardController implements ContextualCardController, 42 ConditionListener, LifecycleObserver, OnStart, OnStop { 43 public static final int EXPANDING_THRESHOLD = 0; 44 45 private static final double UNSUPPORTED_RANKING = -99999.0; 46 private static final String TAG = "ConditionCtxCardCtrl"; 47 private static final String CONDITION_FOOTER = "condition_footer"; 48 private static final String CONDITION_HEADER = "condition_header"; 49 50 private final Context mContext; 51 private final ConditionManager mConditionManager; 52 53 private ContextualCardUpdateListener mListener; 54 private boolean mIsExpanded; 55 ConditionContextualCardController(Context context)56 public ConditionContextualCardController(Context context) { 57 mContext = context; 58 mConditionManager = new ConditionManager(context.getApplicationContext(), this); 59 mConditionManager.startMonitoringStateChange(); 60 } 61 setIsExpanded(boolean isExpanded)62 public void setIsExpanded(boolean isExpanded) { 63 mIsExpanded = isExpanded; 64 } 65 66 @Override setCardUpdateListener(ContextualCardUpdateListener listener)67 public void setCardUpdateListener(ContextualCardUpdateListener listener) { 68 mListener = listener; 69 } 70 71 @Override getCardType()72 public int getCardType() { 73 return ContextualCard.CardType.CONDITIONAL; 74 } 75 76 @Override onStart()77 public void onStart() { 78 mConditionManager.startMonitoringStateChange(); 79 } 80 81 @Override onStop()82 public void onStop() { 83 mConditionManager.stopMonitoringStateChange(); 84 } 85 86 @Override onPrimaryClick(ContextualCard contextualCard)87 public void onPrimaryClick(ContextualCard contextualCard) { 88 final ConditionalContextualCard card = (ConditionalContextualCard) contextualCard; 89 mConditionManager.onPrimaryClick(mContext, card.getConditionId()); 90 } 91 92 @Override onActionClick(ContextualCard contextualCard)93 public void onActionClick(ContextualCard contextualCard) { 94 final ConditionalContextualCard card = (ConditionalContextualCard) contextualCard; 95 mConditionManager.onActionClick(card.getConditionId()); 96 } 97 98 @Override onDismissed(ContextualCard contextualCard)99 public void onDismissed(ContextualCard contextualCard) { 100 101 } 102 103 @Override onConditionsChanged()104 public void onConditionsChanged() { 105 if (mListener == null) { 106 return; 107 } 108 final List<ContextualCard> conditionCards = mConditionManager.getDisplayableCards(); 109 final Map<Integer, List<ContextualCard>> conditionalCards = 110 buildConditionalCardsWithFooterOrHeader(conditionCards); 111 mListener.onContextualCardUpdated(conditionalCards); 112 } 113 114 /** 115 * According to conditional cards, build a map that includes conditional cards, header card and 116 * footer card. 117 * 118 * Rules: 119 * - The last one of conditional cards will be displayed as a full-width card if the size of 120 * conditional cards is odd number. The rest will be displayed as a half-width card. 121 * - By default conditional cards will be collapsed if there are more than TWO cards. 122 * 123 * For examples: 124 * - Only one conditional card: Returns a map that contains a full-width conditional card, 125 * no header card and no footer card. 126 * <p>Map{(CONDITIONAL, conditionCards), (CONDITIONAL_FOOTER, EMPTY_LIST), (CONDITIONAL_HEADER, 127 * EMPTY_LIST)}</p> 128 * - Two conditional cards: Returns a map that contains two half-width conditional cards, 129 * no header card and no footer card. 130 * <p>Map{(CONDITIONAL, conditionCards), (CONDITIONAL_FOOTER, EMPTY_LIST), (CONDITIONAL_HEADER, 131 * EMPTY_LIST)}</p> 132 * - Three conditional cards or above: By default, returns a map that contains no conditional 133 * card, one header card and no footer card. If conditional cards are expanded, will returns a 134 * map that contains three conditional cards, no header card and one footer card. 135 * If expanding conditional cards: 136 * <p>Map{(CONDITIONAL, conditionCards), (CONDITIONAL_FOOTER, footerCards), (CONDITIONAL_HEADER, 137 * EMPTY_LIST)}</p> 138 * If collapsing conditional cards: 139 * <p>Map{(CONDITIONAL, EMPTY_LIST), (CONDITIONAL_FOOTER, EMPTY_LIST), (CONDITIONAL_HEADER, 140 * headerCards)}</p> 141 * 142 * @param conditionCards A list of conditional cards that are from {@link 143 * ConditionManager#getDisplayableCards} 144 * @return A map contained three types of lists 145 */ 146 @VisibleForTesting buildConditionalCardsWithFooterOrHeader( List<ContextualCard> conditionCards)147 Map<Integer, List<ContextualCard>> buildConditionalCardsWithFooterOrHeader( 148 List<ContextualCard> conditionCards) { 149 final Map<Integer, List<ContextualCard>> conditionalCards = new ArrayMap<>(); 150 conditionalCards.put(ContextualCard.CardType.CONDITIONAL, 151 getExpandedConditionalCards(conditionCards)); 152 conditionalCards.put(ContextualCard.CardType.CONDITIONAL_FOOTER, 153 getConditionalFooterCard(conditionCards)); 154 conditionalCards.put(ContextualCard.CardType.CONDITIONAL_HEADER, 155 getConditionalHeaderCard(conditionCards)); 156 return conditionalCards; 157 } 158 getExpandedConditionalCards(List<ContextualCard> conditionCards)159 private List<ContextualCard> getExpandedConditionalCards(List<ContextualCard> conditionCards) { 160 if (conditionCards.isEmpty() || (conditionCards.size() > EXPANDING_THRESHOLD 161 && !mIsExpanded)) { 162 return Collections.EMPTY_LIST; 163 } 164 final List<ContextualCard> expandedCards = conditionCards.stream().collect( 165 Collectors.toList()); 166 final boolean isOddNumber = expandedCards.size() % 2 == 1; 167 if (isOddNumber) { 168 final int lastIndex = expandedCards.size() - 1; 169 final ConditionalContextualCard card = 170 (ConditionalContextualCard) expandedCards.get(lastIndex); 171 expandedCards.set(lastIndex, card.mutate().setViewType( 172 ConditionContextualCardRenderer.VIEW_TYPE_FULL_WIDTH).build()); 173 } 174 return expandedCards; 175 } 176 getConditionalFooterCard(List<ContextualCard> conditionCards)177 private List<ContextualCard> getConditionalFooterCard(List<ContextualCard> conditionCards) { 178 if (!conditionCards.isEmpty() && mIsExpanded 179 && conditionCards.size() > EXPANDING_THRESHOLD) { 180 final List<ContextualCard> footerCards = new ArrayList<>(); 181 footerCards.add(new ConditionFooterContextualCard.Builder() 182 .setName(CONDITION_FOOTER) 183 .setRankingScore(UNSUPPORTED_RANKING) 184 .setViewType(ConditionFooterContextualCardRenderer.VIEW_TYPE) 185 .build()); 186 return footerCards; 187 } 188 return Collections.EMPTY_LIST; 189 } 190 getConditionalHeaderCard(List<ContextualCard> conditionCards)191 private List<ContextualCard> getConditionalHeaderCard(List<ContextualCard> conditionCards) { 192 if (!conditionCards.isEmpty() && !mIsExpanded 193 && conditionCards.size() > EXPANDING_THRESHOLD) { 194 final List<ContextualCard> headerCards = new ArrayList<>(); 195 headerCards.add(new ConditionHeaderContextualCard.Builder() 196 .setConditionalCards(conditionCards) 197 .setName(CONDITION_HEADER) 198 .setRankingScore(UNSUPPORTED_RANKING) 199 .setViewType(ConditionHeaderContextualCardRenderer.VIEW_TYPE) 200 .build()); 201 return headerCards; 202 } 203 return Collections.EMPTY_LIST; 204 } 205 } 206