• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.systemui.statusbar;
18 
19 import android.app.ActivityManager;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.res.Resources;
25 import android.graphics.Color;
26 import android.hardware.fingerprint.FingerprintManager;
27 import android.os.BatteryManager;
28 import android.os.BatteryStats;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.text.TextUtils;
36 import android.text.format.Formatter;
37 import android.util.Log;
38 import android.view.View;
39 
40 import com.android.internal.app.IBatteryStats;
41 import com.android.keyguard.KeyguardUpdateMonitor;
42 import com.android.keyguard.KeyguardUpdateMonitorCallback;
43 import com.android.systemui.R;
44 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
45 import com.android.systemui.statusbar.phone.LockIcon;
46 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
47 
48 /**
49  * Controls the indications and error messages shown on the Keyguard
50  */
51 public 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 
KeyguardIndicationController(Context context, KeyguardIndicationTextView textView, LockIcon lockIcon)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(mReceiver, UserHandle.SYSTEM,
97                 new IntentFilter(Intent.ACTION_TIME_TICK), null, null);
98     }
99 
setVisible(boolean visible)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      */
setRestingIndication(String restingIndication)112     public void setRestingIndication(String restingIndication) {
113         mRestingIndication = restingIndication;
114         updateIndication();
115     }
116 
117     /**
118      * Hides transient indication in {@param delayMs}.
119      */
hideTransientIndicationDelayed(long delayMs)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      */
showTransientIndication(int transientIndication)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      */
showTransientIndication(String transientIndication)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      */
showTransientIndication(String transientIndication, int textColor)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      */
hideTransientIndication()152     public void hideTransientIndication() {
153         if (mTransientIndication != null) {
154             mTransientIndication = null;
155             mHandler.removeMessages(MSG_HIDE_TRANSIENT);
156             updateIndication();
157         }
158     }
159 
updateIndication()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 
computePowerIndication()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 
325     BroadcastReceiver mReceiver = new BroadcastReceiver() {
326         @Override
327         public void onReceive(Context context, Intent intent) {
328             if (mVisible) {
329                 updateIndication();
330             }
331         }
332     };
333 
334     private final Handler mHandler = new Handler() {
335         @Override
336         public void handleMessage(Message msg) {
337             if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) {
338                 mTransientIndication = null;
339                 updateIndication();
340             } else if (msg.what == MSG_CLEAR_FP_MSG) {
341                 mLockIcon.setTransientFpError(false);
342                 hideTransientIndication();
343             }
344         }
345     };
346 
setStatusBarKeyguardViewManager( StatusBarKeyguardViewManager statusBarKeyguardViewManager)347     public void setStatusBarKeyguardViewManager(
348             StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
349         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
350     }
351 }
352