KeyguardMessageArea.java revision 20daffd91e4a53054f8c4d7a66c2a68100abee03
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
17package com.android.keyguard;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.os.BatteryManager;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Looper;
28import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.os.SystemClock;
31import android.os.UserHandle;
32import android.provider.Settings;
33import android.text.TextUtils;
34import android.util.AttributeSet;
35import android.util.Slog;
36import android.view.View;
37import android.widget.TextView;
38
39import libcore.util.MutableInt;
40
41import java.lang.ref.WeakReference;
42
43import com.android.internal.widget.LockPatternUtils;
44
45/***
46 * Manages a number of views inside of the given layout. See below for a list of widgets.
47 */
48class KeyguardMessageArea extends TextView {
49    /** 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
58    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;
62    protected static final int FADE_DURATION = 750;
63
64    private static final String TAG = "KeyguardMessageArea";
65
66    // are we showing battery information?
67    boolean mShowingBatteryInfo = false;
68
69    // is the bouncer up?
70    boolean mShowingBouncer = false;
71
72    // last known plugged in state
73    boolean mCharging = false;
74
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;
91    private CharSequence mSeparator;
92    private LockPatternUtils mLockPatternUtils;
93
94    Runnable mClearMessageRunnable = new Runnable() {
95        @Override
96        public void run() {
97            mMessage = null;
98            mShowingMessage = false;
99            if (mShowingBouncer) {
100                hideMessage(FADE_DURATION, true);
101            } else {
102                update();
103            }
104        }
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
138        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
150        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();
159            mCharging = status.status == BatteryManager.BATTERY_STATUS_CHARGING
160                     || status.status == BatteryManager.BATTERY_STATUS_FULL;
161            mBatteryLevel = status.level;
162            mBatteryCharged = status.isCharged();
163            mBatteryIsLow = status.isBatteryLow();
164            update();
165        }
166        public void onScreenTurnedOff(int why) {
167            setSelected(false);
168        };
169        public void onScreenTurnedOn() {
170            setSelected(true);
171        };
172    };
173
174    public KeyguardMessageArea(Context context) {
175        this(context, null);
176    }
177
178    public KeyguardMessageArea(Context context, AttributeSet attrs) {
179        super(context, attrs);
180
181        mLockPatternUtils = new LockPatternUtils(context);
182
183        // Registering this callback immediately updates the battery state, among other things.
184        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
185        mUpdateMonitor.registerCallback(mInfoCallback);
186        mHandler = new Handler(Looper.myLooper());
187
188        mSeparator = getResources().getString(R.string.kg_text_message_separator);
189
190        update();
191    }
192
193    @Override
194    protected void onFinishInflate() {
195        final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
196        setSelected(screenOn); // This is required to ensure marquee works
197    }
198
199    public void securityMessageChanged() {
200        setAlpha(1f);
201        mShowingMessage = true;
202        update();
203        mHandler.removeCallbacks(mClearMessageRunnable);
204        if (mTimeout > 0) {
205            mHandler.postDelayed(mClearMessageRunnable, mTimeout);
206        }
207        mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
208        mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
209                (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
210    }
211
212    /**
213     * Update the status lines based on these rules:
214     * AlarmStatus: Alarm state always gets it's own line.
215     * Status1 is shared between help, battery status and generic unlock instructions,
216     * prioritized in that order.
217     * @param showStatusLines status lines are shown if true
218     */
219    void update() {
220        MutableInt icon = new MutableInt(0);
221        CharSequence status = concat(getChargeInfo(icon), getOwnerInfo(), getCurrentMessage());
222        setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0);
223        setText(status);
224    }
225
226    private CharSequence concat(CharSequence... args) {
227        StringBuilder b = new StringBuilder();
228        if (!TextUtils.isEmpty(args[0])) {
229            b.append(args[0]);
230        }
231        for (int i = 1; i < args.length; i++) {
232            CharSequence text = args[i];
233            if (!TextUtils.isEmpty(text)) {
234                if (b.length() > 0) {
235                    b.append(mSeparator);
236                }
237                b.append(text);
238            }
239        }
240        return b.toString();
241    }
242
243    CharSequence getCurrentMessage() {
244        return mShowingMessage ? mMessage : null;
245    }
246
247    String getOwnerInfo() {
248        ContentResolver res = getContext().getContentResolver();
249        String info = null;
250        final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled();
251        if (ownerInfoEnabled && !mShowingMessage) {
252            info = mLockPatternUtils.getOwnerInfo(mLockPatternUtils.getCurrentUser());
253        }
254        return info;
255    }
256
257    private CharSequence getChargeInfo(MutableInt icon) {
258        CharSequence string = null;
259        if (mShowingBatteryInfo && !mShowingMessage) {
260            // Battery status
261            if (mCharging) {
262                // Charging, charged or waiting to charge.
263                string = getContext().getString(mBatteryCharged
264                        ? R.string.keyguard_charged
265                        : R.string.keyguard_plugged_in, mBatteryLevel);
266                icon.value = CHARGING_ICON;
267            } else if (mBatteryIsLow) {
268                // Battery is low
269                string = getContext().getString(R.string.keyguard_low_battery);
270                icon.value = BATTERY_LOW_ICON;
271            }
272        }
273        return string;
274    }
275
276    private void hideMessage(int duration, boolean thenUpdate) {
277        if (duration > 0) {
278            Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f);
279            anim.setDuration(duration);
280            if (thenUpdate) {
281                anim.addListener(new AnimatorListenerAdapter() {
282                        @Override
283                            public void onAnimationEnd(Animator animation) {
284                            update();
285                        }
286                });
287            }
288            anim.start();
289        } else {
290            setAlpha(0f);
291            if (thenUpdate) {
292                update();
293            }
294        }
295    }
296
297    private void showMessage(int duration) {
298        if (duration > 0) {
299            Animator anim = ObjectAnimator.ofFloat(this, "alpha", 1f);
300            anim.setDuration(duration);
301            anim.start();
302        } else {
303            setAlpha(1f);
304        }
305    }
306
307    /**
308     * Runnable used to delay accessibility announcements.
309     */
310    private static class AnnounceRunnable implements Runnable {
311        private final WeakReference<View> mHost;
312        private final CharSequence mTextToAnnounce;
313
314        public AnnounceRunnable(View host, CharSequence textToAnnounce) {
315            mHost = new WeakReference<View>(host);
316            mTextToAnnounce = textToAnnounce;
317        }
318
319        @Override
320        public void run() {
321            final View host = mHost.get();
322            if (host != null) {
323                host.announceForAccessibility(mTextToAnnounce);
324            }
325        }
326    }
327}
328