1 /*
2  * Copyright (C) 2020 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.biometrics.sensors;
18 
19 import static com.android.internal.annotations.VisibleForTesting.Visibility;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.hardware.biometrics.BiometricsProtoEnums;
25 import android.os.IBinder;
26 import android.os.RemoteException;
27 import android.util.Slog;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.NoSuchElementException;
34 
35 /**
36  * Abstract base class for keeping track and dispatching events from the biometric's HAL to the
37  * the current client.  Subclasses are responsible for coordinating the interaction with
38  * the biometric's HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.).
39  */
40 public abstract class BaseClientMonitor extends LoggableMonitor
41         implements IBinder.DeathRecipient {
42 
43     private static final String TAG = "Biometrics/ClientMonitor";
44     protected static final boolean DEBUG = true;
45 
46     // Counter used to distinguish between ClientMonitor instances to help debugging.
47     private static int sCount = 0;
48 
49     /**
50      * Interface that ClientMonitor holders should use to receive callbacks.
51      */
52     public interface Callback {
53         /**
54          * Invoked when the ClientMonitor operation has been started (e.g. reached the head of
55          * the queue and becomes the current operation).
56          *
57          * @param clientMonitor Reference of the ClientMonitor that is starting.
58          */
onClientStarted(@onNull BaseClientMonitor clientMonitor)59         default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
60         }
61 
62         /**
63          * Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous
64          * (i.e. Authenticate, Enroll, Enumerate, Remove) and synchronous (i.e. generateChallenge,
65          * revokeChallenge) so that a scheduler can process ClientMonitors regardless of their
66          * implementation.
67          *
68          * @param clientMonitor Reference of the ClientMonitor that finished.
69          * @param success True if the operation completed successfully.
70          */
onClientFinished(@onNull BaseClientMonitor clientMonitor, boolean success)71         default void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
72         }
73     }
74 
75     /** Holder for wrapping multiple handlers into a single Callback. */
76     public static class CompositeCallback implements Callback {
77         @NonNull
78         private final List<Callback> mCallbacks;
79 
CompositeCallback(@onNull Callback... callbacks)80         public CompositeCallback(@NonNull Callback... callbacks) {
81             mCallbacks = new ArrayList<>();
82 
83             for (Callback callback : callbacks) {
84                 if (callback != null) {
85                     mCallbacks.add(callback);
86                 }
87             }
88         }
89 
90         @Override
onClientStarted(@onNull BaseClientMonitor clientMonitor)91         public final void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
92             for (int i = 0; i < mCallbacks.size(); i++) {
93                 mCallbacks.get(i).onClientStarted(clientMonitor);
94             }
95         }
96 
97         @Override
onClientFinished(@onNull BaseClientMonitor clientMonitor, boolean success)98         public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
99                 boolean success) {
100             for (int i = mCallbacks.size() - 1; i >= 0; i--) {
101                 mCallbacks.get(i).onClientFinished(clientMonitor, success);
102             }
103         }
104     }
105 
106     private final int mSequentialId;
107     @NonNull private final Context mContext;
108     private final int mTargetUserId;
109     @NonNull private final String mOwner;
110     private final int mSensorId; // sensorId as configured by the framework
111 
112     @Nullable private IBinder mToken;
113     private long mRequestId;
114     @Nullable private ClientMonitorCallbackConverter mListener;
115     // Currently only used for authentication client. The cookie generated by BiometricService
116     // is never 0.
117     private final int mCookie;
118     private boolean mAlreadyDone = false;
119 
120     // Use an empty callback by default since delayed operations can receive events
121     // before they are started and cause NPE in subclasses that access this field directly.
122     @NonNull protected Callback mCallback = new Callback() {
123         @Override
124         public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
125             Slog.e(TAG, "mCallback onClientStarted: called before set (should not happen)");
126         }
127 
128         @Override
129         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
130                 boolean success) {
131             Slog.e(TAG, "mCallback onClientFinished: called before set (should not happen)");
132         }
133     };
134 
135     /**
136      * @return A ClientMonitorEnum constant defined in biometrics.proto
137      */
getProtoEnum()138     public abstract int getProtoEnum();
139 
140     /**
141      * @return True if the ClientMonitor should cancel any current and pending interruptable clients
142      */
interruptsPrecedingClients()143     public boolean interruptsPrecedingClients() {
144         return false;
145     }
146 
147     /**
148      * @param context    system_server context
149      * @param token      a unique token for the client
150      * @param listener   recipient of related events (e.g. authentication)
151      * @param userId     target user id for operation
152      * @param owner      name of the client that owns this
153      * @param cookie     BiometricPrompt authentication cookie (to be moved into a subclass soon)
154      * @param sensorId   ID of the sensor that the operation should be requested of
155      * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants
156      * @param statsAction   One of {@link BiometricsProtoEnums} ACTION_* constants
157      * @param statsClient   One of {@link BiometricsProtoEnums} CLIENT_* constants
158      */
BaseClientMonitor(@onNull Context context, @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction, int statsClient)159     public BaseClientMonitor(@NonNull Context context,
160             @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
161             @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
162             int statsClient) {
163         super(context, statsModality, statsAction, statsClient);
164         mSequentialId = sCount++;
165         mContext = context;
166         mToken = token;
167         mRequestId = -1;
168         mListener = listener;
169         mTargetUserId = userId;
170         mOwner = owner;
171         mCookie = cookie;
172         mSensorId = sensorId;
173 
174         try {
175             if (token != null) {
176                 token.linkToDeath(this, 0);
177             }
178         } catch (RemoteException e) {
179             Slog.w(TAG, "caught remote exception in linkToDeath: ", e);
180         }
181     }
182 
getCookie()183     public int getCookie() {
184         return mCookie;
185     }
186 
187     /**
188      * Starts the ClientMonitor's lifecycle.
189      * @param callback invoked when the operation is complete (succeeds, fails, etc)
190      */
start(@onNull Callback callback)191     public void start(@NonNull Callback callback) {
192         mCallback = wrapCallbackForStart(callback);
193         mCallback.onClientStarted(this);
194     }
195 
196     /**
197      * Called during start to provide subclasses a hook for decorating the callback.
198      *
199      * Returns the original callback unless overridden.
200      */
201     @NonNull
wrapCallbackForStart(@onNull Callback callback)202     protected Callback wrapCallbackForStart(@NonNull Callback callback) {
203         return callback;
204     }
205 
206     /** Signals this operation has completed its lifecycle and should no longer be used. */
207     @VisibleForTesting(visibility = Visibility.PACKAGE)
destroy()208     public void destroy() {
209         mAlreadyDone = true;
210         if (mToken != null) {
211             try {
212                 mToken.unlinkToDeath(this, 0);
213             } catch (NoSuchElementException e) {
214                 // TODO: remove when duplicate call bug is found
215                 Slog.e(TAG, "destroy(): " + this + ":", new Exception("here"));
216             }
217             mToken = null;
218         }
219     }
220 
221     /**
222      * Call while the operation is still active, but nearly done, to prevent any action
223      * upon client death (only needed for authentication clients).
224      */
markAlreadyDone()225     void markAlreadyDone() {
226         Slog.d(TAG, "marking operation as done: " + this);
227         mAlreadyDone = true;
228     }
229 
230     /** If this operation has been marked as completely done (or cancelled). */
isAlreadyDone()231     public boolean isAlreadyDone() {
232         return mAlreadyDone;
233     }
234 
235     @Override
binderDied()236     public void binderDied() {
237         binderDiedInternal(true /* clearListener */);
238     }
239 
240     // TODO(b/157790417): Move this to the scheduler
binderDiedInternal(boolean clearListener)241     void binderDiedInternal(boolean clearListener) {
242         Slog.e(TAG, "Binder died, operation: " + this);
243 
244         if (mAlreadyDone) {
245             Slog.w(TAG, "Binder died but client is finished, ignoring");
246             return;
247         }
248 
249         // If the current client dies we should cancel the current operation.
250         if (this instanceof Interruptable) {
251             Slog.e(TAG, "Binder died, cancelling client");
252             ((Interruptable) this).cancel();
253         }
254         mToken = null;
255         if (clearListener) {
256             mListener = null;
257         }
258     }
259 
getContext()260     public final Context getContext() {
261         return mContext;
262     }
263 
getOwnerString()264     public final String getOwnerString() {
265         return mOwner;
266     }
267 
getListener()268     public final ClientMonitorCallbackConverter getListener() {
269         return mListener;
270     }
271 
getTargetUserId()272     public int getTargetUserId() {
273         return mTargetUserId;
274     }
275 
getToken()276     public final IBinder getToken() {
277         return mToken;
278     }
279 
getSensorId()280     public int getSensorId() {
281         return mSensorId;
282     }
283 
284     /** Unique request id. */
getRequestId()285     public final long getRequestId() {
286         return mRequestId;
287     }
288 
289     /** If a unique id has been set via {@link #setRequestId(long)} */
hasRequestId()290     public final boolean hasRequestId() {
291         return mRequestId > 0;
292     }
293 
294     /**
295      * A unique identifier used to tie this operation to a request (i.e an API invocation).
296      *
297      * Subclasses should not call this method if this operation does not have a direct
298      * correspondence to a request and {@link #hasRequestId()} will return false.
299      */
setRequestId(long id)300     protected final void setRequestId(long id) {
301         if (id <= 0) {
302             throw new IllegalArgumentException("request id must be positive");
303         }
304         mRequestId = id;
305     }
306 
307     @VisibleForTesting
getCallback()308     public Callback getCallback() {
309         return mCallback;
310     }
311 
312     @Override
toString()313     public String toString() {
314         return "{[" + mSequentialId + "] "
315                 + this.getClass().getName()
316                 + ", proto=" + getProtoEnum()
317                 + ", owner=" + getOwnerString()
318                 + ", cookie=" + getCookie()
319                 + ", requestId=" + getRequestId()
320                 + ", userId=" + getTargetUserId() + "}";
321     }
322 }
323