1 /* 2 * Copyright (C) 2021 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.server.policy; 18 19 import static android.hardware.biometrics.BiometricStateListener.STATE_BP_AUTH; 20 import static android.hardware.biometrics.BiometricStateListener.STATE_ENROLLING; 21 import static android.hardware.biometrics.BiometricStateListener.STATE_IDLE; 22 import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.pm.PackageManager; 31 import android.hardware.biometrics.BiometricStateListener; 32 import android.hardware.fingerprint.FingerprintManager; 33 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; 34 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; 35 import android.os.Handler; 36 import android.os.PowerManager; 37 import android.util.Log; 38 import android.view.View; 39 import android.view.Window; 40 import android.view.WindowManager; 41 import android.view.accessibility.AccessibilityManager; 42 43 import com.android.internal.R; 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import java.util.List; 47 import java.util.concurrent.atomic.AtomicBoolean; 48 49 /** 50 * Defines behavior for handling interactions between power button events and fingerprint-related 51 * operations, for devices where the fingerprint sensor (side fps) lives on the power button. 52 */ 53 public class SideFpsEventHandler implements View.OnClickListener { 54 55 private static final int DEBOUNCE_DELAY_MILLIS = 500; 56 57 private static final String TAG = "SideFpsEventHandler"; 58 59 @NonNull 60 private final Context mContext; 61 @NonNull 62 private final Handler mHandler; 63 @NonNull 64 private final PowerManager mPowerManager; 65 @NonNull 66 private final AtomicBoolean mSideFpsEventHandlerReady; 67 private final int mDismissDialogTimeout; 68 @Nullable 69 private SideFpsToast mDialog; 70 private final AccessibilityManager mAccessibilityManager; 71 private final Runnable mTurnOffDialog = 72 () -> { 73 dismissDialog("mTurnOffDialog"); 74 }; 75 private @BiometricStateListener.State int mBiometricState; 76 private FingerprintManager mFingerprintManager; 77 private DialogProvider mDialogProvider; 78 private long mLastPowerPressTime; 79 SideFpsEventHandler( Context context, Handler handler, PowerManager powerManager)80 SideFpsEventHandler( 81 Context context, 82 Handler handler, 83 PowerManager powerManager) { 84 this(context, handler, powerManager, (ctx) -> { 85 SideFpsToast dialog = new SideFpsToast(ctx); 86 dialog.getWindow() 87 .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); 88 dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 89 return dialog; 90 }); 91 } 92 93 @VisibleForTesting SideFpsEventHandler( Context context, Handler handler, PowerManager powerManager, DialogProvider provider)94 SideFpsEventHandler( 95 Context context, 96 Handler handler, 97 PowerManager powerManager, 98 DialogProvider provider) { 99 mContext = context; 100 mHandler = handler; 101 mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); 102 mPowerManager = powerManager; 103 mBiometricState = STATE_IDLE; 104 mSideFpsEventHandlerReady = new AtomicBoolean(false); 105 mDialogProvider = provider; 106 // ensure dialog is dismissed if screen goes off for unrelated reasons 107 context.registerReceiver( 108 new BroadcastReceiver() { 109 @Override 110 public void onReceive(Context context, Intent intent) { 111 if (mDialog != null) { 112 mDialog.dismiss(); 113 mDialog = null; 114 } 115 } 116 }, 117 new IntentFilter(Intent.ACTION_SCREEN_OFF)); 118 mDismissDialogTimeout = context.getResources().getInteger( 119 R.integer.config_sideFpsToastTimeout); 120 } 121 122 @Override onClick(View v)123 public void onClick(View v) { 124 goToSleep(mLastPowerPressTime); 125 } 126 127 /** 128 * Called from {@link PhoneWindowManager} to notify FingerprintManager that a single tap power 129 * button has been pressed. 130 */ notifyPowerPressed()131 public void notifyPowerPressed() { 132 Log.i(TAG, "notifyPowerPressed"); 133 if (mFingerprintManager == null && mSideFpsEventHandlerReady.get()) { 134 mFingerprintManager = mContext.getSystemService(FingerprintManager.class); 135 } 136 if (mFingerprintManager == null) { 137 return; 138 } 139 mFingerprintManager.onPowerPressed(); 140 } 141 142 /** 143 * Called from {@link PhoneWindowManager} and will dictate if the SideFpsEventHandler should 144 * handle the power press. 145 * 146 * @param eventTime powerPress event time 147 * @return true if powerPress was consumed, false otherwise 148 */ shouldConsumeSinglePress(long eventTime)149 public boolean shouldConsumeSinglePress(long eventTime) { 150 if (!mSideFpsEventHandlerReady.get()) { 151 return false; 152 } 153 154 switch (mBiometricState) { 155 case STATE_ENROLLING: 156 mHandler.post( 157 () -> { 158 if (mHandler.hasCallbacks(mTurnOffDialog)) { 159 Log.v(TAG, "Detected a tap to turn off dialog, ignoring"); 160 mHandler.removeCallbacks(mTurnOffDialog); 161 } 162 showDialog(eventTime, "Enroll Power Press"); 163 if (!mAccessibilityManager.isEnabled()) { 164 mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout); 165 } 166 }); 167 return true; 168 case STATE_BP_AUTH: 169 return true; 170 case STATE_KEYGUARD_AUTH: 171 default: 172 return false; 173 } 174 } 175 goToSleep(long eventTime)176 private void goToSleep(long eventTime) { 177 mPowerManager.goToSleep( 178 eventTime, 179 PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 180 0 /* flags */); 181 } 182 183 /** 184 * Awaits notification from PhoneWindowManager that fingerprint service is ready to send updates 185 * about power button fps sensor state. Then configures a BiometricStateListener to receive and 186 * record updates to fps state, and registers the BiometricStateListener in FingerprintManager. 187 */ onFingerprintSensorReady()188 public void onFingerprintSensorReady() { 189 final PackageManager pm = mContext.getPackageManager(); 190 if (!pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { 191 return; 192 } 193 194 final FingerprintManager fingerprintManager = 195 mContext.getSystemService(FingerprintManager.class); 196 fingerprintManager.addAuthenticatorsRegisteredCallback( 197 new IFingerprintAuthenticatorsRegisteredCallback.Stub() { 198 @Override 199 public void onAllAuthenticatorsRegistered( 200 List<FingerprintSensorPropertiesInternal> sensors) { 201 if (fingerprintManager.isPowerbuttonFps()) { 202 fingerprintManager.registerBiometricStateListener( 203 new BiometricStateListener() { 204 @Nullable 205 private Runnable mStateRunnable = null; 206 207 @Override 208 public void onStateChanged( 209 @BiometricStateListener.State int newState) { 210 Log.d(TAG, "onStateChanged : " + newState); 211 if (mStateRunnable != null) { 212 mHandler.removeCallbacks(mStateRunnable); 213 mStateRunnable = null; 214 } 215 216 // When the user hits the power button the events can 217 // arrive in any order (success auth & power). Add a 218 // damper when moving to idle in case auth is first 219 if (newState == STATE_IDLE) { 220 mStateRunnable = () -> mBiometricState = newState; 221 // This is also useful in the case of biometric 222 // prompt. 223 // If a user has recently succeeded/failed auth, we 224 // want to disable the power button for a short 225 // period of time (so ethey are able to view the 226 // prompt) 227 mHandler.postDelayed( 228 mStateRunnable, DEBOUNCE_DELAY_MILLIS); 229 dismissDialog("STATE_IDLE"); 230 } else { 231 mBiometricState = newState; 232 } 233 } 234 235 @Override 236 public void onBiometricAction( 237 @BiometricStateListener.Action int action) { 238 Log.d(TAG, "onBiometricAction " + action); 239 if (mAccessibilityManager != null 240 && mAccessibilityManager.isEnabled()) { 241 dismissDialog("mTurnOffDialog"); 242 } 243 } 244 }); 245 mSideFpsEventHandlerReady.set(true); 246 } 247 } 248 }); 249 } 250 dismissDialog(String reason)251 private void dismissDialog(String reason) { 252 Log.d(TAG, "Dismissing dialog with reason: " + reason); 253 if (mDialog != null && mDialog.isShowing()) { 254 mDialog.dismiss(); 255 } 256 } 257 showDialog(long time, String reason)258 private void showDialog(long time, String reason) { 259 Log.d(TAG, "Showing dialog with reason: " + reason); 260 if (mDialog != null && mDialog.isShowing()) { 261 Log.d(TAG, "Ignoring show dialog"); 262 return; 263 } 264 mDialog = mDialogProvider.provideDialog(mContext); 265 mLastPowerPressTime = time; 266 mDialog.show(); 267 mDialog.setOnClickListener(this); 268 if (mAccessibilityManager.isEnabled()) { 269 mDialog.addAccessibilityDelegate(); 270 } 271 } 272 273 interface DialogProvider { provideDialog(Context context)274 SideFpsToast provideDialog(Context context); 275 } 276 }