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 }