1/*
2 * Copyright (C) 2014 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.systemui.statusbar;
18
19import android.app.ActivityManager;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.res.Resources;
25import android.graphics.Color;
26import android.hardware.fingerprint.FingerprintManager;
27import android.os.BatteryManager;
28import android.os.BatteryStats;
29import android.os.Handler;
30import android.os.Message;
31import android.os.RemoteException;
32import android.os.ServiceManager;
33import android.os.UserHandle;
34import android.os.UserManager;
35import android.text.TextUtils;
36import android.text.format.Formatter;
37import android.util.Log;
38import android.view.View;
39
40import com.android.internal.app.IBatteryStats;
41import com.android.keyguard.KeyguardUpdateMonitor;
42import com.android.keyguard.KeyguardUpdateMonitorCallback;
43import com.android.systemui.R;
44import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
45import com.android.systemui.statusbar.phone.LockIcon;
46import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
47
48/**
49 * Controls the indications and error messages shown on the Keyguard
50 */
51public class KeyguardIndicationController {
52
53    private static final String TAG = "KeyguardIndication";
54    private static final boolean DEBUG_CHARGING_SPEED = false;
55
56    private static final int MSG_HIDE_TRANSIENT = 1;
57    private static final int MSG_CLEAR_FP_MSG = 2;
58    private static final long TRANSIENT_FP_ERROR_TIMEOUT = 1300;
59
60    private final Context mContext;
61    private final KeyguardIndicationTextView mTextView;
62    private final UserManager mUserManager;
63    private final IBatteryStats mBatteryInfo;
64
65    private final int mSlowThreshold;
66    private final int mFastThreshold;
67    private final LockIcon mLockIcon;
68    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
69
70    private String mRestingIndication;
71    private String mTransientIndication;
72    private int mTransientTextColor;
73    private boolean mVisible;
74
75    private boolean mPowerPluggedIn;
76    private boolean mPowerCharged;
77    private int mChargingSpeed;
78    private int mChargingWattage;
79    private String mMessageToShowOnScreenOn;
80
81    public KeyguardIndicationController(Context context, KeyguardIndicationTextView textView,
82                                        LockIcon lockIcon) {
83        mContext = context;
84        mTextView = textView;
85        mLockIcon = lockIcon;
86
87        Resources res = context.getResources();
88        mSlowThreshold = res.getInteger(R.integer.config_chargingSlowlyThreshold);
89        mFastThreshold = res.getInteger(R.integer.config_chargingFastThreshold);
90
91        mUserManager = context.getSystemService(UserManager.class);
92        mBatteryInfo = IBatteryStats.Stub.asInterface(
93                ServiceManager.getService(BatteryStats.SERVICE_NAME));
94
95        KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitor);
96        context.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM,
97                new IntentFilter(Intent.ACTION_TIME_TICK), null, null);
98    }
99
100    public void setVisible(boolean visible) {
101        mVisible = visible;
102        mTextView.setVisibility(visible ? View.VISIBLE : View.GONE);
103        if (visible) {
104            hideTransientIndication();
105            updateIndication();
106        }
107    }
108
109    /**
110     * Sets the indication that is shown if nothing else is showing.
111     */
112    public void setRestingIndication(String restingIndication) {
113        mRestingIndication = restingIndication;
114        updateIndication();
115    }
116
117    /**
118     * Hides transient indication in {@param delayMs}.
119     */
120    public void hideTransientIndicationDelayed(long delayMs) {
121        mHandler.sendMessageDelayed(
122                mHandler.obtainMessage(MSG_HIDE_TRANSIENT), delayMs);
123    }
124
125    /**
126     * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
127     */
128    public void showTransientIndication(int transientIndication) {
129        showTransientIndication(mContext.getResources().getString(transientIndication));
130    }
131
132    /**
133     * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
134     */
135    public void showTransientIndication(String transientIndication) {
136        showTransientIndication(transientIndication, Color.WHITE);
137    }
138
139    /**
140     * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
141     */
142    public void showTransientIndication(String transientIndication, int textColor) {
143        mTransientIndication = transientIndication;
144        mTransientTextColor = textColor;
145        mHandler.removeMessages(MSG_HIDE_TRANSIENT);
146        updateIndication();
147    }
148
149    /**
150     * Hides transient indication.
151     */
152    public void hideTransientIndication() {
153        if (mTransientIndication != null) {
154            mTransientIndication = null;
155            mHandler.removeMessages(MSG_HIDE_TRANSIENT);
156            updateIndication();
157        }
158    }
159
160    private void updateIndication() {
161        if (mVisible) {
162            // Walk down a precedence-ordered list of what should indication
163            // should be shown based on user or device state
164            if (!mUserManager.isUserUnlocked(ActivityManager.getCurrentUser())) {
165                mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
166                mTextView.setTextColor(Color.WHITE);
167
168            } else if (!TextUtils.isEmpty(mTransientIndication)) {
169                mTextView.switchIndication(mTransientIndication);
170                mTextView.setTextColor(mTransientTextColor);
171
172            } else if (mPowerPluggedIn) {
173                String indication = computePowerIndication();
174                if (DEBUG_CHARGING_SPEED) {
175                    indication += ",  " + (mChargingWattage / 1000) + " mW";
176                }
177                mTextView.switchIndication(indication);
178                mTextView.setTextColor(Color.WHITE);
179
180            } else {
181                mTextView.switchIndication(mRestingIndication);
182                mTextView.setTextColor(Color.WHITE);
183            }
184        }
185    }
186
187    private String computePowerIndication() {
188        if (mPowerCharged) {
189            return mContext.getResources().getString(R.string.keyguard_charged);
190        }
191
192        // Try fetching charging time from battery stats.
193        long chargingTimeRemaining = 0;
194        try {
195            chargingTimeRemaining = mBatteryInfo.computeChargeTimeRemaining();
196
197        } catch (RemoteException e) {
198            Log.e(TAG, "Error calling IBatteryStats: ", e);
199        }
200        final boolean hasChargingTime = chargingTimeRemaining > 0;
201
202        int chargingId;
203        switch (mChargingSpeed) {
204            case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST:
205                chargingId = hasChargingTime
206                        ? R.string.keyguard_indication_charging_time_fast
207                        : R.string.keyguard_plugged_in_charging_fast;
208                break;
209            case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY:
210                chargingId = hasChargingTime
211                        ? R.string.keyguard_indication_charging_time_slowly
212                        : R.string.keyguard_plugged_in_charging_slowly;
213                break;
214            default:
215                chargingId = hasChargingTime
216                        ? R.string.keyguard_indication_charging_time
217                        : R.string.keyguard_plugged_in;
218                break;
219        }
220
221        if (hasChargingTime) {
222            String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
223                    mContext, chargingTimeRemaining);
224            return mContext.getResources().getString(chargingId, chargingTimeFormatted);
225        } else {
226            return mContext.getResources().getString(chargingId);
227        }
228    }
229
230    KeyguardUpdateMonitorCallback mUpdateMonitor = new KeyguardUpdateMonitorCallback() {
231        public int mLastSuccessiveErrorMessage = -1;
232
233        @Override
234        public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
235            boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
236                    || status.status == BatteryManager.BATTERY_STATUS_FULL;
237            mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull;
238            mPowerCharged = status.isCharged();
239            mChargingWattage = status.maxChargingWattage;
240            mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold);
241            updateIndication();
242        }
243
244        @Override
245        public void onFingerprintHelp(int msgId, String helpString) {
246            KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
247            if (!updateMonitor.isUnlockingWithFingerprintAllowed()) {
248                return;
249            }
250            int errorColor = mContext.getResources().getColor(R.color.system_warning_color, null);
251            if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
252                mStatusBarKeyguardViewManager.showBouncerMessage(helpString, errorColor);
253            } else if (updateMonitor.isDeviceInteractive()) {
254                mLockIcon.setTransientFpError(true);
255                showTransientIndication(helpString, errorColor);
256                mHandler.removeMessages(MSG_CLEAR_FP_MSG);
257                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_FP_MSG),
258                        TRANSIENT_FP_ERROR_TIMEOUT);
259            }
260            // Help messages indicate that there was actually a try since the last error, so those
261            // are not two successive error messages anymore.
262            mLastSuccessiveErrorMessage = -1;
263        }
264
265        @Override
266        public void onFingerprintError(int msgId, String errString) {
267            KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
268            if (!updateMonitor.isUnlockingWithFingerprintAllowed()
269                    || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
270                return;
271            }
272            int errorColor = mContext.getResources().getColor(R.color.system_warning_color, null);
273            if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
274                // When swiping up right after receiving a fingerprint error, the bouncer calls
275                // authenticate leading to the same message being shown again on the bouncer.
276                // We want to avoid this, as it may confuse the user when the message is too
277                // generic.
278                if (mLastSuccessiveErrorMessage != msgId) {
279                    mStatusBarKeyguardViewManager.showBouncerMessage(errString, errorColor);
280                }
281            } else if (updateMonitor.isDeviceInteractive()) {
282                showTransientIndication(errString, errorColor);
283                // We want to keep this message around in case the screen was off
284                mHandler.removeMessages(MSG_HIDE_TRANSIENT);
285                hideTransientIndicationDelayed(5000);
286            } else {
287                mMessageToShowOnScreenOn = errString;
288            }
289            mLastSuccessiveErrorMessage = msgId;
290        }
291
292        @Override
293        public void onScreenTurnedOn() {
294            if (mMessageToShowOnScreenOn != null) {
295                int errorColor = mContext.getResources().getColor(R.color.system_warning_color,
296                        null);
297                showTransientIndication(mMessageToShowOnScreenOn, errorColor);
298                // We want to keep this message around in case the screen was off
299                mHandler.removeMessages(MSG_HIDE_TRANSIENT);
300                hideTransientIndicationDelayed(5000);
301                mMessageToShowOnScreenOn = null;
302            }
303        }
304
305        @Override
306        public void onFingerprintRunningStateChanged(boolean running) {
307            if (running) {
308                mMessageToShowOnScreenOn = null;
309            }
310        }
311
312        @Override
313        public void onFingerprintAuthenticated(int userId) {
314            super.onFingerprintAuthenticated(userId);
315            mLastSuccessiveErrorMessage = -1;
316        }
317
318        @Override
319        public void onFingerprintAuthFailed() {
320            super.onFingerprintAuthFailed();
321            mLastSuccessiveErrorMessage = -1;
322        }
323
324        @Override
325        public void onUserUnlocked() {
326            if (mVisible) {
327                updateIndication();
328            }
329        }
330    };
331
332    BroadcastReceiver mTickReceiver = new BroadcastReceiver() {
333        @Override
334        public void onReceive(Context context, Intent intent) {
335            if (mVisible) {
336                updateIndication();
337            }
338        }
339    };
340
341
342    private final Handler mHandler = new Handler() {
343        @Override
344        public void handleMessage(Message msg) {
345            if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) {
346                mTransientIndication = null;
347                updateIndication();
348            } else if (msg.what == MSG_CLEAR_FP_MSG) {
349                mLockIcon.setTransientFpError(false);
350                hideTransientIndication();
351            }
352        }
353    };
354
355    public void setStatusBarKeyguardViewManager(
356            StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
357        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
358    }
359}
360