blob: 751572c7276130247edaeb890ec6ee1d715a8007 [file] [log] [blame]
Jim Miller0b728242012-10-28 19:42:30 -07001/*
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
Jim Miller5ecd8112013-01-09 18:50:26 -080017package com.android.keyguard;
Jim Miller0b728242012-10-28 19:42:30 -070018
Chris Wrenc3451462012-10-30 11:22:58 -040019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
Jim Miller0b728242012-10-28 19:42:30 -070022import android.content.ContentResolver;
23import android.content.Context;
Daniel Sandlerfe0e1e42012-11-29 15:11:50 -050024import android.os.BatteryManager;
Jim Miller0b728242012-10-28 19:42:30 -070025import android.os.Handler;
Jim Miller187ec582013-04-15 18:27:54 -070026import android.os.IBinder;
Jim Miller0b728242012-10-28 19:42:30 -070027import android.os.Looper;
Jim Miller187ec582013-04-15 18:27:54 -070028import android.os.RemoteException;
29import android.os.ServiceManager;
Alan Viveretteb428b0f2013-02-07 12:19:15 -080030import android.os.SystemClock;
Jim Miller0b728242012-10-28 19:42:30 -070031import android.os.UserHandle;
32import android.provider.Settings;
33import android.text.TextUtils;
34import android.util.AttributeSet;
Jim Miller187ec582013-04-15 18:27:54 -070035import android.util.Slog;
Jim Miller0b728242012-10-28 19:42:30 -070036import android.view.View;
37import android.widget.TextView;
38
39import libcore.util.MutableInt;
40
Alan Viveretteb428b0f2013-02-07 12:19:15 -080041import java.lang.ref.WeakReference;
42
Jim Miller187ec582013-04-15 18:27:54 -070043import com.android.internal.widget.LockPatternUtils;
Jim Miller0b728242012-10-28 19:42:30 -070044
45/***
46 * Manages a number of views inside of the given layout. See below for a list of widgets.
47 */
48class KeyguardMessageArea extends TextView {
Alan Viveretteb428b0f2013-02-07 12:19:15 -080049 /** Handler token posted with accessibility announcement runnables. */
50 private static final Object ANNOUNCE_TOKEN = new Object();
51
52 /**
53 * Delay before speaking an accessibility announcement. Used to prevent
54 * lift-to-type from interrupting itself.
55 */
56 private static final long ANNOUNCEMENT_DELAY = 250;
57
Jim Miller0b728242012-10-28 19:42:30 -070058 static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging;
59 static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery;
60
61 static final int SECURITY_MESSAGE_DURATION = 5000;
Chris Wrenc3451462012-10-30 11:22:58 -040062 protected static final int FADE_DURATION = 750;
Jim Miller0b728242012-10-28 19:42:30 -070063
Jim Miller187ec582013-04-15 18:27:54 -070064 private static final String TAG = "KeyguardMessageArea";
65
Jim Miller0b728242012-10-28 19:42:30 -070066 // are we showing battery information?
67 boolean mShowingBatteryInfo = false;
68
Chris Wrenc3451462012-10-30 11:22:58 -040069 // is the bouncer up?
70 boolean mShowingBouncer = false;
71
Jim Miller0b728242012-10-28 19:42:30 -070072 // last known plugged in state
Daniel Sandlerfe0e1e42012-11-29 15:11:50 -050073 boolean mCharging = false;
Jim Miller0b728242012-10-28 19:42:30 -070074
75 // last known battery level
76 int mBatteryLevel = 100;
77
78 KeyguardUpdateMonitor mUpdateMonitor;
79
80 // Timeout before we reset the message to show charging/owner info
81 long mTimeout = SECURITY_MESSAGE_DURATION;
82
83 // Shadowed text values
84 protected boolean mBatteryCharged;
85 protected boolean mBatteryIsLow;
86
87 private Handler mHandler;
88
89 CharSequence mMessage;
90 boolean mShowingMessage;
Jim Miller187ec582013-04-15 18:27:54 -070091 private CharSequence mSeparator;
92 private LockPatternUtils mLockPatternUtils;
93
Jim Miller0b728242012-10-28 19:42:30 -070094 Runnable mClearMessageRunnable = new Runnable() {
95 @Override
96 public void run() {
97 mMessage = null;
98 mShowingMessage = false;
Chris Wrenc3451462012-10-30 11:22:58 -040099 if (mShowingBouncer) {
100 hideMessage(FADE_DURATION, true);
101 } else {
102 update();
103 }
Jim Miller0b728242012-10-28 19:42:30 -0700104 }
105 };
106
107 public static class Helper implements SecurityMessageDisplay {
108 KeyguardMessageArea mMessageArea;
109 Helper(View v) {
110 mMessageArea = (KeyguardMessageArea) v.findViewById(R.id.keyguard_message_area);
111 if (mMessageArea == null) {
112 throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass());
113 }
114 }
115
116 public void setMessage(CharSequence msg, boolean important) {
117 if (!TextUtils.isEmpty(msg) && important) {
118 mMessageArea.mMessage = msg;
119 mMessageArea.securityMessageChanged();
120 }
121 }
122
123 public void setMessage(int resId, boolean important) {
124 if (resId != 0 && important) {
125 mMessageArea.mMessage = mMessageArea.getContext().getResources().getText(resId);
126 mMessageArea.securityMessageChanged();
127 }
128 }
129
130 public void setMessage(int resId, boolean important, Object... formatArgs) {
131 if (resId != 0 && important) {
132 mMessageArea.mMessage = mMessageArea.getContext().getString(resId, formatArgs);
133 mMessageArea.securityMessageChanged();
134 }
135 }
136
137 @Override
Chris Wrenc3451462012-10-30 11:22:58 -0400138 public void showBouncer(int duration) {
139 mMessageArea.hideMessage(duration, false);
140 mMessageArea.mShowingBouncer = true;
141 }
142
143 @Override
144 public void hideBouncer(int duration) {
145 mMessageArea.showMessage(duration);
146 mMessageArea.mShowingBouncer = false;
147 }
148
149 @Override
Jim Miller0b728242012-10-28 19:42:30 -0700150 public void setTimeout(int timeoutMs) {
151 mMessageArea.mTimeout = timeoutMs;
152 }
153 }
154
155 private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
156 @Override
157 public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
158 mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow();
Daniel Sandlerfe0e1e42012-11-29 15:11:50 -0500159 mCharging = status.status == BatteryManager.BATTERY_STATUS_CHARGING
160 || status.status == BatteryManager.BATTERY_STATUS_FULL;
Jim Miller0b728242012-10-28 19:42:30 -0700161 mBatteryLevel = status.level;
162 mBatteryCharged = status.isCharged();
163 mBatteryIsLow = status.isBatteryLow();
164 update();
165 }
Jim Miller20daffd2013-10-07 14:59:53 -0700166 public void onScreenTurnedOff(int why) {
167 setSelected(false);
168 };
169 public void onScreenTurnedOn() {
170 setSelected(true);
171 };
Jim Miller0b728242012-10-28 19:42:30 -0700172 };
173
174 public KeyguardMessageArea(Context context) {
175 this(context, null);
176 }
177
178 public KeyguardMessageArea(Context context, AttributeSet attrs) {
179 super(context, attrs);
Chris Craik362adb82013-11-05 17:41:21 -0800180 setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
Jim Miller0b728242012-10-28 19:42:30 -0700181
Jim Miller187ec582013-04-15 18:27:54 -0700182 mLockPatternUtils = new LockPatternUtils(context);
183
Jim Miller0b728242012-10-28 19:42:30 -0700184 // Registering this callback immediately updates the battery state, among other things.
185 mUpdateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
186 mUpdateMonitor.registerCallback(mInfoCallback);
187 mHandler = new Handler(Looper.myLooper());
188
Jim Millere9be1402012-11-01 16:14:20 -0700189 mSeparator = getResources().getString(R.string.kg_text_message_separator);
190
Jim Miller0b728242012-10-28 19:42:30 -0700191 update();
192 }
193
Jim Miller20daffd2013-10-07 14:59:53 -0700194 @Override
195 protected void onFinishInflate() {
196 final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
197 setSelected(screenOn); // This is required to ensure marquee works
198 }
199
Jim Miller0b728242012-10-28 19:42:30 -0700200 public void securityMessageChanged() {
Chris Wrenc3451462012-10-30 11:22:58 -0400201 setAlpha(1f);
Jim Miller0b728242012-10-28 19:42:30 -0700202 mShowingMessage = true;
203 update();
204 mHandler.removeCallbacks(mClearMessageRunnable);
205 if (mTimeout > 0) {
206 mHandler.postDelayed(mClearMessageRunnable, mTimeout);
207 }
Alan Viveretteb428b0f2013-02-07 12:19:15 -0800208 mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
209 mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
210 (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
Jim Miller0b728242012-10-28 19:42:30 -0700211 }
212
213 /**
214 * Update the status lines based on these rules:
215 * AlarmStatus: Alarm state always gets it's own line.
216 * Status1 is shared between help, battery status and generic unlock instructions,
217 * prioritized in that order.
218 * @param showStatusLines status lines are shown if true
219 */
220 void update() {
221 MutableInt icon = new MutableInt(0);
222 CharSequence status = concat(getChargeInfo(icon), getOwnerInfo(), getCurrentMessage());
223 setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0);
224 setText(status);
225 }
226
Jim Millere9be1402012-11-01 16:14:20 -0700227 private CharSequence concat(CharSequence... args) {
Jim Miller0b728242012-10-28 19:42:30 -0700228 StringBuilder b = new StringBuilder();
Jim Millere9be1402012-11-01 16:14:20 -0700229 if (!TextUtils.isEmpty(args[0])) {
230 b.append(args[0]);
231 }
232 for (int i = 1; i < args.length; i++) {
233 CharSequence text = args[i];
234 if (!TextUtils.isEmpty(text)) {
235 if (b.length() > 0) {
236 b.append(mSeparator);
237 }
238 b.append(text);
Jim Miller0b728242012-10-28 19:42:30 -0700239 }
240 }
241 return b.toString();
242 }
243
Jim Miller0b728242012-10-28 19:42:30 -0700244 CharSequence getCurrentMessage() {
245 return mShowingMessage ? mMessage : null;
246 }
247
248 String getOwnerInfo() {
249 ContentResolver res = getContext().getContentResolver();
Jim Miller187ec582013-04-15 18:27:54 -0700250 String info = null;
251 final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled();
252 if (ownerInfoEnabled && !mShowingMessage) {
253 info = mLockPatternUtils.getOwnerInfo(mLockPatternUtils.getCurrentUser());
254 }
255 return info;
Jim Miller0b728242012-10-28 19:42:30 -0700256 }
257
258 private CharSequence getChargeInfo(MutableInt icon) {
259 CharSequence string = null;
260 if (mShowingBatteryInfo && !mShowingMessage) {
261 // Battery status
Daniel Sandlerfe0e1e42012-11-29 15:11:50 -0500262 if (mCharging) {
Jim Miller0b728242012-10-28 19:42:30 -0700263 // Charging, charged or waiting to charge.
Daniel Sandlerfe0e1e42012-11-29 15:11:50 -0500264 string = getContext().getString(mBatteryCharged
Jim Miller5ecd8112013-01-09 18:50:26 -0800265 ? R.string.keyguard_charged
266 : R.string.keyguard_plugged_in, mBatteryLevel);
Jim Miller0b728242012-10-28 19:42:30 -0700267 icon.value = CHARGING_ICON;
268 } else if (mBatteryIsLow) {
269 // Battery is low
Jim Miller5ecd8112013-01-09 18:50:26 -0800270 string = getContext().getString(R.string.keyguard_low_battery);
Jim Miller0b728242012-10-28 19:42:30 -0700271 icon.value = BATTERY_LOW_ICON;
272 }
273 }
274 return string;
275 }
276
Chris Wrenc3451462012-10-30 11:22:58 -0400277 private void hideMessage(int duration, boolean thenUpdate) {
Chris Wrenc0ae9e62012-11-05 13:16:31 -0500278 if (duration > 0) {
279 Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f);
280 anim.setDuration(duration);
281 if (thenUpdate) {
282 anim.addListener(new AnimatorListenerAdapter() {
283 @Override
284 public void onAnimationEnd(Animator animation) {
285 update();
286 }
Chris Wrenc3451462012-10-30 11:22:58 -0400287 });
Chris Wrenc0ae9e62012-11-05 13:16:31 -0500288 }
289 anim.start();
290 } else {
291 setAlpha(0f);
292 if (thenUpdate) {
293 update();
294 }
Chris Wrenc3451462012-10-30 11:22:58 -0400295 }
Chris Wrenc3451462012-10-30 11:22:58 -0400296 }
297
298 private void showMessage(int duration) {
Chris Wrenc0ae9e62012-11-05 13:16:31 -0500299 if (duration > 0) {
300 Animator anim = ObjectAnimator.ofFloat(this, "alpha", 1f);
301 anim.setDuration(duration);
302 anim.start();
303 } else {
304 setAlpha(1f);
305 }
Chris Wrenc3451462012-10-30 11:22:58 -0400306 }
Alan Viveretteb428b0f2013-02-07 12:19:15 -0800307
308 /**
309 * Runnable used to delay accessibility announcements.
310 */
311 private static class AnnounceRunnable implements Runnable {
312 private final WeakReference<View> mHost;
313 private final CharSequence mTextToAnnounce;
314
315 public AnnounceRunnable(View host, CharSequence textToAnnounce) {
316 mHost = new WeakReference<View>(host);
317 mTextToAnnounce = textToAnnounce;
318 }
319
320 @Override
321 public void run() {
322 final View host = mHost.get();
323 if (host != null) {
324 host.announceForAccessibility(mTextToAnnounce);
325 }
326 }
327 }
Jim Miller0b728242012-10-28 19:42:30 -0700328}