KeyguardMessageArea.java revision 5ecd81154fa039961f65bb4e36d18ac555b0d1d6
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.Looper;
27import android.os.SystemClock;
28import android.os.UserHandle;
29import android.provider.Settings;
30import android.text.TextUtils;
31import android.util.AttributeSet;
32import android.view.View;
33import android.widget.TextView;
34
35import libcore.util.MutableInt;
36
37import java.lang.ref.WeakReference;
38/***
39 * Manages a number of views inside of the given layout. See below for a list of widgets.
40 */
41class 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 CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging;
52    static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery;
53
54    static final int SECURITY_MESSAGE_DURATION = 5000;
55    protected static final int FADE_DURATION = 750;
56
57    // are we showing battery information?
58    boolean mShowingBatteryInfo = false;
59
60    // is the bouncer up?
61    boolean mShowingBouncer = false;
62
63    // last known plugged in state
64    boolean mCharging = false;
65
66    // last known battery level
67    int mBatteryLevel = 100;
68
69    KeyguardUpdateMonitor mUpdateMonitor;
70
71    // Timeout before we reset the message to show charging/owner info
72    long mTimeout = SECURITY_MESSAGE_DURATION;
73
74    // Shadowed text values
75    protected boolean mBatteryCharged;
76    protected boolean mBatteryIsLow;
77
78    private Handler mHandler;
79
80    CharSequence mMessage;
81    boolean mShowingMessage;
82    Runnable mClearMessageRunnable = new Runnable() {
83        @Override
84        public void run() {
85            mMessage = null;
86            mShowingMessage = false;
87            if (mShowingBouncer) {
88                hideMessage(FADE_DURATION, true);
89            } else {
90                update();
91            }
92        }
93    };
94
95    public static class Helper implements SecurityMessageDisplay {
96        KeyguardMessageArea mMessageArea;
97        Helper(View v) {
98            mMessageArea = (KeyguardMessageArea) v.findViewById(R.id.keyguard_message_area);
99            if (mMessageArea == null) {
100                throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass());
101            }
102        }
103
104        public void setMessage(CharSequence msg, boolean important) {
105            if (!TextUtils.isEmpty(msg) && important) {
106                mMessageArea.mMessage = msg;
107                mMessageArea.securityMessageChanged();
108            }
109        }
110
111        public void setMessage(int resId, boolean important) {
112            if (resId != 0 && important) {
113                mMessageArea.mMessage = mMessageArea.getContext().getResources().getText(resId);
114                mMessageArea.securityMessageChanged();
115            }
116        }
117
118        public void setMessage(int resId, boolean important, Object... formatArgs) {
119            if (resId != 0 && important) {
120                mMessageArea.mMessage = mMessageArea.getContext().getString(resId, formatArgs);
121                mMessageArea.securityMessageChanged();
122            }
123        }
124
125        @Override
126        public void showBouncer(int duration) {
127            mMessageArea.hideMessage(duration, false);
128            mMessageArea.mShowingBouncer = true;
129        }
130
131        @Override
132        public void hideBouncer(int duration) {
133            mMessageArea.showMessage(duration);
134            mMessageArea.mShowingBouncer = false;
135        }
136
137        @Override
138        public void setTimeout(int timeoutMs) {
139            mMessageArea.mTimeout = timeoutMs;
140        }
141    }
142
143    private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
144        @Override
145        public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
146            mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow();
147            mCharging = status.status == BatteryManager.BATTERY_STATUS_CHARGING
148                     || status.status == BatteryManager.BATTERY_STATUS_FULL;
149            mBatteryLevel = status.level;
150            mBatteryCharged = status.isCharged();
151            mBatteryIsLow = status.isBatteryLow();
152            update();
153        }
154    };
155
156    private CharSequence mSeparator;
157
158    public KeyguardMessageArea(Context context) {
159        this(context, null);
160    }
161
162    public KeyguardMessageArea(Context context, AttributeSet attrs) {
163        super(context, attrs);
164
165        // This is required to ensure marquee works
166        setSelected(true);
167
168        // Registering this callback immediately updates the battery state, among other things.
169        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
170        mUpdateMonitor.registerCallback(mInfoCallback);
171        mHandler = new Handler(Looper.myLooper());
172
173        mSeparator = getResources().getString(R.string.kg_text_message_separator);
174
175        update();
176    }
177
178    public void securityMessageChanged() {
179        setAlpha(1f);
180        mShowingMessage = true;
181        update();
182        mHandler.removeCallbacks(mClearMessageRunnable);
183        if (mTimeout > 0) {
184            mHandler.postDelayed(mClearMessageRunnable, mTimeout);
185        }
186        mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
187        mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
188                (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
189    }
190
191    /**
192     * Update the status lines based on these rules:
193     * AlarmStatus: Alarm state always gets it's own line.
194     * Status1 is shared between help, battery status and generic unlock instructions,
195     * prioritized in that order.
196     * @param showStatusLines status lines are shown if true
197     */
198    void update() {
199        MutableInt icon = new MutableInt(0);
200        CharSequence status = concat(getChargeInfo(icon), getOwnerInfo(), getCurrentMessage());
201        setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0);
202        setText(status);
203    }
204
205    private CharSequence concat(CharSequence... args) {
206        StringBuilder b = new StringBuilder();
207        if (!TextUtils.isEmpty(args[0])) {
208            b.append(args[0]);
209        }
210        for (int i = 1; i < args.length; i++) {
211            CharSequence text = args[i];
212            if (!TextUtils.isEmpty(text)) {
213                if (b.length() > 0) {
214                    b.append(mSeparator);
215                }
216                b.append(text);
217            }
218        }
219        return b.toString();
220    }
221
222    CharSequence getCurrentMessage() {
223        return mShowingMessage ? mMessage : null;
224    }
225
226    String getOwnerInfo() {
227        ContentResolver res = getContext().getContentResolver();
228        final boolean ownerInfoEnabled = Settings.Secure.getIntForUser(res,
229                Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
230        return ownerInfoEnabled && !mShowingMessage ?
231                Settings.Secure.getStringForUser(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO,
232                        UserHandle.USER_CURRENT) : null;
233    }
234
235    private CharSequence getChargeInfo(MutableInt icon) {
236        CharSequence string = null;
237        if (mShowingBatteryInfo && !mShowingMessage) {
238            // Battery status
239            if (mCharging) {
240                // Charging, charged or waiting to charge.
241                string = getContext().getString(mBatteryCharged
242                        ? R.string.keyguard_charged
243                        : R.string.keyguard_plugged_in, mBatteryLevel);
244                icon.value = CHARGING_ICON;
245            } else if (mBatteryIsLow) {
246                // Battery is low
247                string = getContext().getString(R.string.keyguard_low_battery);
248                icon.value = BATTERY_LOW_ICON;
249            }
250        }
251        return string;
252    }
253
254    private void hideMessage(int duration, boolean thenUpdate) {
255        if (duration > 0) {
256            Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f);
257            anim.setDuration(duration);
258            if (thenUpdate) {
259                anim.addListener(new AnimatorListenerAdapter() {
260                        @Override
261                            public void onAnimationEnd(Animator animation) {
262                            update();
263                        }
264                });
265            }
266            anim.start();
267        } else {
268            setAlpha(0f);
269            if (thenUpdate) {
270                update();
271            }
272        }
273    }
274
275    private void showMessage(int duration) {
276        if (duration > 0) {
277            Animator anim = ObjectAnimator.ofFloat(this, "alpha", 1f);
278            anim.setDuration(duration);
279            anim.start();
280        } else {
281            setAlpha(1f);
282        }
283    }
284
285    /**
286     * Runnable used to delay accessibility announcements.
287     */
288    private static class AnnounceRunnable implements Runnable {
289        private final WeakReference<View> mHost;
290        private final CharSequence mTextToAnnounce;
291
292        public AnnounceRunnable(View host, CharSequence textToAnnounce) {
293            mHost = new WeakReference<View>(host);
294            mTextToAnnounce = textToAnnounce;
295        }
296
297        @Override
298        public void run() {
299            final View host = mHost.get();
300            if (host != null) {
301                host.announceForAccessibility(mTextToAnnounce);
302            }
303        }
304    }
305}
306