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