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