1 /*
2  * Copyright (C) 2018 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.fingerprint;
18 
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.hardware.biometrics.BiometricPrompt;
22 import android.hardware.biometrics.IBiometricPromptReceiver;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Message;
26 import android.os.RemoteException;
27 import android.util.Log;
28 import android.view.WindowManager;
29 
30 import com.android.internal.os.SomeArgs;
31 import com.android.systemui.SystemUI;
32 import com.android.systemui.statusbar.CommandQueue;
33 
34 public class FingerprintDialogImpl extends SystemUI implements CommandQueue.Callbacks {
35     private static final String TAG = "FingerprintDialogImpl";
36     private static final boolean DEBUG = true;
37 
38     protected static final int MSG_SHOW_DIALOG = 1;
39     protected static final int MSG_FINGERPRINT_AUTHENTICATED = 2;
40     protected static final int MSG_FINGERPRINT_HELP = 3;
41     protected static final int MSG_FINGERPRINT_ERROR = 4;
42     protected static final int MSG_HIDE_DIALOG = 5;
43     protected static final int MSG_BUTTON_NEGATIVE = 6;
44     protected static final int MSG_USER_CANCELED = 7;
45     protected static final int MSG_BUTTON_POSITIVE = 8;
46     protected static final int MSG_CLEAR_MESSAGE = 9;
47 
48 
49     private FingerprintDialogView mDialogView;
50     private WindowManager mWindowManager;
51     private IBiometricPromptReceiver mReceiver;
52     private boolean mDialogShowing;
53 
54     private Handler mHandler = new Handler() {
55         @Override
56         public void handleMessage(Message msg) {
57             switch(msg.what) {
58                 case MSG_SHOW_DIALOG:
59                     handleShowDialog((SomeArgs) msg.obj);
60                     break;
61                 case MSG_FINGERPRINT_AUTHENTICATED:
62                     handleFingerprintAuthenticated();
63                     break;
64                 case MSG_FINGERPRINT_HELP:
65                     handleFingerprintHelp((String) msg.obj);
66                     break;
67                 case MSG_FINGERPRINT_ERROR:
68                     handleFingerprintError((String) msg.obj);
69                     break;
70                 case MSG_HIDE_DIALOG:
71                     handleHideDialog((Boolean) msg.obj);
72                     break;
73                 case MSG_BUTTON_NEGATIVE:
74                     handleButtonNegative();
75                     break;
76                 case MSG_USER_CANCELED:
77                     handleUserCanceled();
78                     break;
79                 case MSG_BUTTON_POSITIVE:
80                     handleButtonPositive();
81                     break;
82                 case MSG_CLEAR_MESSAGE:
83                     handleClearMessage();
84                     break;
85             }
86         }
87     };
88 
89     @Override
start()90     public void start() {
91         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
92             return;
93         }
94         getComponent(CommandQueue.class).addCallbacks(this);
95         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
96         mDialogView = new FingerprintDialogView(mContext, mHandler);
97     }
98 
99     @Override
showFingerprintDialog(Bundle bundle, IBiometricPromptReceiver receiver)100     public void showFingerprintDialog(Bundle bundle, IBiometricPromptReceiver receiver) {
101         if (DEBUG) Log.d(TAG, "showFingerprintDialog");
102         // Remove these messages as they are part of the previous client
103         mHandler.removeMessages(MSG_FINGERPRINT_ERROR);
104         mHandler.removeMessages(MSG_FINGERPRINT_HELP);
105         mHandler.removeMessages(MSG_FINGERPRINT_AUTHENTICATED);
106         SomeArgs args = SomeArgs.obtain();
107         args.arg1 = bundle;
108         args.arg2 = receiver;
109         mHandler.obtainMessage(MSG_SHOW_DIALOG, args).sendToTarget();
110     }
111 
112     @Override
onFingerprintAuthenticated()113     public void onFingerprintAuthenticated() {
114         if (DEBUG) Log.d(TAG, "onFingerprintAuthenticated");
115         mHandler.obtainMessage(MSG_FINGERPRINT_AUTHENTICATED).sendToTarget();
116     }
117 
118     @Override
onFingerprintHelp(String message)119     public void onFingerprintHelp(String message) {
120         if (DEBUG) Log.d(TAG, "onFingerprintHelp: " + message);
121         mHandler.obtainMessage(MSG_FINGERPRINT_HELP, message).sendToTarget();
122     }
123 
124     @Override
onFingerprintError(String error)125     public void onFingerprintError(String error) {
126         if (DEBUG) Log.d(TAG, "onFingerprintError: " + error);
127         mHandler.obtainMessage(MSG_FINGERPRINT_ERROR, error).sendToTarget();
128     }
129 
130     @Override
hideFingerprintDialog()131     public void hideFingerprintDialog() {
132         if (DEBUG) Log.d(TAG, "hideFingerprintDialog");
133         mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
134     }
135 
handleShowDialog(SomeArgs args)136     private void handleShowDialog(SomeArgs args) {
137         if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: "
138                 + mDialogView.isAnimatingAway());
139         if (mDialogView.isAnimatingAway()) {
140             mDialogView.forceRemove();
141         } else if (mDialogShowing) {
142             Log.w(TAG, "Dialog already showing");
143             return;
144         }
145         mReceiver = (IBiometricPromptReceiver) args.arg2;
146         mDialogView.setBundle((Bundle)args.arg1);
147         mWindowManager.addView(mDialogView, mDialogView.getLayoutParams());
148         mDialogShowing = true;
149     }
150 
handleFingerprintAuthenticated()151     private void handleFingerprintAuthenticated() {
152         if (DEBUG) Log.d(TAG, "handleFingerprintAuthenticated");
153         mDialogView.announceForAccessibility(
154                 mContext.getResources().getText(
155                         com.android.internal.R.string.fingerprint_authenticated));
156         handleHideDialog(false /* userCanceled */);
157     }
158 
handleFingerprintHelp(String message)159     private void handleFingerprintHelp(String message) {
160         if (DEBUG) Log.d(TAG, "handleFingerprintHelp: " + message);
161         mDialogView.showHelpMessage(message);
162     }
163 
handleFingerprintError(String error)164     private void handleFingerprintError(String error) {
165         if (DEBUG) Log.d(TAG, "handleFingerprintError: " + error);
166         if (!mDialogShowing) {
167             if (DEBUG) Log.d(TAG, "Dialog already dismissed");
168             return;
169         }
170         mDialogView.showErrorMessage(error);
171     }
172 
handleHideDialog(boolean userCanceled)173     private void handleHideDialog(boolean userCanceled) {
174         if (DEBUG) Log.d(TAG, "handleHideDialog, userCanceled: " + userCanceled);
175         if (!mDialogShowing) {
176             // This can happen if there's a race and we get called from both
177             // onAuthenticated and onError, etc.
178             Log.w(TAG, "Dialog already dismissed, userCanceled: " + userCanceled);
179             return;
180         }
181         if (userCanceled) {
182             try {
183                 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
184             } catch (RemoteException e) {
185                 Log.e(TAG, "RemoteException when hiding dialog", e);
186             }
187         }
188         mReceiver = null;
189         mDialogShowing = false;
190         mDialogView.startDismiss();
191     }
192 
handleButtonNegative()193     private void handleButtonNegative() {
194         if (mReceiver == null) {
195             Log.e(TAG, "Receiver is null");
196             return;
197         }
198         try {
199             mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
200         } catch (RemoteException e) {
201             Log.e(TAG, "Remote exception when handling negative button", e);
202         }
203         handleHideDialog(false /* userCanceled */);
204     }
205 
handleButtonPositive()206     private void handleButtonPositive() {
207         if (mReceiver == null) {
208             Log.e(TAG, "Receiver is null");
209             return;
210         }
211         try {
212             mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_POSITIVE);
213         } catch (RemoteException e) {
214             Log.e(TAG, "Remote exception when handling positive button", e);
215         }
216         handleHideDialog(false /* userCanceled */);
217     }
218 
handleClearMessage()219     private void handleClearMessage() {
220         mDialogView.resetMessage();
221     }
222 
handleUserCanceled()223     private void handleUserCanceled() {
224         handleHideDialog(true /* userCanceled */);
225     }
226 }
227