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.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.os.BatteryManager;
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.MutableInt;
31 import android.view.View;
32 import android.widget.TextView;
33 
34 import java.lang.ref.WeakReference;
35 
36 import com.android.internal.widget.LockPatternUtils;
37 
38 /***
39  * Manages a number of views inside of the given layout. See below for a list of widgets.
40  */
41 class KeyguardMessageArea extends TextView {
42     /** Handler token posted with accessibility announcement runnables. */
43     private static final Object ANNOUNCE_TOKEN = new Object();
44 
45     /**
46      * Delay before speaking an accessibility announcement. Used to prevent
47      * lift-to-type from interrupting itself.
48      */
49     private static final long ANNOUNCEMENT_DELAY = 250;
50 
51     static final int SECURITY_MESSAGE_DURATION = 5000;
52     protected static final int FADE_DURATION = 750;
53 
54     private static final String TAG = "KeyguardMessageArea";
55 
56     // is the bouncer up?
57     boolean mShowingBouncer = false;
58 
59     KeyguardUpdateMonitor mUpdateMonitor;
60 
61     // Timeout before we reset the message to show charging/owner info
62     long mTimeout = SECURITY_MESSAGE_DURATION;
63 
64     private Handler mHandler;
65 
66     CharSequence mMessage;
67     boolean mShowingMessage;
68     private CharSequence mSeparator;
69     private LockPatternUtils mLockPatternUtils;
70 
71     Runnable mClearMessageRunnable = new Runnable() {
72         @Override
73         public void run() {
74             mMessage = null;
75             mShowingMessage = false;
76             if (mShowingBouncer) {
77                 hideMessage(FADE_DURATION, true);
78             } else {
79                 update();
80             }
81         }
82     };
83 
84     public static class Helper implements SecurityMessageDisplay {
85         KeyguardMessageArea mMessageArea;
Helper(View v)86         Helper(View v) {
87             mMessageArea = (KeyguardMessageArea) v.findViewById(R.id.keyguard_message_area);
88             if (mMessageArea == null) {
89                 throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass());
90             }
91         }
92 
setMessage(CharSequence msg, boolean important)93         public void setMessage(CharSequence msg, boolean important) {
94             if (!TextUtils.isEmpty(msg) && important) {
95                 mMessageArea.mMessage = msg;
96                 mMessageArea.securityMessageChanged();
97             }
98         }
99 
setMessage(int resId, boolean important)100         public void setMessage(int resId, boolean important) {
101             if (resId != 0 && important) {
102                 mMessageArea.mMessage = mMessageArea.getContext().getResources().getText(resId);
103                 mMessageArea.securityMessageChanged();
104             }
105         }
106 
setMessage(int resId, boolean important, Object... formatArgs)107         public void setMessage(int resId, boolean important, Object... formatArgs) {
108             if (resId != 0 && important) {
109                 mMessageArea.mMessage = mMessageArea.getContext().getString(resId, formatArgs);
110                 mMessageArea.securityMessageChanged();
111             }
112         }
113 
114         @Override
showBouncer(int duration)115         public void showBouncer(int duration) {
116             mMessageArea.hideMessage(duration, false);
117             mMessageArea.mShowingBouncer = true;
118         }
119 
120         @Override
hideBouncer(int duration)121         public void hideBouncer(int duration) {
122             mMessageArea.showMessage(duration);
123             mMessageArea.mShowingBouncer = false;
124         }
125 
126         @Override
setTimeout(int timeoutMs)127         public void setTimeout(int timeoutMs) {
128             mMessageArea.mTimeout = timeoutMs;
129         }
130     }
131 
132     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
133         public void onScreenTurnedOff(int why) {
134             setSelected(false);
135         };
136         public void onScreenTurnedOn() {
137             setSelected(true);
138         };
139     };
140 
KeyguardMessageArea(Context context)141     public KeyguardMessageArea(Context context) {
142         this(context, null);
143     }
144 
KeyguardMessageArea(Context context, AttributeSet attrs)145     public KeyguardMessageArea(Context context, AttributeSet attrs) {
146         super(context, attrs);
147         setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
148 
149         mLockPatternUtils = new LockPatternUtils(context);
150 
151         // Registering this callback immediately updates the battery state, among other things.
152         mUpdateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
153         mUpdateMonitor.registerCallback(mInfoCallback);
154         mHandler = new Handler(Looper.myLooper());
155 
156         mSeparator = getResources().getString(
157                 com.android.internal.R.string.kg_text_message_separator);
158 
159         update();
160     }
161 
162     @Override
onFinishInflate()163     protected void onFinishInflate() {
164         final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
165         setSelected(screenOn); // This is required to ensure marquee works
166     }
167 
securityMessageChanged()168     public void securityMessageChanged() {
169         setAlpha(1f);
170         mShowingMessage = true;
171         update();
172         mHandler.removeCallbacks(mClearMessageRunnable);
173         if (mTimeout > 0) {
174             mHandler.postDelayed(mClearMessageRunnable, mTimeout);
175         }
176         mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
177         mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
178                 (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
179     }
180 
181     /**
182      * Update the status lines based on these rules:
183      * AlarmStatus: Alarm state always gets it's own line.
184      * Status1 is shared between help, battery status and generic unlock instructions,
185      * prioritized in that order.
186      * @param showStatusLines status lines are shown if true
187      */
update()188     void update() {
189         MutableInt icon = new MutableInt(0);
190         CharSequence status = getCurrentMessage();
191         setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0);
192         setText(status);
193     }
194 
195 
getCurrentMessage()196     CharSequence getCurrentMessage() {
197         return mShowingMessage ? mMessage : null;
198     }
199 
hideMessage(int duration, boolean thenUpdate)200     private void hideMessage(int duration, boolean thenUpdate) {
201         if (duration > 0) {
202             Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f);
203             anim.setDuration(duration);
204             if (thenUpdate) {
205                 anim.addListener(new AnimatorListenerAdapter() {
206                         @Override
207                             public void onAnimationEnd(Animator animation) {
208                             update();
209                         }
210                 });
211             }
212             anim.start();
213         } else {
214             setAlpha(0f);
215             if (thenUpdate) {
216                 update();
217             }
218         }
219     }
220 
showMessage(int duration)221     private void showMessage(int duration) {
222         if (duration > 0) {
223             Animator anim = ObjectAnimator.ofFloat(this, "alpha", 1f);
224             anim.setDuration(duration);
225             anim.start();
226         } else {
227             setAlpha(1f);
228         }
229     }
230 
231     /**
232      * Runnable used to delay accessibility announcements.
233      */
234     private static class AnnounceRunnable implements Runnable {
235         private final WeakReference<View> mHost;
236         private final CharSequence mTextToAnnounce;
237 
AnnounceRunnable(View host, CharSequence textToAnnounce)238         public AnnounceRunnable(View host, CharSequence textToAnnounce) {
239             mHost = new WeakReference<View>(host);
240             mTextToAnnounce = textToAnnounce;
241         }
242 
243         @Override
run()244         public void run() {
245             final View host = mHost.get();
246             if (host != null) {
247                 host.announceForAccessibility(mTextToAnnounce);
248             }
249         }
250     }
251 }
252