KeyguardMessageArea.java revision cacbe1b1ef01aff8726726edaf43d0b449319afe
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.MutableInt;
36import android.util.Slog;
37import android.view.View;
38import android.widget.TextView;
39
40import java.lang.ref.WeakReference;
41
42import com.android.internal.widget.LockPatternUtils;
43
44/***
45 * Manages a number of views inside of the given layout. See below for a list of widgets.
46 */
47class KeyguardMessageArea extends TextView {
48    /** Handler token posted with accessibility announcement runnables. */
49    private static final Object ANNOUNCE_TOKEN = new Object();
50
51    /**
52     * Delay before speaking an accessibility announcement. Used to prevent
53     * lift-to-type from interrupting itself.
54     */
55    private static final long ANNOUNCEMENT_DELAY = 250;
56
57    static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging;
58    static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery;
59
60    static final int SECURITY_MESSAGE_DURATION = 5000;
61    protected static final int FADE_DURATION = 750;
62
63    private static final String TAG = "KeyguardMessageArea";
64
65    // are we showing battery information?
66    boolean mShowingBatteryInfo = false;
67
68    // is the bouncer up?
69    boolean mShowingBouncer = false;
70
71    // last known plugged in state
72    boolean mCharging = false;
73
74    // last known battery level
75    int mBatteryLevel = 100;
76
77    KeyguardUpdateMonitor mUpdateMonitor;
78
79    // Timeout before we reset the message to show charging/owner info
80    long mTimeout = SECURITY_MESSAGE_DURATION;
81
82    // Shadowed text values
83    protected boolean mBatteryCharged;
84    protected boolean mBatteryIsLow;
85
86    private Handler mHandler;
87
88    CharSequence mMessage;
89    boolean mShowingMessage;
90    private CharSequence mSeparator;
91    private LockPatternUtils mLockPatternUtils;
92
93    Runnable mClearMessageRunnable = new Runnable() {
94        @Override
95        public void run() {
96            mMessage = null;
97            mShowingMessage = false;
98            if (mShowingBouncer) {
99                hideMessage(FADE_DURATION, true);
100            } else {
101                update();
102            }
103        }
104    };
105
106    public static class Helper implements SecurityMessageDisplay {
107        KeyguardMessageArea mMessageArea;
108        Helper(View v) {
109            mMessageArea = (KeyguardMessageArea) v.findViewById(R.id.keyguard_message_area);
110            if (mMessageArea == null) {
111                throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass());
112            }
113        }
114
115        public void setMessage(CharSequence msg, boolean important) {
116            if (!TextUtils.isEmpty(msg) && important) {
117                mMessageArea.mMessage = msg;
118                mMessageArea.securityMessageChanged();
119            }
120        }
121
122        public void setMessage(int resId, boolean important) {
123            if (resId != 0 && important) {
124                mMessageArea.mMessage = mMessageArea.getContext().getResources().getText(resId);
125                mMessageArea.securityMessageChanged();
126            }
127        }
128
129        public void setMessage(int resId, boolean important, Object... formatArgs) {
130            if (resId != 0 && important) {
131                mMessageArea.mMessage = mMessageArea.getContext().getString(resId, formatArgs);
132                mMessageArea.securityMessageChanged();
133            }
134        }
135
136        @Override
137        public void showBouncer(int duration) {
138            mMessageArea.hideMessage(duration, false);
139            mMessageArea.mShowingBouncer = true;
140        }
141
142        @Override
143        public void hideBouncer(int duration) {
144            mMessageArea.showMessage(duration);
145            mMessageArea.mShowingBouncer = false;
146        }
147
148        @Override
149        public void setTimeout(int timeoutMs) {
150            mMessageArea.mTimeout = timeoutMs;
151        }
152    }
153
154    private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
155        @Override
156        public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
157            mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow();
158            mCharging = status.status == BatteryManager.BATTERY_STATUS_CHARGING
159                     || status.status == BatteryManager.BATTERY_STATUS_FULL;
160            mBatteryLevel = status.level;
161            mBatteryCharged = status.isCharged();
162            mBatteryIsLow = status.isBatteryLow();
163            update();
164        }
165        public void onScreenTurnedOff(int why) {
166            setSelected(false);
167        };
168        public void onScreenTurnedOn() {
169            setSelected(true);
170        };
171    };
172
173    public KeyguardMessageArea(Context context) {
174        this(context, null);
175    }
176
177    public KeyguardMessageArea(Context context, AttributeSet attrs) {
178        super(context, attrs);
179        setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
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