1 /* 2 * Copyright (C) 2021 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.systemui.keyguard; 18 19 import android.annotation.Nullable; 20 import android.content.res.ColorStateList; 21 import android.graphics.Color; 22 import android.text.TextUtils; 23 24 import androidx.annotation.IntDef; 25 26 import com.android.systemui.Dumpable; 27 import com.android.systemui.dagger.qualifiers.Main; 28 import com.android.systemui.plugins.statusbar.StatusBarStateController; 29 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; 30 import com.android.systemui.util.ViewController; 31 import com.android.systemui.util.concurrency.DelayableExecutor; 32 33 import java.io.FileDescriptor; 34 import java.io.PrintWriter; 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.util.HashMap; 38 import java.util.LinkedList; 39 import java.util.List; 40 import java.util.Map; 41 42 /** 43 * Rotates through messages to show on the keyguard bottom area on the lock screen 44 * NOTE: This controller should not be used on AoD to avoid waking up the AP too often. 45 */ 46 public class KeyguardIndicationRotateTextViewController extends 47 ViewController<KeyguardIndicationTextView> implements Dumpable { 48 public static String TAG = "KgIndicationRotatingCtrl"; 49 private static final long DEFAULT_INDICATION_SHOW_LENGTH = 3500; // milliseconds 50 51 private final StatusBarStateController mStatusBarStateController; 52 private final float mMaxAlpha; 53 private final ColorStateList mInitialTextColorState; 54 55 // Stores @IndicationType => KeyguardIndication messages 56 private final Map<Integer, KeyguardIndication> mIndicationMessages = new HashMap<>(); 57 58 // Executor that will show the next message after a delay 59 private final DelayableExecutor mExecutor; 60 @Nullable private ShowNextIndication mShowNextIndicationRunnable; 61 62 // List of indication types to show. The next indication to show is always at index 0 63 private final List<Integer> mIndicationQueue = new LinkedList<>(); 64 private @IndicationType int mCurrIndicationType = INDICATION_TYPE_NONE; 65 66 private boolean mIsDozing; 67 KeyguardIndicationRotateTextViewController( KeyguardIndicationTextView view, @Main DelayableExecutor executor, StatusBarStateController statusBarStateController )68 public KeyguardIndicationRotateTextViewController( 69 KeyguardIndicationTextView view, 70 @Main DelayableExecutor executor, 71 StatusBarStateController statusBarStateController 72 ) { 73 super(view); 74 mMaxAlpha = view.getAlpha(); 75 mExecutor = executor; 76 mInitialTextColorState = mView != null 77 ? mView.getTextColors() : ColorStateList.valueOf(Color.WHITE); 78 mStatusBarStateController = statusBarStateController; 79 init(); 80 } 81 82 @Override onViewAttached()83 protected void onViewAttached() { 84 mStatusBarStateController.addCallback(mStatusBarStateListener); 85 } 86 87 @Override onViewDetached()88 protected void onViewDetached() { 89 mStatusBarStateController.removeCallback(mStatusBarStateListener); 90 cancelScheduledIndication(); 91 } 92 93 /** 94 * Update the indication type with the given String. 95 * @param type of indication 96 * @param newIndication message to associate with this indication type 97 * @param showImmediately if true: shows this indication message immediately. Else, the text 98 * associated with this type is updated and will show when its turn in 99 * the IndicationQueue comes around. 100 */ updateIndication(@ndicationType int type, KeyguardIndication newIndication, boolean updateImmediately)101 public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, 102 boolean updateImmediately) { 103 if (type == INDICATION_TYPE_NOW_PLAYING 104 || type == INDICATION_TYPE_REVERSE_CHARGING) { 105 // temporarily don't show here, instead use AmbientContainer b/181049781 106 return; 107 } 108 final boolean hasPreviousIndication = mIndicationMessages.get(type) != null; 109 final boolean hasNewIndication = newIndication != null; 110 if (!hasNewIndication) { 111 mIndicationMessages.remove(type); 112 mIndicationQueue.removeIf(x -> x == type); 113 } else { 114 if (!hasPreviousIndication) { 115 mIndicationQueue.add(type); 116 } 117 118 mIndicationMessages.put(type, newIndication); 119 } 120 121 if (mIsDozing) { 122 return; 123 } 124 125 final boolean showNow = updateImmediately 126 || mCurrIndicationType == INDICATION_TYPE_NONE 127 || mCurrIndicationType == type; 128 if (hasNewIndication) { 129 if (showNow) { 130 showIndication(type); 131 } else if (!isNextIndicationScheduled()) { 132 scheduleShowNextIndication(); 133 } 134 return; 135 } 136 137 if (mCurrIndicationType == type 138 && !hasNewIndication 139 && updateImmediately) { 140 if (mShowNextIndicationRunnable != null) { 141 mShowNextIndicationRunnable.runImmediately(); 142 } else { 143 showIndication(INDICATION_TYPE_NONE); 144 } 145 } 146 } 147 148 /** 149 * Stop showing the following indication type. 150 * 151 * If the current indication is of this type, immediately stops showing the message. 152 */ hideIndication(@ndicationType int type)153 public void hideIndication(@IndicationType int type) { 154 if (!mIndicationMessages.containsKey(type) 155 || TextUtils.isEmpty(mIndicationMessages.get(type).getMessage())) { 156 return; 157 } 158 updateIndication(type, null, true); 159 } 160 161 /** 162 * Show a transient message. 163 * Transient messages: 164 * - show immediately 165 * - will continue to be in the rotation of messages shown until hideTransient is called. 166 */ showTransient(CharSequence newIndication)167 public void showTransient(CharSequence newIndication) { 168 final long inAnimationDuration = 600L; // see KeyguardIndicationTextView.getYInDuration 169 updateIndication(INDICATION_TYPE_TRANSIENT, 170 new KeyguardIndication.Builder() 171 .setMessage(newIndication) 172 .setMinVisibilityMillis(2000L + inAnimationDuration) 173 .setTextColor(mInitialTextColorState) 174 .build(), 175 /* showImmediately */true); 176 } 177 178 /** 179 * Hide a transient message immediately. 180 */ hideTransient()181 public void hideTransient() { 182 hideIndication(INDICATION_TYPE_TRANSIENT); 183 } 184 185 /** 186 * @return true if there are available indications to show 187 */ hasIndications()188 public boolean hasIndications() { 189 return mIndicationMessages.keySet().size() > 0; 190 } 191 192 /** 193 * Immediately show the passed indication type and schedule the next indication to show. 194 * Will re-add this indication to be re-shown after all other indications have been 195 * rotated through. 196 */ showIndication(@ndicationType int type)197 private void showIndication(@IndicationType int type) { 198 cancelScheduledIndication(); 199 200 mCurrIndicationType = type; 201 mIndicationQueue.removeIf(x -> x == type); 202 if (mCurrIndicationType != INDICATION_TYPE_NONE) { 203 mIndicationQueue.add(type); // re-add to show later 204 } 205 206 mView.switchIndication(mIndicationMessages.get(type)); 207 208 // only schedule next indication if there's more than just this indication in the queue 209 if (mCurrIndicationType != INDICATION_TYPE_NONE && mIndicationQueue.size() > 1) { 210 scheduleShowNextIndication(); 211 } 212 } 213 isNextIndicationScheduled()214 protected boolean isNextIndicationScheduled() { 215 return mShowNextIndicationRunnable != null; 216 } 217 scheduleShowNextIndication()218 private void scheduleShowNextIndication() { 219 cancelScheduledIndication(); 220 mShowNextIndicationRunnable = new ShowNextIndication(DEFAULT_INDICATION_SHOW_LENGTH); 221 } 222 cancelScheduledIndication()223 private void cancelScheduledIndication() { 224 if (mShowNextIndicationRunnable != null) { 225 mShowNextIndicationRunnable.cancelDelayedExecution(); 226 mShowNextIndicationRunnable = null; 227 } 228 } 229 230 private StatusBarStateController.StateListener mStatusBarStateListener = 231 new StatusBarStateController.StateListener() { 232 @Override 233 public void onDozeAmountChanged(float linear, float eased) { 234 mView.setAlpha((1 - linear) * mMaxAlpha); 235 } 236 237 @Override 238 public void onDozingChanged(boolean isDozing) { 239 if (isDozing == mIsDozing) return; 240 mIsDozing = isDozing; 241 if (mIsDozing) { 242 showIndication(INDICATION_TYPE_NONE); 243 } else if (mIndicationQueue.size() > 0) { 244 showIndication(mIndicationQueue.remove(0)); 245 } 246 } 247 }; 248 249 /** 250 * Shows the next indication in the IndicationQueue after an optional delay. 251 * This wrapper has the ability to cancel itself (remove runnable from DelayableExecutor) or 252 * immediately run itself (which also removes itself from the DelayableExecutor). 253 */ 254 class ShowNextIndication { 255 private final Runnable mShowIndicationRunnable; 256 private Runnable mCancelDelayedRunnable; 257 ShowNextIndication(long delay)258 ShowNextIndication(long delay) { 259 mShowIndicationRunnable = () -> { 260 int type = mIndicationQueue.size() == 0 261 ? INDICATION_TYPE_NONE : mIndicationQueue.remove(0); 262 showIndication(type); 263 }; 264 mCancelDelayedRunnable = mExecutor.executeDelayed(mShowIndicationRunnable, delay); 265 } 266 runImmediately()267 public void runImmediately() { 268 cancelDelayedExecution(); 269 mShowIndicationRunnable.run(); 270 } 271 cancelDelayedExecution()272 public void cancelDelayedExecution() { 273 if (mCancelDelayedRunnable != null) { 274 mCancelDelayedRunnable.run(); 275 mCancelDelayedRunnable = null; 276 } 277 } 278 } 279 280 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)281 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 282 pw.println("KeyguardIndicationRotatingTextViewController:"); 283 pw.println(" currentMessage=" + mView.getText()); 284 pw.println(" dozing:" + mIsDozing); 285 pw.println(" queue:" + mIndicationQueue.toString()); 286 pw.println(" showNextIndicationRunnable:" + mShowNextIndicationRunnable); 287 288 if (hasIndications()) { 289 pw.println(" All messages:"); 290 for (int type : mIndicationMessages.keySet()) { 291 pw.println(" type=" + type + " " + mIndicationMessages.get(type)); 292 } 293 } 294 } 295 296 static final int INDICATION_TYPE_NONE = -1; 297 public static final int INDICATION_TYPE_OWNER_INFO = 0; 298 public static final int INDICATION_TYPE_DISCLOSURE = 1; 299 public static final int INDICATION_TYPE_LOGOUT = 2; 300 public static final int INDICATION_TYPE_BATTERY = 3; 301 public static final int INDICATION_TYPE_ALIGNMENT = 4; 302 public static final int INDICATION_TYPE_TRANSIENT = 5; 303 public static final int INDICATION_TYPE_TRUST = 6; 304 public static final int INDICATION_TYPE_RESTING = 7; 305 public static final int INDICATION_TYPE_USER_LOCKED = 8; 306 public static final int INDICATION_TYPE_NOW_PLAYING = 9; 307 public static final int INDICATION_TYPE_REVERSE_CHARGING = 10; 308 309 @IntDef({ 310 INDICATION_TYPE_NONE, 311 INDICATION_TYPE_DISCLOSURE, 312 INDICATION_TYPE_OWNER_INFO, 313 INDICATION_TYPE_LOGOUT, 314 INDICATION_TYPE_BATTERY, 315 INDICATION_TYPE_ALIGNMENT, 316 INDICATION_TYPE_TRANSIENT, 317 INDICATION_TYPE_TRUST, 318 INDICATION_TYPE_RESTING, 319 INDICATION_TYPE_USER_LOCKED, 320 INDICATION_TYPE_NOW_PLAYING, 321 INDICATION_TYPE_REVERSE_CHARGING, 322 }) 323 @Retention(RetentionPolicy.SOURCE) 324 public @interface IndicationType{} 325 } 326