| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.biometrics.sensors.face.aidl; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.ActivityManager; |
| import android.app.ActivityTaskManager; |
| import android.app.TaskStackListener; |
| import android.content.Context; |
| import android.content.pm.UserInfo; |
| import android.hardware.biometrics.IInvalidationCallback; |
| import android.hardware.biometrics.ITestSession; |
| import android.hardware.biometrics.face.IFace; |
| import android.hardware.biometrics.face.SensorProps; |
| import android.hardware.face.Face; |
| import android.hardware.face.FaceSensorPropertiesInternal; |
| import android.hardware.face.IFaceServiceReceiver; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.NativeHandle; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.UserManager; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.util.proto.ProtoOutputStream; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.server.biometrics.Utils; |
| import com.android.server.biometrics.sensors.AuthenticationClient; |
| import com.android.server.biometrics.sensors.BaseClientMonitor; |
| import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; |
| import com.android.server.biometrics.sensors.HalClientMonitor; |
| import com.android.server.biometrics.sensors.InvalidationRequesterClient; |
| import com.android.server.biometrics.sensors.LockoutResetDispatcher; |
| import com.android.server.biometrics.sensors.PerformanceTracker; |
| import com.android.server.biometrics.sensors.face.FaceUtils; |
| import com.android.server.biometrics.sensors.face.ServiceProvider; |
| import com.android.server.biometrics.sensors.face.UsageStats; |
| |
| import org.json.JSONArray; |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Provider for a single instance of the {@link IFace} HAL. |
| */ |
| public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { |
| private static final int ENROLL_TIMEOUT_SEC = 75; |
| |
| private boolean mTestHalEnabled; |
| |
| @NonNull private final Context mContext; |
| @NonNull private final String mHalInstanceName; |
| @NonNull @VisibleForTesting |
| final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports |
| @NonNull private final HalClientMonitor.LazyDaemon<IFace> mLazyDaemon; |
| @NonNull private final Handler mHandler; |
| @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher; |
| @NonNull private final UsageStats mUsageStats; |
| @NonNull private final ActivityTaskManager mActivityTaskManager; |
| @NonNull private final BiometricTaskStackListener mTaskStackListener; |
| |
| @Nullable private IFace mDaemon; |
| |
| private final class BiometricTaskStackListener extends TaskStackListener { |
| @Override |
| public void onTaskStackChanged() { |
| mHandler.post(() -> { |
| for (int i = 0; i < mSensors.size(); i++) { |
| final BaseClientMonitor client = mSensors.valueAt(i).getScheduler() |
| .getCurrentClient(); |
| if (!(client instanceof AuthenticationClient)) { |
| Slog.e(getTag(), "Task stack changed for client: " + client); |
| continue; |
| } |
| if (Utils.isKeyguard(mContext, client.getOwnerString())) { |
| continue; // Keyguard is always allowed |
| } |
| |
| final List<ActivityManager.RunningTaskInfo> runningTasks = |
| mActivityTaskManager.getTasks(1); |
| if (!runningTasks.isEmpty()) { |
| final String topPackage = |
| runningTasks.get(0).topActivity.getPackageName(); |
| if (!topPackage.contentEquals(client.getOwnerString()) |
| && !client.isAlreadyDone()) { |
| Slog.e(getTag(), "Stopping background authentication, top: " |
| + topPackage + " currentClient: " + client); |
| mSensors.valueAt(i).getScheduler() |
| .cancelAuthentication(client.getToken()); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| public FaceProvider(@NonNull Context context, @NonNull SensorProps[] props, |
| @NonNull String halInstanceName, |
| @NonNull LockoutResetDispatcher lockoutResetDispatcher) { |
| mContext = context; |
| mHalInstanceName = halInstanceName; |
| mSensors = new SparseArray<>(); |
| mLazyDaemon = this::getHalInstance; |
| mHandler = new Handler(Looper.getMainLooper()); |
| mUsageStats = new UsageStats(context); |
| mLockoutResetDispatcher = lockoutResetDispatcher; |
| mActivityTaskManager = ActivityTaskManager.getInstance(); |
| mTaskStackListener = new BiometricTaskStackListener(); |
| |
| for (SensorProps prop : props) { |
| final int sensorId = prop.commonProps.sensorId; |
| |
| final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal( |
| prop.commonProps.sensorId, prop.commonProps.sensorStrength, |
| prop.commonProps.maxEnrollmentsPerUser, false /* supportsFaceDetection */, |
| prop.halControlsPreview); |
| final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler, |
| internalProp); |
| |
| mSensors.put(sensorId, sensor); |
| Slog.d(getTag(), "Added: " + internalProp); |
| } |
| } |
| |
| private String getTag() { |
| return "FaceProvider/" + mHalInstanceName; |
| } |
| |
| @Nullable |
| private synchronized IFace getHalInstance() { |
| if (mTestHalEnabled) { |
| return new TestHal(); |
| } |
| |
| if (mDaemon != null) { |
| return mDaemon; |
| } |
| |
| Slog.d(getTag(), "Daemon was null, reconnecting"); |
| |
| mDaemon = IFace.Stub.asInterface( |
| ServiceManager.waitForDeclaredService(IFace.DESCRIPTOR + "/" + mHalInstanceName)); |
| if (mDaemon == null) { |
| Slog.e(getTag(), "Unable to get daemon"); |
| return null; |
| } |
| |
| try { |
| mDaemon.asBinder().linkToDeath(this, 0 /* flags */); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Unable to linkToDeath", e); |
| } |
| |
| for (int i = 0; i < mSensors.size(); i++) { |
| final int sensorId = mSensors.keyAt(i); |
| scheduleLoadAuthenticatorIds(sensorId); |
| scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser()); |
| } |
| |
| return mDaemon; |
| } |
| |
| private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client) { |
| if (!mSensors.contains(sensorId)) { |
| throw new IllegalStateException("Unable to schedule client: " + client |
| + " for sensor: " + sensorId); |
| } |
| mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client); |
| } |
| |
| private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client, |
| BaseClientMonitor.Callback callback) { |
| if (!mSensors.contains(sensorId)) { |
| throw new IllegalStateException("Unable to schedule client: " + client |
| + " for sensor: " + sensorId); |
| } |
| mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback); |
| } |
| |
| private void createNewSessionWithoutHandler(@NonNull IFace daemon, int sensorId, |
| int userId) throws RemoteException { |
| // Note that per IFace createSession contract, this method will block until all |
| // existing operations are canceled/finished. However, also note that this is fine, since |
| // this method "withoutHandler" means it should only ever be invoked from the worker thread, |
| // so callers will never be blocked. |
| mSensors.get(sensorId).createNewSession(daemon, sensorId, userId); |
| |
| if (FaceUtils.getInstance(sensorId).isInvalidationInProgress(mContext, userId)) { |
| Slog.w(getTag(), "Scheduling unfinished invalidation request for sensor: " + sensorId |
| + ", user: " + userId); |
| scheduleInvalidationRequest(sensorId, userId); |
| } |
| } |
| |
| |
| private void scheduleLoadAuthenticatorIds(int sensorId) { |
| for (UserInfo user : UserManager.get(mContext).getAliveUsers()) { |
| scheduleLoadAuthenticatorIdsForUser(sensorId, user.id); |
| } |
| } |
| |
| private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) { |
| mHandler.post(() -> { |
| final IFace daemon = getHalInstance(); |
| if (daemon == null) { |
| Slog.e(getTag(), "Null daemon during loadAuthenticatorIds, sensorId: " + sensorId); |
| return; |
| } |
| |
| try { |
| if (!mSensors.get(sensorId).hasSessionForUser(userId)) { |
| createNewSessionWithoutHandler(daemon, sensorId, userId); |
| } |
| |
| final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient( |
| mContext, mSensors.get(sensorId).getLazySession(), userId, |
| mContext.getOpPackageName(), sensorId, |
| mSensors.get(sensorId).getAuthenticatorIds()); |
| |
| mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId" |
| + ", sensorId: " + sensorId |
| + ", userId: " + userId, e); |
| } |
| }); |
| } |
| |
| private void scheduleInvalidationRequest(int sensorId, int userId) { |
| mHandler.post(() -> { |
| final InvalidationRequesterClient<Face> client = |
| new InvalidationRequesterClient<>(mContext, userId, sensorId, |
| FaceUtils.getInstance(sensorId)); |
| mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client); |
| }); |
| } |
| |
| @Override |
| public boolean containsSensor(int sensorId) { |
| return mSensors.contains(sensorId); |
| } |
| |
| @NonNull |
| @Override |
| public List<FaceSensorPropertiesInternal> getSensorProperties() { |
| final List<FaceSensorPropertiesInternal> props = new ArrayList<>(); |
| for (int i = 0; i < mSensors.size(); ++i) { |
| props.add(mSensors.valueAt(i).getSensorProperties()); |
| } |
| return props; |
| } |
| |
| @NonNull |
| @Override |
| public FaceSensorPropertiesInternal getSensorProperties(int sensorId) { |
| return mSensors.get(sensorId).getSensorProperties(); |
| } |
| |
| @NonNull |
| @Override |
| public List<Face> getEnrolledFaces(int sensorId, int userId) { |
| return FaceUtils.getInstance(sensorId).getBiometricsForUser(mContext, userId); |
| } |
| |
| @Override |
| public void scheduleInvalidateAuthenticatorId(int sensorId, int userId, |
| @NonNull IInvalidationCallback callback) { |
| mHandler.post(() -> { |
| final IFace daemon = getHalInstance(); |
| if (daemon == null) { |
| Slog.e(getTag(), "Null daemon during scheduleInvalidateAuthenticatorId: " |
| + sensorId); |
| return; |
| } |
| |
| try { |
| if (!mSensors.get(sensorId).hasSessionForUser(userId)) { |
| createNewSessionWithoutHandler(daemon, sensorId, userId); |
| } |
| |
| final FaceInvalidationClient client = new FaceInvalidationClient(mContext, |
| mSensors.get(sensorId).getLazySession(), userId, sensorId, |
| mSensors.get(sensorId).getAuthenticatorIds(), callback); |
| mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Remote exception", e); |
| } |
| }); |
| } |
| |
| @Override |
| public int getLockoutModeForUser(int sensorId, int userId) { |
| return mSensors.get(sensorId).getLockoutCache().getLockoutModeForUser(userId); |
| } |
| |
| @Override |
| public long getAuthenticatorId(int sensorId, int userId) { |
| return mSensors.get(sensorId).getAuthenticatorIds().getOrDefault(userId, 0L); |
| } |
| |
| @Override |
| public boolean isHardwareDetected(int sensorId) { |
| return getHalInstance() != null; |
| } |
| |
| @Override |
| public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token, |
| @NonNull IFaceServiceReceiver receiver, String opPackageName) { |
| mHandler.post(() -> { |
| final IFace daemon = getHalInstance(); |
| if (daemon == null) { |
| Slog.e(getTag(), "Null daemon during generateChallenge, sensorId: " + sensorId); |
| return; |
| } |
| |
| try { |
| if (!mSensors.get(sensorId).hasSessionForUser(userId)) { |
| createNewSessionWithoutHandler(daemon, sensorId, userId); |
| } |
| |
| final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, |
| mSensors.get(sensorId).getLazySession(), token, |
| new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId); |
| |
| scheduleForSensor(sensorId, client); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Remote exception when scheduling generateChallenge", e); |
| } |
| }); |
| } |
| |
| @Override |
| public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, |
| @NonNull String opPackageName, long challenge) { |
| mHandler.post(() -> { |
| final IFace daemon = getHalInstance(); |
| if (daemon == null) { |
| Slog.e(getTag(), "Null daemon during revokeChallenge, sensorId: " + sensorId); |
| return; |
| } |
| |
| try { |
| if (!mSensors.get(sensorId).hasSessionForUser(userId)) { |
| createNewSessionWithoutHandler(daemon, sensorId, userId); |
| } |
| |
| final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, |
| mSensors.get(sensorId).getLazySession(), token, opPackageName, sensorId, |
| challenge); |
| |
| scheduleForSensor(sensorId, client); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Remote exception when scheduling revokeChallenge", e); |
| } |
| }); |
| } |
| |
| @Override |
| public void scheduleEnroll(int sensorId, @NonNull IBinder token, |
| @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, |
| @NonNull String opPackageName, @NonNull int[] disabledFeatures, |
| @Nullable NativeHandle previewSurface, boolean debugConsent) { |
| mHandler.post(() -> { |
| final IFace daemon = getHalInstance(); |
| if (daemon == null) { |
| Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId); |
| // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to |
| // this operation. We should not send the callback yet, since the scheduler may |
| // be processing something else. |
| return; |
| } |
| |
| try { |
| if (!mSensors.get(sensorId).hasSessionForUser(userId)) { |
| createNewSessionWithoutHandler(daemon, sensorId, userId); |
| } |
| |
| final int maxTemplatesPerUser = mSensors.get( |
| sensorId).getSensorProperties().maxEnrollmentsPerUser; |
| final FaceEnrollClient client = new FaceEnrollClient(mContext, |
| mSensors.get(sensorId).getLazySession(), token, |
| new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, |
| opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures, |
| ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser, |
| debugConsent); |
| scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() { |
| @Override |
| public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, |
| boolean success) { |
| if (success) { |
| scheduleLoadAuthenticatorIdsForUser(sensorId, userId); |
| scheduleInvalidationRequest(sensorId, userId); |
| } |
| } |
| }); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Remote exception when scheduling enroll", e); |
| } |
| }); |
| } |
| |
| @Override |
| public void cancelEnrollment(int sensorId, @NonNull IBinder token) { |
| mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token)); |
| } |
| |
| @Override |
| public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, |
| int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback, |
| @NonNull String opPackageName, boolean restricted, int statsClient, |
| boolean isKeyguard) { |
| mHandler.post(() -> { |
| final IFace daemon = getHalInstance(); |
| if (daemon == null) { |
| Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId); |
| // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to |
| // this operation. We should not send the callback yet, since the scheduler may |
| // be processing something else. |
| return; |
| } |
| |
| try { |
| if (!mSensors.get(sensorId).hasSessionForUser(userId)) { |
| createNewSessionWithoutHandler(daemon, sensorId, userId); |
| } |
| |
| final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); |
| final FaceAuthenticationClient client = new FaceAuthenticationClient( |
| mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId, |
| operationId, restricted, opPackageName, cookie, |
| false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient, |
| mUsageStats, mSensors.get(sensorId).getLockoutCache(), isKeyguard); |
| mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Remote exception when scheduling authenticate", e); |
| } |
| }); |
| } |
| |
| @Override |
| public void cancelAuthentication(int sensorId, @NonNull IBinder token) { |
| mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelAuthentication(token)); |
| } |
| |
| @Override |
| public void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId, |
| @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { |
| scheduleRemoveSpecifiedIds(sensorId, token, new int[] {faceId}, userId, receiver, |
| opPackageName); |
| } |
| |
| @Override |
| public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId, |
| @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { |
| final List<Face> faces = FaceUtils.getInstance(sensorId) |
| .getBiometricsForUser(mContext, userId); |
| final int[] faceIds = new int[faces.size()]; |
| for (int i = 0; i < faces.size(); i++) { |
| faceIds[i] = faces.get(i).getBiometricId(); |
| } |
| |
| scheduleRemoveSpecifiedIds(sensorId, token, faceIds, userId, receiver, opPackageName); |
| } |
| |
| private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds, |
| int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { |
| mHandler.post(() -> { |
| final IFace daemon = getHalInstance(); |
| if (daemon == null) { |
| Slog.e(getTag(), "Null daemon during remove, sensorId: " + sensorId); |
| // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to |
| // this operation. We should not send the callback yet, since the scheduler may |
| // be processing something else. |
| return; |
| } |
| |
| try { |
| if (!mSensors.get(sensorId).hasSessionForUser(userId)) { |
| createNewSessionWithoutHandler(daemon, sensorId, userId); |
| } |
| |
| final FaceRemovalClient client = new FaceRemovalClient(mContext, |
| mSensors.get(sensorId).getLazySession(), token, |
| new ClientMonitorCallbackConverter(receiver), faceIds, userId, |
| opPackageName, FaceUtils.getInstance(sensorId), sensorId, |
| mSensors.get(sensorId).getAuthenticatorIds()); |
| |
| mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Remote exception when scheduling remove", e); |
| } |
| }); |
| } |
| |
| @Override |
| public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) { |
| mHandler.post(() -> { |
| final IFace daemon = getHalInstance(); |
| if (daemon == null) { |
| Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId); |
| return; |
| } |
| |
| try { |
| if (!mSensors.get(sensorId).hasSessionForUser(userId)) { |
| createNewSessionWithoutHandler(daemon, sensorId, userId); |
| } |
| |
| final FaceResetLockoutClient client = new FaceResetLockoutClient( |
| mContext, mSensors.get(sensorId).getLazySession(), userId, |
| mContext.getOpPackageName(), sensorId, hardwareAuthToken, |
| mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher); |
| |
| scheduleForSensor(sensorId, client); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Remote exception when scheduling resetLockout", e); |
| } |
| }); |
| } |
| |
| @Override |
| public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature, |
| boolean enabled, @NonNull byte[] hardwareAuthToken, |
| @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { |
| // TODO(b/171335732): implement this. |
| } |
| |
| @Override |
| public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature, |
| @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) { |
| // TODO(b/171335732): implement this. |
| } |
| |
| @Override |
| public void startPreparedClient(int sensorId, int cookie) { |
| mHandler.post(() -> { |
| mSensors.get(sensorId).getScheduler().startPreparedClient(cookie); |
| }); |
| } |
| |
| @Override |
| public void scheduleInternalCleanup(int sensorId, int userId) { |
| mHandler.post(() -> { |
| final IFace daemon = getHalInstance(); |
| if (daemon == null) { |
| Slog.e(getTag(), "Null daemon during internal cleanup, sensorId: " + sensorId); |
| return; |
| } |
| |
| try { |
| if (!mSensors.get(sensorId).hasSessionForUser(userId)) { |
| createNewSessionWithoutHandler(daemon, sensorId, userId); |
| } |
| |
| final List<Face> enrolledList = getEnrolledFaces(sensorId, userId); |
| final FaceInternalCleanupClient client = |
| new FaceInternalCleanupClient(mContext, |
| mSensors.get(sensorId).getLazySession(), userId, |
| mContext.getOpPackageName(), sensorId, enrolledList, |
| FaceUtils.getInstance(sensorId), |
| mSensors.get(sensorId).getAuthenticatorIds()); |
| |
| mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client); |
| } catch (RemoteException e) { |
| Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e); |
| } |
| }); |
| } |
| |
| @Override |
| public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, |
| boolean clearSchedulerBuffer) { |
| if (mSensors.contains(sensorId)) { |
| mSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer); |
| } |
| } |
| |
| @Override |
| public void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd) { |
| |
| } |
| |
| @Override |
| public void dumpInternal(int sensorId, @NonNull PrintWriter pw) { |
| PerformanceTracker performanceTracker = |
| PerformanceTracker.getInstanceForSensorId(sensorId); |
| |
| JSONObject dump = new JSONObject(); |
| try { |
| dump.put("service", getTag()); |
| |
| JSONArray sets = new JSONArray(); |
| for (UserInfo user : UserManager.get(mContext).getUsers()) { |
| final int userId = user.getUserHandle().getIdentifier(); |
| final int c = FaceUtils.getInstance(sensorId) |
| .getBiometricsForUser(mContext, userId).size(); |
| JSONObject set = new JSONObject(); |
| set.put("id", userId); |
| set.put("count", c); |
| set.put("accept", performanceTracker.getAcceptForUser(userId)); |
| set.put("reject", performanceTracker.getRejectForUser(userId)); |
| set.put("acquire", performanceTracker.getAcquireForUser(userId)); |
| set.put("lockout", performanceTracker.getTimedLockoutForUser(userId)); |
| set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId)); |
| // cryptoStats measures statistics about secure face transactions |
| // (e.g. to unlock password storage, make secure purchases, etc.) |
| set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId)); |
| set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId)); |
| set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId)); |
| sets.put(set); |
| } |
| |
| dump.put("prints", sets); |
| } catch (JSONException e) { |
| Slog.e(getTag(), "dump formatting failure", e); |
| } |
| pw.println(dump); |
| pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount()); |
| |
| mSensors.get(sensorId).getScheduler().dump(pw); |
| mUsageStats.print(pw); |
| } |
| |
| @NonNull |
| @Override |
| public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) { |
| return mSensors.get(sensorId).createTestSession(); |
| } |
| |
| @Override |
| public void dumpHal(int sensorId, @NonNull FileDescriptor fd, @NonNull String[] args) {} |
| |
| @Override |
| public void binderDied() { |
| Slog.e(getTag(), "HAL died"); |
| mHandler.post(() -> { |
| mDaemon = null; |
| for (int i = 0; i < mSensors.size(); i++) { |
| final Sensor sensor = mSensors.valueAt(i); |
| final int sensorId = mSensors.keyAt(i); |
| PerformanceTracker.getInstanceForSensorId(sensorId).incrementHALDeathCount(); |
| sensor.onBinderDied(); |
| } |
| }); |
| } |
| |
| void setTestHalEnabled(boolean enabled) { |
| mTestHalEnabled = enabled; |
| } |
| } |