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