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