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