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.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      */
    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         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 
    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      */
    155     protected KeyguardUpdateMonitorCallback getKeyguardCallback() {
    156         if (mUpdateMonitor == null) {
    157             mUpdateMonitor = new BaseKeyguardCallback();
    158         }
    159         return mUpdateMonitor;
    160     }
    161 
    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 
    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      */
    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      */
    202     public void setUserInfoController(UserInfoController userInfoController) {
    203     }
    204 
    205     /**
    206      * Hides transient indication in {@param delayMs}.
    207      */
    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      */
    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      */
    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      */
    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      */
    245     public void hideTransientIndication() {
    246         if (mTransientIndication != null) {
    247             mTransientIndication = null;
    248             mHandler.removeMessages(MSG_HIDE_TRANSIENT);
    249             updateIndication();
    250         }
    251     }
    252 
    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 
    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 
    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 
    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
    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
    401         public void onKeyguardVisibilityChanged(boolean showing) {
    402             if (showing) {
    403                 updateDisclosure();
    404             }
    405         }
    406 
    407         @Override
    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
    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
    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
    467         public void onFingerprintRunningStateChanged(boolean running) {
    468             if (running) {
    469                 mMessageToShowOnScreenOn = null;
    470             }
    471         }
    472 
    473         @Override
    474         public void onFingerprintAuthenticated(int userId) {
    475             super.onFingerprintAuthenticated(userId);
    476             mLastSuccessiveErrorMessage = -1;
    477         }
    478 
    479         @Override
    480         public void onFingerprintAuthFailed() {
    481             super.onFingerprintAuthFailed();
    482             mLastSuccessiveErrorMessage = -1;
    483         }
    484 
    485         @Override
    486         public void onUserUnlocked() {
    487             if (mVisible) {
    488                 updateIndication();
    489             }
    490         }
    491     };
    492 }
    493