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.Log; 21 22 import androidx.annotation.NonNull; 23 import androidx.annotation.VisibleForTesting; 24 25 import com.android.settings.homepage.contextualcards.ContextualCard; 26 import com.android.settingslib.utils.ThreadUtils; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 import java.util.concurrent.Callable; 31 import java.util.concurrent.ExecutionException; 32 import java.util.concurrent.Future; 33 import java.util.concurrent.TimeUnit; 34 import java.util.concurrent.TimeoutException; 35 36 public class ConditionManager { 37 private static final String TAG = "ConditionManager"; 38 39 @VisibleForTesting 40 final List<ConditionalCardController> mCardControllers; 41 42 private static final long DISPLAYABLE_CHECKER_TIMEOUT_MS = 20; 43 44 private final Context mAppContext; 45 private final ConditionListener mListener; 46 47 private boolean mIsListeningToStateChange; 48 ConditionManager(Context context, ConditionListener listener)49 public ConditionManager(Context context, ConditionListener listener) { 50 mAppContext = context.getApplicationContext(); 51 mCardControllers = new ArrayList<>(); 52 mListener = listener; 53 initCandidates(); 54 } 55 56 /** 57 * Returns a list of {@link ContextualCard}s eligible for display. 58 */ getDisplayableCards()59 public List<ContextualCard> getDisplayableCards() { 60 final List<ContextualCard> cards = new ArrayList<>(); 61 final List<Future<ContextualCard>> displayableCards = new ArrayList<>(); 62 // Check displayable future 63 for (ConditionalCardController card : mCardControllers) { 64 final DisplayableChecker checker = new DisplayableChecker(getController(card.getId())); 65 displayableCards.add(ThreadUtils.postOnBackgroundThread(checker)); 66 } 67 // Collect future and add displayable cards 68 for (Future<ContextualCard> cardFuture : displayableCards) { 69 try { 70 final ContextualCard card = cardFuture.get( 71 DISPLAYABLE_CHECKER_TIMEOUT_MS, TimeUnit.MILLISECONDS); 72 if (card != null) { 73 cards.add(card); 74 } 75 } catch (InterruptedException | ExecutionException | TimeoutException e) { 76 Log.w(TAG, "Failed to get displayable state for card, likely timeout. Skipping", e); 77 } 78 } 79 return cards; 80 } 81 82 /** 83 * Handler when the card is clicked. 84 * 85 * @see {@link ConditionalCardController#onPrimaryClick(Context)} 86 */ onPrimaryClick(Context context, long id)87 public void onPrimaryClick(Context context, long id) { 88 getController(id).onPrimaryClick(context); 89 } 90 91 /** 92 * Handler when the card action is clicked. 93 * 94 * @see {@link ConditionalCardController#onActionClick()} 95 */ onActionClick(long id)96 public void onActionClick(long id) { 97 getController(id).onActionClick(); 98 } 99 100 /** 101 * Start monitoring state change for all conditions 102 */ startMonitoringStateChange()103 public void startMonitoringStateChange() { 104 if (mIsListeningToStateChange) { 105 Log.d(TAG, "Already listening to condition state changes, skipping monitor setup"); 106 } else { 107 mIsListeningToStateChange = true; 108 for (ConditionalCardController controller : mCardControllers) { 109 controller.startMonitoringStateChange(); 110 } 111 } 112 // Force a refresh on listener 113 onConditionChanged(); 114 } 115 116 /** 117 * Stop monitoring state change for all conditions 118 */ stopMonitoringStateChange()119 public void stopMonitoringStateChange() { 120 if (!mIsListeningToStateChange) { 121 Log.d(TAG, "Not listening to condition state changes, skipping"); 122 return; 123 } 124 for (ConditionalCardController controller : mCardControllers) { 125 controller.stopMonitoringStateChange(); 126 } 127 mIsListeningToStateChange = false; 128 } 129 130 /** 131 * Called when some conditional card's state has changed 132 */ onConditionChanged()133 void onConditionChanged() { 134 if (mListener != null) { 135 mListener.onConditionsChanged(); 136 } 137 } 138 139 @NonNull getController(long id)140 private <T extends ConditionalCardController> T getController(long id) { 141 for (ConditionalCardController controller : mCardControllers) { 142 if (controller.getId() == id) { 143 return (T) controller; 144 } 145 } 146 throw new IllegalStateException("Cannot find controller for " + id); 147 } 148 initCandidates()149 private void initCandidates() { 150 // Initialize controllers first. 151 mCardControllers.add(new AirplaneModeConditionController(mAppContext, this /* manager */)); 152 mCardControllers.add( 153 new BackgroundDataConditionController(mAppContext, this /* manager */)); 154 mCardControllers.add(new BatterySaverConditionController(mAppContext, this /* manager */)); 155 mCardControllers.add(new CellularDataConditionController(mAppContext, this /* manager */)); 156 mCardControllers.add(new DndConditionCardController(mAppContext, this /* manager */)); 157 mCardControllers.add(new HotspotConditionController(mAppContext, this /* manager */)); 158 mCardControllers.add(new NightDisplayConditionController(mAppContext, this /* manager */)); 159 mCardControllers.add(new RingerVibrateConditionController(mAppContext, this /* manager */)); 160 mCardControllers.add(new RingerMutedConditionController(mAppContext, this /* manager */)); 161 mCardControllers.add(new WorkModeConditionController(mAppContext, this /* manager */)); 162 mCardControllers.add(new GrayscaleConditionController(mAppContext, this /* manager */)); 163 } 164 165 /** 166 * Returns card if controller says it's displayable. Otherwise returns null. 167 */ 168 public static class DisplayableChecker implements Callable<ContextualCard> { 169 170 private final ConditionalCardController mController; 171 DisplayableChecker(ConditionalCardController controller)172 private DisplayableChecker(ConditionalCardController controller) { 173 mController = controller; 174 } 175 176 @Override call()177 public ContextualCard call() throws Exception { 178 return mController.isDisplayable() ? mController.buildContextualCard() : null; 179 } 180 } 181 } 182