1 /*
2  * Copyright (C) 2011 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 static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
20 
21 import android.content.Context;
22 import android.content.res.ColorStateList;
23 import android.content.res.TypedArray;
24 import android.graphics.Color;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.SystemClock;
28 import android.text.TextUtils;
29 import android.util.AttributeSet;
30 import android.util.TypedValue;
31 import android.view.View;
32 import android.widget.TextView;
33 
34 import com.android.systemui.R;
35 import com.android.systemui.statusbar.policy.ConfigurationController;
36 
37 import java.lang.ref.WeakReference;
38 
39 import javax.inject.Inject;
40 import javax.inject.Named;
41 
42 /***
43  * Manages a number of views inside of the given layout. See below for a list of widgets.
44  */
45 public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay,
46         ConfigurationController.ConfigurationListener {
47     /** Handler token posted with accessibility announcement runnables. */
48     private static final Object ANNOUNCE_TOKEN = new Object();
49 
50     /**
51      * Delay before speaking an accessibility announcement. Used to prevent
52      * lift-to-type from interrupting itself.
53      */
54     private static final long ANNOUNCEMENT_DELAY = 250;
55     private static final int DEFAULT_COLOR = -1;
56 
57     private final Handler mHandler;
58     private final ConfigurationController mConfigurationController;
59 
60     private ColorStateList mDefaultColorState;
61     private CharSequence mMessage;
62     private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
63     private boolean mBouncerVisible;
64 
65     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
66         public void onFinishedGoingToSleep(int why) {
67             setSelected(false);
68         }
69 
70         public void onStartedWakingUp() {
71             setSelected(true);
72         }
73 
74         @Override
75         public void onKeyguardBouncerChanged(boolean bouncer) {
76             mBouncerVisible = bouncer;
77             update();
78         }
79     };
80 
KeyguardMessageArea(Context context)81     public KeyguardMessageArea(Context context) {
82         super(context, null);
83         throw new IllegalStateException("This constructor should never be invoked");
84     }
85 
86     @Inject
KeyguardMessageArea(@amedVIEW_CONTEXT) Context context, AttributeSet attrs, ConfigurationController configurationController)87     public KeyguardMessageArea(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
88             ConfigurationController configurationController) {
89         this(context, attrs, KeyguardUpdateMonitor.getInstance(context), configurationController);
90     }
91 
KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor, ConfigurationController configurationController)92     public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor,
93             ConfigurationController configurationController) {
94         super(context, attrs);
95         setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
96 
97         monitor.registerCallback(mInfoCallback);
98         mHandler = new Handler(Looper.myLooper());
99         mConfigurationController = configurationController;
100         onThemeChanged();
101     }
102 
103     @Override
onAttachedToWindow()104     protected void onAttachedToWindow() {
105         super.onAttachedToWindow();
106         mConfigurationController.addCallback(this);
107         onThemeChanged();
108     }
109 
110     @Override
onDetachedFromWindow()111     protected void onDetachedFromWindow() {
112         super.onDetachedFromWindow();
113         mConfigurationController.removeCallback(this);
114     }
115 
116     @Override
setNextMessageColor(ColorStateList colorState)117     public void setNextMessageColor(ColorStateList colorState) {
118         mNextMessageColorState = colorState;
119     }
120 
121     @Override
onThemeChanged()122     public void onThemeChanged() {
123         TypedArray array = mContext.obtainStyledAttributes(new int[] {
124                 R.attr.wallpaperTextColor
125         });
126         ColorStateList newTextColors = ColorStateList.valueOf(array.getColor(0, Color.RED));
127         array.recycle();
128         mDefaultColorState = newTextColors;
129         update();
130     }
131 
132     @Override
onDensityOrFontScaleChanged()133     public void onDensityOrFontScaleChanged() {
134         TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] {
135                 android.R.attr.textSize
136         });
137         setTextSize(TypedValue.COMPLEX_UNIT_PX, array.getDimensionPixelSize(0, 0));
138         array.recycle();
139     }
140 
141     @Override
setMessage(CharSequence msg)142     public void setMessage(CharSequence msg) {
143         if (!TextUtils.isEmpty(msg)) {
144             securityMessageChanged(msg);
145         } else {
146             clearMessage();
147         }
148     }
149 
150     @Override
setMessage(int resId)151     public void setMessage(int resId) {
152         CharSequence message = null;
153         if (resId != 0) {
154             message = getContext().getResources().getText(resId);
155         }
156         setMessage(message);
157     }
158 
159     @Override
formatMessage(int resId, Object... formatArgs)160     public void formatMessage(int resId, Object... formatArgs) {
161         CharSequence message = null;
162         if (resId != 0) {
163             message = getContext().getString(resId, formatArgs);
164         }
165         setMessage(message);
166     }
167 
findSecurityMessageDisplay(View v)168     public static KeyguardMessageArea findSecurityMessageDisplay(View v) {
169         KeyguardMessageArea messageArea = v.findViewById(R.id.keyguard_message_area);
170         if (messageArea == null) {
171             messageArea = v.getRootView().findViewById(R.id.keyguard_message_area);
172         }
173         if (messageArea == null) {
174             throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass());
175         }
176         return messageArea;
177     }
178 
179     @Override
onFinishInflate()180     protected void onFinishInflate() {
181         boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
182         setSelected(shouldMarquee); // This is required to ensure marquee works
183     }
184 
securityMessageChanged(CharSequence message)185     private void securityMessageChanged(CharSequence message) {
186         mMessage = message;
187         update();
188         mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
189         mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
190                 (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
191     }
192 
clearMessage()193     private void clearMessage() {
194         mMessage = null;
195         update();
196     }
197 
update()198     private void update() {
199         CharSequence status = mMessage;
200         setVisibility(TextUtils.isEmpty(status) || !mBouncerVisible ? INVISIBLE : VISIBLE);
201         setText(status);
202         ColorStateList colorState = mDefaultColorState;
203         if (mNextMessageColorState.getDefaultColor() != DEFAULT_COLOR) {
204             colorState = mNextMessageColorState;
205             mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
206         }
207         setTextColor(colorState);
208     }
209 
210 
211     /**
212      * Runnable used to delay accessibility announcements.
213      */
214     private static class AnnounceRunnable implements Runnable {
215         private final WeakReference<View> mHost;
216         private final CharSequence mTextToAnnounce;
217 
AnnounceRunnable(View host, CharSequence textToAnnounce)218         AnnounceRunnable(View host, CharSequence textToAnnounce) {
219             mHost = new WeakReference<View>(host);
220             mTextToAnnounce = textToAnnounce;
221         }
222 
223         @Override
run()224         public void run() {
225             final View host = mHost.get();
226             if (host != null) {
227                 host.announceForAccessibility(mTextToAnnounce);
228             }
229         }
230     }
231 }
232