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 android.content.Context;
20 import android.os.Handler;
21 import android.os.Looper;
22 import android.os.SystemClock;
23 import android.text.TextUtils;
24 import android.util.AttributeSet;
25 import android.view.View;
26 import android.widget.TextView;
27 
28 import java.lang.ref.WeakReference;
29 
30 /***
31  * Manages a number of views inside of the given layout. See below for a list of widgets.
32  */
33 class KeyguardMessageArea extends TextView implements SecurityMessageDisplay {
34     /** Handler token posted with accessibility announcement runnables. */
35     private static final Object ANNOUNCE_TOKEN = new Object();
36 
37     /**
38      * Delay before speaking an accessibility announcement. Used to prevent
39      * lift-to-type from interrupting itself.
40      */
41     private static final long ANNOUNCEMENT_DELAY = 250;
42     private static final int DEFAULT_COLOR = -1;
43 
44     private static final int SECURITY_MESSAGE_DURATION = 5000;
45 
46     private final KeyguardUpdateMonitor mUpdateMonitor;
47     private final Handler mHandler;
48     private final int mDefaultColor;
49 
50     // Timeout before we reset the message to show charging/owner info
51     long mTimeout = SECURITY_MESSAGE_DURATION;
52     CharSequence mMessage;
53     private int mNextMessageColor = DEFAULT_COLOR;
54 
55     private final Runnable mClearMessageRunnable = new Runnable() {
56         @Override
57         public void run() {
58             mMessage = null;
59             update();
60         }
61     };
62 
63     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
64         public void onFinishedGoingToSleep(int why) {
65             setSelected(false);
66         };
67         public void onStartedWakingUp() {
68             setSelected(true);
69         };
70     };
71 
KeyguardMessageArea(Context context)72     public KeyguardMessageArea(Context context) {
73         this(context, null);
74     }
75 
KeyguardMessageArea(Context context, AttributeSet attrs)76     public KeyguardMessageArea(Context context, AttributeSet attrs) {
77         super(context, attrs);
78         setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
79 
80         mUpdateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
81         mUpdateMonitor.registerCallback(mInfoCallback);
82         mHandler = new Handler(Looper.myLooper());
83 
84         mDefaultColor = getCurrentTextColor();
85         update();
86     }
87 
88     @Override
setNextMessageColor(int color)89     public void setNextMessageColor(int color) {
90         mNextMessageColor = color;
91     }
92 
93     @Override
setMessage(CharSequence msg, boolean important)94     public void setMessage(CharSequence msg, boolean important) {
95         if (!TextUtils.isEmpty(msg) && important) {
96             securityMessageChanged(msg);
97         } else {
98             clearMessage();
99         }
100     }
101 
102     @Override
setMessage(int resId, boolean important)103     public void setMessage(int resId, boolean important) {
104         if (resId != 0 && important) {
105             CharSequence message = getContext().getResources().getText(resId);
106             securityMessageChanged(message);
107         } else {
108             clearMessage();
109         }
110     }
111 
112     @Override
setMessage(int resId, boolean important, Object... formatArgs)113     public void setMessage(int resId, boolean important, Object... formatArgs) {
114         if (resId != 0 && important) {
115             String message = getContext().getString(resId, formatArgs);
116             securityMessageChanged(message);
117         } else {
118             clearMessage();
119         }
120     }
121 
122     @Override
setTimeout(int timeoutMs)123     public void setTimeout(int timeoutMs) {
124         mTimeout = timeoutMs;
125     }
126 
findSecurityMessageDisplay(View v)127     public static SecurityMessageDisplay findSecurityMessageDisplay(View v) {
128         KeyguardMessageArea messageArea = (KeyguardMessageArea) v.findViewById(
129                 R.id.keyguard_message_area);
130         if (messageArea == null) {
131             throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass());
132         }
133         return messageArea;
134     }
135 
136     @Override
onFinishInflate()137     protected void onFinishInflate() {
138         boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
139         setSelected(shouldMarquee); // This is required to ensure marquee works
140     }
141 
securityMessageChanged(CharSequence message)142     private void securityMessageChanged(CharSequence message) {
143         mMessage = message;
144         update();
145         mHandler.removeCallbacks(mClearMessageRunnable);
146         if (mTimeout > 0) {
147             mHandler.postDelayed(mClearMessageRunnable, mTimeout);
148         }
149         mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
150         mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
151                 (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
152     }
153 
clearMessage()154     private void clearMessage() {
155         mHandler.removeCallbacks(mClearMessageRunnable);
156         mHandler.post(mClearMessageRunnable);
157     }
158 
update()159     private void update() {
160         CharSequence status = mMessage;
161         setVisibility(TextUtils.isEmpty(status) ? INVISIBLE : VISIBLE);
162         setText(status);
163         int color = mDefaultColor;
164         if (mNextMessageColor != DEFAULT_COLOR) {
165             color = mNextMessageColor;
166             mNextMessageColor = DEFAULT_COLOR;
167         }
168         setTextColor(color);
169     }
170 
171 
172     /**
173      * Runnable used to delay accessibility announcements.
174      */
175     private static class AnnounceRunnable implements Runnable {
176         private final WeakReference<View> mHost;
177         private final CharSequence mTextToAnnounce;
178 
AnnounceRunnable(View host, CharSequence textToAnnounce)179         AnnounceRunnable(View host, CharSequence textToAnnounce) {
180             mHost = new WeakReference<View>(host);
181             mTextToAnnounce = textToAnnounce;
182         }
183 
184         @Override
run()185         public void run() {
186             final View host = mHost.get();
187             if (host != null) {
188                 host.announceForAccessibility(mTextToAnnounce);
189             }
190         }
191     }
192 }
193