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 
43     private static final int SECURITY_MESSAGE_DURATION = 5000;
44 
45     private final KeyguardUpdateMonitor mUpdateMonitor;
46     private final Handler mHandler;
47 
48     // Timeout before we reset the message to show charging/owner info
49     long mTimeout = SECURITY_MESSAGE_DURATION;
50     CharSequence mMessage;
51 
52     private final Runnable mClearMessageRunnable = new Runnable() {
53         @Override
54         public void run() {
55             mMessage = null;
56             update();
57         }
58     };
59 
60     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
61         public void onFinishedGoingToSleep(int why) {
62             setSelected(false);
63         };
64         public void onStartedWakingUp() {
65             setSelected(true);
66         };
67     };
68 
KeyguardMessageArea(Context context)69     public KeyguardMessageArea(Context context) {
70         this(context, null);
71     }
72 
KeyguardMessageArea(Context context, AttributeSet attrs)73     public KeyguardMessageArea(Context context, AttributeSet attrs) {
74         super(context, attrs);
75         setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
76 
77         mUpdateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
78         mUpdateMonitor.registerCallback(mInfoCallback);
79         mHandler = new Handler(Looper.myLooper());
80 
81         update();
82     }
83 
84     @Override
setMessage(CharSequence msg, boolean important)85     public void setMessage(CharSequence msg, boolean important) {
86         if (!TextUtils.isEmpty(msg) && important) {
87             securityMessageChanged(msg);
88         } else {
89             clearMessage();
90         }
91     }
92 
93     @Override
setMessage(int resId, boolean important)94     public void setMessage(int resId, boolean important) {
95         if (resId != 0 && important) {
96             CharSequence message = getContext().getResources().getText(resId);
97             securityMessageChanged(message);
98         } else {
99             clearMessage();
100         }
101     }
102 
103     @Override
setMessage(int resId, boolean important, Object... formatArgs)104     public void setMessage(int resId, boolean important, Object... formatArgs) {
105         if (resId != 0 && important) {
106             String message = getContext().getString(resId, formatArgs);
107             securityMessageChanged(message);
108         } else {
109             clearMessage();
110         }
111     }
112 
113     @Override
setTimeout(int timeoutMs)114     public void setTimeout(int timeoutMs) {
115         mTimeout = timeoutMs;
116     }
117 
findSecurityMessageDisplay(View v)118     public static SecurityMessageDisplay findSecurityMessageDisplay(View v) {
119         KeyguardMessageArea messageArea = (KeyguardMessageArea) v.findViewById(
120                 R.id.keyguard_message_area);
121         if (messageArea == null) {
122             throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass());
123         }
124         return messageArea;
125     }
126 
127     @Override
onFinishInflate()128     protected void onFinishInflate() {
129         boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
130         setSelected(shouldMarquee); // This is required to ensure marquee works
131     }
132 
securityMessageChanged(CharSequence message)133     private void securityMessageChanged(CharSequence message) {
134         mMessage = message;
135         update();
136         mHandler.removeCallbacks(mClearMessageRunnable);
137         if (mTimeout > 0) {
138             mHandler.postDelayed(mClearMessageRunnable, mTimeout);
139         }
140         mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
141         mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
142                 (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
143     }
144 
clearMessage()145     private void clearMessage() {
146         mHandler.removeCallbacks(mClearMessageRunnable);
147         mHandler.post(mClearMessageRunnable);
148     }
149 
update()150     private void update() {
151         CharSequence status = mMessage;
152         setVisibility(TextUtils.isEmpty(status) ? INVISIBLE : VISIBLE);
153         setText(status);
154     }
155 
156 
157     /**
158      * Runnable used to delay accessibility announcements.
159      */
160     private static class AnnounceRunnable implements Runnable {
161         private final WeakReference<View> mHost;
162         private final CharSequence mTextToAnnounce;
163 
AnnounceRunnable(View host, CharSequence textToAnnounce)164         AnnounceRunnable(View host, CharSequence textToAnnounce) {
165             mHost = new WeakReference<View>(host);
166             mTextToAnnounce = textToAnnounce;
167         }
168 
169         @Override
run()170         public void run() {
171             final View host = mHost.get();
172             if (host != null) {
173                 host.announceForAccessibility(mTextToAnnounce);
174             }
175         }
176     }
177 }
178