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.app.admin.DevicePolicyManager;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.res.Resources;
26 import android.graphics.Color;
27 import android.hardware.fingerprint.FingerprintManager;
28 import android.os.BatteryManager;
29 import android.os.BatteryStats;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.text.TextUtils;
37 import android.text.format.Formatter;
38 import android.util.Log;
39 import android.view.View;
40 import android.view.ViewGroup;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.app.IBatteryStats;
44 import com.android.keyguard.KeyguardUpdateMonitor;
45 import com.android.keyguard.KeyguardUpdateMonitorCallback;
46 import com.android.settingslib.Utils;
47 import com.android.systemui.Dependency;
48 import com.android.systemui.R;
49 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
50 import com.android.systemui.statusbar.phone.LockIcon;
51 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
52 import com.android.systemui.statusbar.policy.UserInfoController;
53 import com.android.systemui.util.wakelock.SettableWakeLock;
54 import com.android.systemui.util.wakelock.WakeLock;
55 
56 /**
57  * Controls the indications and error messages shown on the Keyguard
58  */
59 public class KeyguardIndicationController {
60 
61     private static final String TAG = "KeyguardIndication";
62     private static final boolean DEBUG_CHARGING_SPEED = false;
63 
64     private static final int MSG_HIDE_TRANSIENT = 1;
65     private static final int MSG_CLEAR_FP_MSG = 2;
66     private static final long TRANSIENT_FP_ERROR_TIMEOUT = 1300;
67 
68     private final Context mContext;
69     private final ViewGroup mIndicationArea;
70     private final KeyguardIndicationTextView mTextView;
71     private final KeyguardIndicationTextView mDisclosure;
72     private final UserManager mUserManager;
73     private final IBatteryStats mBatteryInfo;
74     private final SettableWakeLock mWakeLock;
75 
76     private final int mSlowThreshold;
77     private final int mFastThreshold;
78     private final LockIcon mLockIcon;
79     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
80 
81     private String mRestingIndication;
82     private String mTransientIndication;
83     private int mTransientTextColor;
84     private boolean mVisible;
85 
86     private boolean mPowerPluggedIn;
87     private boolean mPowerCharged;
88     private int mChargingSpeed;
89     private int mChargingWattage;
90     private String mMessageToShowOnScreenOn;
91 
92     private KeyguardUpdateMonitorCallback mUpdateMonitor;
93 
94     private final DevicePolicyManager mDevicePolicyManager;
95     private boolean mDozing;
96 
97     /**
98      * Creates a new KeyguardIndicationController and registers callbacks.
99      */
KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon)100     public KeyguardIndicationController(Context context, ViewGroup indicationArea,
101             LockIcon lockIcon) {
102         this(context, indicationArea, lockIcon,
103                 WakeLock.createPartial(context, "Doze:KeyguardIndication"));
104 
105         registerCallbacks(KeyguardUpdateMonitor.getInstance(context));
106     }
107 
108     /**
109      * Creates a new KeyguardIndicationController for testing. Does *not* register callbacks.
110      */
111     @VisibleForTesting
KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon, WakeLock wakeLock)112     KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon,
113                 WakeLock wakeLock) {
114         mContext = context;
115         mIndicationArea = indicationArea;
116         mTextView = (KeyguardIndicationTextView) indicationArea.findViewById(
117                 R.id.keyguard_indication_text);
118         mDisclosure = (KeyguardIndicationTextView) indicationArea.findViewById(
119                 R.id.keyguard_indication_enterprise_disclosure);
120         mLockIcon = lockIcon;
121         mWakeLock = new SettableWakeLock(wakeLock);
122 
123         Resources res = context.getResources();
124         mSlowThreshold = res.getInteger(R.integer.config_chargingSlowlyThreshold);
125         mFastThreshold = res.getInteger(R.integer.config_chargingFastThreshold);
126 
127         mUserManager = context.getSystemService(UserManager.class);
128         mBatteryInfo = IBatteryStats.Stub.asInterface(
129                 ServiceManager.getService(BatteryStats.SERVICE_NAME));
130 
131         mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(
132                 Context.DEVICE_POLICY_SERVICE);
133 
134         updateDisclosure();
135     }
136 
registerCallbacks(KeyguardUpdateMonitor monitor)137     private void registerCallbacks(KeyguardUpdateMonitor monitor) {
138         monitor.registerCallback(getKeyguardCallback());
139 
140         mContext.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM,
141                 new IntentFilter(Intent.ACTION_TIME_TICK), null,
142                 Dependency.get(Dependency.TIME_TICK_HANDLER));
143     }
144 
145     /**
146      * Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this
147      * {@link KeyguardIndicationController}.
148      *
149      * <p>Subclasses may override this method to extend or change the callback behavior by extending
150      * the {@link BaseKeyguardCallback}.
151      *
152      * @return A KeyguardUpdateMonitorCallback. Multiple calls to this method <b>must</b> return the
153      * same instance.
154      */
getKeyguardCallback()155     protected KeyguardUpdateMonitorCallback getKeyguardCallback() {
156         if (mUpdateMonitor == null) {
157             mUpdateMonitor = new BaseKeyguardCallback();
158         }
159         return mUpdateMonitor;
160     }
161 
updateDisclosure()162     private void updateDisclosure() {
163         if (mDevicePolicyManager == null) {
164             return;
165         }
166 
167         if (!mDozing && mDevicePolicyManager.isDeviceManaged()) {
168             final CharSequence organizationName =
169                     mDevicePolicyManager.getDeviceOwnerOrganizationName();
170             if (organizationName != null) {
171                 mDisclosure.switchIndication(mContext.getResources().getString(
172                         R.string.do_disclosure_with_name, organizationName));
173             } else {
174                 mDisclosure.switchIndication(R.string.do_disclosure_generic);
175             }
176             mDisclosure.setVisibility(View.VISIBLE);
177         } else {
178             mDisclosure.setVisibility(View.GONE);
179         }
180     }
181 
setVisible(boolean visible)182     public void setVisible(boolean visible) {
183         mVisible = visible;
184         mIndicationArea.setVisibility(visible ? View.VISIBLE : View.GONE);
185         if (visible) {
186             hideTransientIndication();
187             updateIndication();
188         }
189     }
190 
191     /**
192      * Sets the indication that is shown if nothing else is showing.
193      */
setRestingIndication(String restingIndication)194     public void setRestingIndication(String restingIndication) {
195         mRestingIndication = restingIndication;
196         updateIndication();
197     }
198 
199     /**
200      * Sets the active controller managing changes and callbacks to user information.
201      */
setUserInfoController(UserInfoController userInfoController)202     public void setUserInfoController(UserInfoController userInfoController) {
203     }
204 
205     /**
206      * Hides transient indication in {@param delayMs}.
207      */
hideTransientIndicationDelayed(long delayMs)208     public void hideTransientIndicationDelayed(long delayMs) {
209         mHandler.sendMessageDelayed(
210                 mHandler.obtainMessage(MSG_HIDE_TRANSIENT), delayMs);
211     }
212 
213     /**
214      * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
215      */
showTransientIndication(int transientIndication)216     public void showTransientIndication(int transientIndication) {
217         showTransientIndication(mContext.getResources().getString(transientIndication));
218     }
219 
220     /**
221      * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
222      */
showTransientIndication(String transientIndication)223     public void showTransientIndication(String transientIndication) {
224         showTransientIndication(transientIndication, Color.WHITE);
225     }
226 
227     /**
228      * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
229      */
showTransientIndication(String transientIndication, int textColor)230     public void showTransientIndication(String transientIndication, int textColor) {
231         mTransientIndication = transientIndication;
232         mTransientTextColor = textColor;
233         mHandler.removeMessages(MSG_HIDE_TRANSIENT);
234         if (mDozing && !TextUtils.isEmpty(mTransientIndication)) {
235             // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared.
236             mWakeLock.setAcquired(true);
237             hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
238         }
239         updateIndication();
240     }
241 
242     /**
243      * Hides transient indication.
244      */
hideTransientIndication()245     public void hideTransientIndication() {
246         if (mTransientIndication != null) {
247             mTransientIndication = null;
248             mHandler.removeMessages(MSG_HIDE_TRANSIENT);
249             updateIndication();
250         }
251     }
252 
updateIndication()253     private void updateIndication() {
254         if (TextUtils.isEmpty(mTransientIndication)) {
255             mWakeLock.setAcquired(false);
256         }
257 
258         if (mVisible) {
259             // Walk down a precedence-ordered list of what should indication
260             // should be shown based on user or device state
261             if (mDozing) {
262                 // If we're dozing, never show a persistent indication.
263                 if (!TextUtils.isEmpty(mTransientIndication)) {
264                     mTextView.switchIndication(mTransientIndication);
265                     mTextView.setTextColor(mTransientTextColor);
266 
267                 } else {
268                     mTextView.switchIndication(null);
269                 }
270                 return;
271             }
272 
273             if (!mUserManager.isUserUnlocked(KeyguardUpdateMonitor.getCurrentUser())) {
274                 mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
275                 mTextView.setTextColor(Color.WHITE);
276 
277             } else if (!TextUtils.isEmpty(mTransientIndication)) {
278                 mTextView.switchIndication(mTransientIndication);
279                 mTextView.setTextColor(mTransientTextColor);
280 
281             } else if (mPowerPluggedIn) {
282                 String indication = computePowerIndication();
283                 if (DEBUG_CHARGING_SPEED) {
284                     indication += ",  " + (mChargingWattage / 1000) + " mW";
285                 }
286                 mTextView.switchIndication(indication);
287                 mTextView.setTextColor(Color.WHITE);
288 
289             } else {
290                 mTextView.switchIndication(mRestingIndication);
291                 mTextView.setTextColor(Color.WHITE);
292             }
293         }
294     }
295 
computePowerIndication()296     private String computePowerIndication() {
297         if (mPowerCharged) {
298             return mContext.getResources().getString(R.string.keyguard_charged);
299         }
300 
301         // Try fetching charging time from battery stats.
302         long chargingTimeRemaining = 0;
303         try {
304             chargingTimeRemaining = mBatteryInfo.computeChargeTimeRemaining();
305 
306         } catch (RemoteException e) {
307             Log.e(TAG, "Error calling IBatteryStats: ", e);
308         }
309         final boolean hasChargingTime = chargingTimeRemaining > 0;
310 
311         int chargingId;
312         switch (mChargingSpeed) {
313             case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST:
314                 chargingId = hasChargingTime
315                         ? R.string.keyguard_indication_charging_time_fast
316                         : R.string.keyguard_plugged_in_charging_fast;
317                 break;
318             case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY:
319                 chargingId = hasChargingTime
320                         ? R.string.keyguard_indication_charging_time_slowly
321                         : R.string.keyguard_plugged_in_charging_slowly;
322                 break;
323             default:
324                 chargingId = hasChargingTime
325                         ? R.string.keyguard_indication_charging_time
326                         : R.string.keyguard_plugged_in;
327                 break;
328         }
329 
330         if (hasChargingTime) {
331             String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
332                     mContext, chargingTimeRemaining);
333             return mContext.getResources().getString(chargingId, chargingTimeFormatted);
334         } else {
335             return mContext.getResources().getString(chargingId);
336         }
337     }
338 
setStatusBarKeyguardViewManager( StatusBarKeyguardViewManager statusBarKeyguardViewManager)339     public void setStatusBarKeyguardViewManager(
340             StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
341         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
342     }
343 
344     private final BroadcastReceiver mTickReceiver = new BroadcastReceiver() {
345         @Override
346         public void onReceive(Context context, Intent intent) {
347             mHandler.post(() -> {
348                 if (mVisible) {
349                     updateIndication();
350                 }
351             });
352         }
353     };
354 
355     private final Handler mHandler = new Handler() {
356         @Override
357         public void handleMessage(Message msg) {
358             if (msg.what == MSG_HIDE_TRANSIENT) {
359                 hideTransientIndication();
360             } else if (msg.what == MSG_CLEAR_FP_MSG) {
361                 mLockIcon.setTransientFpError(false);
362                 hideTransientIndication();
363             }
364         }
365     };
366 
setDozing(boolean dozing)367     public void setDozing(boolean dozing) {
368         if (mDozing == dozing) {
369             return;
370         }
371         mDozing = dozing;
372         updateIndication();
373         updateDisclosure();
374     }
375 
376     protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
377         public static final int HIDE_DELAY_MS = 5000;
378         private int mLastSuccessiveErrorMessage = -1;
379 
380         @Override
onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status)381         public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
382             boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
383                     || status.status == BatteryManager.BATTERY_STATUS_FULL;
384             boolean wasPluggedIn = mPowerPluggedIn;
385             mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull;
386             mPowerCharged = status.isCharged();
387             mChargingWattage = status.maxChargingWattage;
388             mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold);
389             updateIndication();
390             if (mDozing) {
391                 if (!wasPluggedIn && mPowerPluggedIn) {
392                     showTransientIndication(computePowerIndication());
393                     hideTransientIndicationDelayed(HIDE_DELAY_MS);
394                 } else if (wasPluggedIn && !mPowerPluggedIn) {
395                     hideTransientIndication();
396                 }
397             }
398         }
399 
400         @Override
onKeyguardVisibilityChanged(boolean showing)401         public void onKeyguardVisibilityChanged(boolean showing) {
402             if (showing) {
403                 updateDisclosure();
404             }
405         }
406 
407         @Override
onFingerprintHelp(int msgId, String helpString)408         public void onFingerprintHelp(int msgId, String helpString) {
409             KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
410             if (!updateMonitor.isUnlockingWithFingerprintAllowed()) {
411                 return;
412             }
413             int errorColor = Utils.getColorError(mContext);
414             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
415                 mStatusBarKeyguardViewManager.showBouncerMessage(helpString, errorColor);
416             } else if (updateMonitor.isDeviceInteractive()
417                     || mDozing && updateMonitor.isScreenOn()) {
418                 mLockIcon.setTransientFpError(true);
419                 showTransientIndication(helpString, errorColor);
420                 mHandler.removeMessages(MSG_CLEAR_FP_MSG);
421                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_FP_MSG),
422                         TRANSIENT_FP_ERROR_TIMEOUT);
423             }
424             // Help messages indicate that there was actually a try since the last error, so those
425             // are not two successive error messages anymore.
426             mLastSuccessiveErrorMessage = -1;
427         }
428 
429         @Override
onFingerprintError(int msgId, String errString)430         public void onFingerprintError(int msgId, String errString) {
431             KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
432             if (!updateMonitor.isUnlockingWithFingerprintAllowed()
433                     || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
434                 return;
435             }
436             int errorColor = Utils.getColorError(mContext);
437             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
438                 // When swiping up right after receiving a fingerprint error, the bouncer calls
439                 // authenticate leading to the same message being shown again on the bouncer.
440                 // We want to avoid this, as it may confuse the user when the message is too
441                 // generic.
442                 if (mLastSuccessiveErrorMessage != msgId) {
443                     mStatusBarKeyguardViewManager.showBouncerMessage(errString, errorColor);
444                 }
445             } else if (updateMonitor.isDeviceInteractive()) {
446                 showTransientIndication(errString, errorColor);
447                 // We want to keep this message around in case the screen was off
448                 hideTransientIndicationDelayed(HIDE_DELAY_MS);
449             } else {
450                 mMessageToShowOnScreenOn = errString;
451             }
452             mLastSuccessiveErrorMessage = msgId;
453         }
454 
455         @Override
onScreenTurnedOn()456         public void onScreenTurnedOn() {
457             if (mMessageToShowOnScreenOn != null) {
458                 int errorColor = Utils.getColorError(mContext);
459                 showTransientIndication(mMessageToShowOnScreenOn, errorColor);
460                 // We want to keep this message around in case the screen was off
461                 hideTransientIndicationDelayed(HIDE_DELAY_MS);
462                 mMessageToShowOnScreenOn = null;
463             }
464         }
465 
466         @Override
onFingerprintRunningStateChanged(boolean running)467         public void onFingerprintRunningStateChanged(boolean running) {
468             if (running) {
469                 mMessageToShowOnScreenOn = null;
470             }
471         }
472 
473         @Override
onFingerprintAuthenticated(int userId)474         public void onFingerprintAuthenticated(int userId) {
475             super.onFingerprintAuthenticated(userId);
476             mLastSuccessiveErrorMessage = -1;
477         }
478 
479         @Override
onFingerprintAuthFailed()480         public void onFingerprintAuthFailed() {
481             super.onFingerprintAuthFailed();
482             mLastSuccessiveErrorMessage = -1;
483         }
484 
485         @Override
onUserUnlocked()486         public void onUserUnlocked() {
487             if (mVisible) {
488                 updateIndication();
489             }
490         }
491     };
492 }
493