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