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