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