1 /* 2 * Copyright (C) 2020 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.keyguard; 18 19 import android.content.res.ColorStateList; 20 import android.content.res.Configuration; 21 import android.hardware.biometrics.BiometricSourceType; 22 import android.os.SystemClock; 23 import android.text.Editable; 24 import android.text.TextUtils; 25 import android.text.TextWatcher; 26 import android.util.Log; 27 import android.util.Pair; 28 import android.view.View; 29 30 import androidx.annotation.Nullable; 31 import androidx.annotation.VisibleForTesting; 32 33 import com.android.systemui.statusbar.policy.ConfigurationController; 34 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; 35 import com.android.systemui.util.ViewController; 36 37 import java.lang.ref.WeakReference; 38 39 import javax.inject.Inject; 40 41 /** 42 * Controller for a {@link KeyguardMessageAreaController}. 43 * @param <T> A subclass of KeyguardMessageArea. 44 */ 45 public class KeyguardMessageAreaController<T extends KeyguardMessageArea> 46 extends ViewController<T> { 47 /** 48 * Pair representing: 49 * first - BiometricSource the currently displayed message is associated with. 50 * second - Timestamp the biometric message came in uptimeMillis. 51 * This Pair can be null if the message is not associated with a biometric. 52 */ 53 @Nullable 54 private Pair<BiometricSourceType, Long> mMessageBiometricSource = null; 55 private static final Long SKIP_SHOWING_FACE_MESSAGE_AFTER_FP_MESSAGE_MS = 3500L; 56 57 /** 58 * Delay before speaking an accessibility announcement. Used to prevent 59 * lift-to-type from interrupting itself. 60 */ 61 private static final long ANNOUNCEMENT_DELAY = 250; 62 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 63 private final ConfigurationController mConfigurationController; 64 private final AnnounceRunnable mAnnounceRunnable; 65 private final TextWatcher mTextWatcher = new TextWatcher() { 66 @Override 67 public void afterTextChanged(Editable editable) { 68 CharSequence msg = editable; 69 if (!TextUtils.isEmpty(msg)) { 70 mView.removeCallbacks(mAnnounceRunnable); 71 mAnnounceRunnable.setTextToAnnounce(msg); 72 mView.postDelayed(() -> { 73 if (msg == mView.getText()) { 74 mAnnounceRunnable.run(); 75 } 76 }, ANNOUNCEMENT_DELAY); 77 } 78 } 79 80 @Override 81 public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 82 /* no-op */ 83 } 84 85 @Override 86 public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 87 /* no-op */ 88 } 89 }; 90 91 private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { 92 public void onFinishedGoingToSleep(int why) { 93 mView.setSelected(false); 94 } 95 96 public void onStartedWakingUp() { 97 mView.setSelected(true); 98 } 99 }; 100 101 private ConfigurationListener mConfigurationListener = new ConfigurationListener() { 102 @Override 103 public void onConfigChanged(Configuration newConfig) { 104 mView.onConfigChanged(); 105 } 106 107 @Override 108 public void onThemeChanged() { 109 mView.onThemeChanged(); 110 } 111 112 @Override 113 public void onDensityOrFontScaleChanged() { 114 mView.onDensityOrFontScaleChanged(); 115 } 116 }; 117 KeyguardMessageAreaController(T view, KeyguardUpdateMonitor keyguardUpdateMonitor, ConfigurationController configurationController)118 protected KeyguardMessageAreaController(T view, 119 KeyguardUpdateMonitor keyguardUpdateMonitor, 120 ConfigurationController configurationController) { 121 super(view); 122 123 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 124 mConfigurationController = configurationController; 125 mAnnounceRunnable = new AnnounceRunnable(mView); 126 } 127 128 @Override onViewAttached()129 protected void onViewAttached() { 130 mConfigurationController.addCallback(mConfigurationListener); 131 mKeyguardUpdateMonitor.registerCallback(mInfoCallback); 132 mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive()); 133 mView.onThemeChanged(); 134 mView.addTextChangedListener(mTextWatcher); 135 } 136 137 @Override onViewDetached()138 protected void onViewDetached() { 139 mConfigurationController.removeCallback(mConfigurationListener); 140 mKeyguardUpdateMonitor.removeCallback(mInfoCallback); 141 mView.removeTextChangedListener(mTextWatcher); 142 } 143 144 /** 145 * Indicate that view is visible and can display messages. 146 */ setIsVisible(boolean isVisible)147 public void setIsVisible(boolean isVisible) { 148 mView.setIsVisible(isVisible); 149 } 150 151 /** 152 * Mark this view with {@link View#GONE} visibility to remove this from the layout of the view. 153 * Any calls to {@link #setIsVisible(boolean)} after this will be a no-op. 154 */ disable()155 public void disable() { 156 mView.disable(); 157 } 158 setMessage(CharSequence s)159 public void setMessage(CharSequence s) { 160 setMessage(s, true); 161 } 162 163 /** 164 * Sets a message to the underlying text view. 165 */ setMessage(CharSequence s, boolean animate)166 public void setMessage(CharSequence s, boolean animate) { 167 setMessage(s, animate, null); 168 } 169 170 /** 171 * Sets a message to the underlying text view. 172 */ setMessage(CharSequence s, BiometricSourceType biometricSourceType)173 public void setMessage(CharSequence s, BiometricSourceType biometricSourceType) { 174 setMessage(s, true, biometricSourceType); 175 } 176 setMessage( CharSequence s, boolean animate, BiometricSourceType biometricSourceType)177 private void setMessage( 178 CharSequence s, 179 boolean animate, 180 BiometricSourceType biometricSourceType) { 181 final long uptimeMillis = SystemClock.uptimeMillis(); 182 if (skipShowingFaceMessage(biometricSourceType, uptimeMillis)) { 183 Log.d("KeyguardMessageAreaController", "Skip showing face message \"" + s + "\""); 184 return; 185 } 186 mMessageBiometricSource = new Pair<>(biometricSourceType, uptimeMillis); 187 if (mView.isDisabled()) { 188 return; 189 } 190 mView.setMessage(s, animate); 191 } 192 skipShowingFaceMessage( BiometricSourceType biometricSourceType, Long currentUptimeMillis )193 private boolean skipShowingFaceMessage( 194 BiometricSourceType biometricSourceType, Long currentUptimeMillis 195 ) { 196 return mMessageBiometricSource != null 197 && biometricSourceType == BiometricSourceType.FACE 198 && mMessageBiometricSource.first == BiometricSourceType.FINGERPRINT 199 && (currentUptimeMillis - mMessageBiometricSource.second) 200 < SKIP_SHOWING_FACE_MESSAGE_AFTER_FP_MESSAGE_MS; 201 } 202 setMessage(int resId)203 public void setMessage(int resId) { 204 String message = resId != 0 ? mView.getResources().getString(resId) : null; 205 setMessage(message); 206 } 207 setNextMessageColor(ColorStateList colorState)208 public void setNextMessageColor(ColorStateList colorState) { 209 mView.setNextMessageColor(colorState); 210 } 211 212 /** Returns the message of the underlying TextView. */ getMessage()213 public CharSequence getMessage() { 214 return mView.getText(); 215 } 216 217 /** Factory for creating {@link com.android.keyguard.KeyguardMessageAreaController}. */ 218 public static class Factory { 219 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 220 private final ConfigurationController mConfigurationController; 221 222 @Inject Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, ConfigurationController configurationController)223 public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, 224 ConfigurationController configurationController) { 225 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 226 mConfigurationController = configurationController; 227 } 228 229 /** Build a new {@link KeyguardMessageAreaController}. */ create(KeyguardMessageArea view)230 public KeyguardMessageAreaController create(KeyguardMessageArea view) { 231 return new KeyguardMessageAreaController( 232 view, mKeyguardUpdateMonitor, mConfigurationController); 233 } 234 } 235 236 /** 237 * Runnable used to delay accessibility announcements. 238 */ 239 @VisibleForTesting 240 public static class AnnounceRunnable implements Runnable { 241 private final WeakReference<View> mHost; 242 private CharSequence mTextToAnnounce; 243 AnnounceRunnable(View host)244 AnnounceRunnable(View host) { 245 mHost = new WeakReference<>(host); 246 } 247 248 /** Sets the text to announce. */ setTextToAnnounce(CharSequence textToAnnounce)249 public void setTextToAnnounce(CharSequence textToAnnounce) { 250 mTextToAnnounce = textToAnnounce; 251 } 252 253 @Override run()254 public void run() { 255 final View host = mHost.get(); 256 if (host != null && host.isVisibleToUser()) { 257 host.announceForAccessibility(mTextToAnnounce); 258 } 259 } 260 } 261 } 262