Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a9f0e99e authored by Jan Tomljanovic's avatar Jan Tomljanovic Committed by Automerger Merge Worker
Browse files

Merge changes from topics "facesettingchange",...

Merge changes from topics "facesettingchange", "presubmit-am-1a8eb017a271444b896f19197b617d62" into tm-dev am: 66a3b3a9

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/17002665

Change-Id: Ieac35d0047319844d2484eba504e3593b9c99b53
parents e560fac0 66a3b3a9
Loading
Loading
Loading
Loading
+3 −5
Original line number Diff line number Diff line
@@ -19,9 +19,7 @@ package com.android.settings.biometrics.face;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.hardware.face.FaceManager;
import android.os.UserHandle;

import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollSidecar;

import java.util.Arrays;
@@ -33,7 +31,7 @@ public class FaceEnrollSidecar extends BiometricEnrollSidecar {

    private final int[] mDisabledFeatures;

    private FaceManager mFaceManager;
    private FaceUpdater mFaceUpdater;

    public FaceEnrollSidecar(int[] disabledFeatures) {
        mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
@@ -42,13 +40,13 @@ public class FaceEnrollSidecar extends BiometricEnrollSidecar {
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mFaceManager = Utils.getFaceManagerOrNull(activity);
        mFaceUpdater = new FaceUpdater(activity);
    }

    @Override
    public void startEnrollment() {
        super.startEnrollment();
        mFaceManager.enroll(mUserId, mToken, mEnrollmentCancel,
        mFaceUpdater.enroll(mUserId, mToken, mEnrollmentCancel,
                mEnrollmentCallback, mDisabledFeatures);
    }

+3 −1
Original line number Diff line number Diff line
@@ -103,6 +103,7 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
    private final MetricsFeatureProvider mMetricsFeatureProvider;
    private final Context mContext;
    private final FaceManager mFaceManager;
    private final FaceUpdater mFaceUpdater;
    private final FaceManager.RemovalCallback mRemovalCallback = new FaceManager.RemovalCallback() {
        @Override
        public void onRemovalError(Face face, int errMsgId, CharSequence errString) {
@@ -144,7 +145,7 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
                }

                // Remove the first/only face
                mFaceManager.remove(faces.get(0), mUserId, mRemovalCallback);
                mFaceUpdater.remove(faces.get(0), mUserId, mRemovalCallback);
            } else {
                mButton.setEnabled(true);
                mRemoving = false;
@@ -157,6 +158,7 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference
        mContext = context;
        mFaceManager = context.getSystemService(FaceManager.class);
        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
        mFaceUpdater = new FaceUpdater(context, mFaceManager);
    }

    public FaceSettingsRemoveButtonPreferenceController(Context context) {
+137 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.settings.biometrics.face;

import android.content.Context;
import android.hardware.face.Face;
import android.hardware.face.FaceEnrollCell;
import android.hardware.face.FaceManager;
import android.os.CancellationSignal;
import android.view.Surface;

import androidx.annotation.Nullable;

import com.android.settings.Utils;
import com.android.settings.safetycenter.BiometricsSafetySource;

/**
 * Responsible for making {@link FaceManager#enroll} and {@link FaceManager#remove} calls and thus
 * updating the face setting.
 */
public class FaceUpdater {

    private final Context mContext;
    private final FaceManager mFaceManager;

    public FaceUpdater(Context context) {
        mContext = context;
        mFaceManager = Utils.getFaceManagerOrNull(context);
    }

    public FaceUpdater(Context context, FaceManager faceManager) {
        mContext = context;
        mFaceManager = faceManager;
    }

    /** Wrapper around the {@link FaceManager#enroll} method. */
    public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel,
            FaceManager.EnrollmentCallback callback, int[] disabledFeatures) {
        mFaceManager.enroll(userId, hardwareAuthToken, cancel,
                new NotifyingEnrollmentCallback(mContext, callback), disabledFeatures);
    }

    /** Wrapper around the {@link FaceManager#enroll} method. */
    public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel,
            FaceManager.EnrollmentCallback callback, int[] disabledFeatures,
            @Nullable Surface previewSurface, boolean debugConsent) {
        mFaceManager.enroll(userId, hardwareAuthToken, cancel,
                new NotifyingEnrollmentCallback(mContext, callback), disabledFeatures,
                previewSurface, debugConsent);
    }

    /** Wrapper around the {@link FaceManager#remove} method. */
    public void remove(Face face, int userId, FaceManager.RemovalCallback callback) {
        mFaceManager.remove(face, userId, new NotifyingRemovalCallback(mContext, callback));
    }

    /**
     * Decorator of the {@link FaceManager.EnrollmentCallback} class that notifies other
     * interested parties that a face setting has changed.
     */
    private static class NotifyingEnrollmentCallback
            extends FaceManager.EnrollmentCallback {

        private final Context mContext;
        private final FaceManager.EnrollmentCallback mCallback;

        NotifyingEnrollmentCallback(Context context,
                FaceManager.EnrollmentCallback callback) {
            mContext = context;
            mCallback = callback;
        }

        @Override
        public void onEnrollmentError(int errMsgId, CharSequence errString) {
            mCallback.onEnrollmentError(errMsgId, errString);
        }

        @Override
        public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
            mCallback.onEnrollmentHelp(helpMsgId, helpString);
        }

        @Override
        public void onEnrollmentFrame(int helpCode, @Nullable CharSequence helpMessage,
                @Nullable FaceEnrollCell cell, int stage, float pan, float tilt, float distance) {
            mCallback.onEnrollmentFrame(helpCode, helpMessage, cell, stage, pan, tilt, distance);
        }

        @Override
        public void onEnrollmentProgress(int remaining) {
            mCallback.onEnrollmentProgress(remaining);
            if (remaining == 0) {
                BiometricsSafetySource.sendSafetyData(mContext); // biometrics data changed
            }
        }
    }

    /**
     * Decorator of the {@link FaceManager.RemovalCallback} class that notifies other
     * interested parties that a face setting has changed.
     */
    private static class NotifyingRemovalCallback extends FaceManager.RemovalCallback {

        private final Context mContext;
        private final FaceManager.RemovalCallback mCallback;

        NotifyingRemovalCallback(Context context, FaceManager.RemovalCallback callback) {
            mContext = context;
            mCallback = callback;
        }

        @Override
        public void onRemovalError(Face fp, int errMsgId, CharSequence errString) {
            mCallback.onRemovalError(fp, errMsgId, errString);
        }

        @Override
        public void onRemovalSucceeded(@Nullable Face fp, int remaining) {
            mCallback.onRemovalSucceeded(fp, remaining);
            BiometricsSafetySource.sendSafetyData(mContext); // biometrics data changed
        }
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.face.FaceUpdater;
import com.android.settings.homepage.contextualcards.slices.FaceSetupSlice;

/**
@@ -43,6 +44,7 @@ public class FaceReEnrollDialog extends AlertActivity implements
    private static final String BIOMETRIC_ENROLL_ACTION = "android.settings.BIOMETRIC_ENROLL";

    private FaceManager mFaceManager;
    private FaceUpdater mFaceUpdater;
    /**
     * The type of re-enrollment that has been requested,
     * see {@link Settings.Secure#FACE_UNLOCK_RE_ENROLL} for more details.
@@ -67,6 +69,7 @@ public class FaceReEnrollDialog extends AlertActivity implements
        alertParams.mPositiveButtonListener = this;

        mFaceManager = Utils.getFaceManagerOrNull(getApplicationContext());
        mFaceUpdater = new FaceUpdater(getApplicationContext(), mFaceManager);

        final Context context = getApplicationContext();
        mReEnrollType = FaceSetupSlice.getReEnrollSetting(context, getUserId());
@@ -96,7 +99,7 @@ public class FaceReEnrollDialog extends AlertActivity implements
        if (mFaceManager == null || !mFaceManager.hasEnrolledTemplates(userId)) {
            finish();
        }
        mFaceManager.remove(new Face("", 0, 0), userId, new FaceManager.RemovalCallback() {
        mFaceUpdater.remove(new Face("", 0, 0), userId, new FaceManager.RemovalCallback() {
            @Override
            public void onRemovalError(Face face, int errMsgId, CharSequence errString) {
                super.onRemovalError(face, errMsgId, errString);
+277 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.settings.biometrics.face;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.hardware.face.Face;
import android.hardware.face.FaceEnrollCell;
import android.hardware.face.FaceEnrollStages;
import android.hardware.face.FaceManager;
import android.os.CancellationSignal;
import android.view.Surface;

import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import com.android.settings.safetycenter.SafetyCenterManagerWrapper;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@RunWith(AndroidJUnit4.class)
public class FaceUpdaterTest {

    private static final byte[] HARDWARE_AUTH_TOKEN = new byte[] {0};
    private static final CancellationSignal CANCELLATION_SIGNAL = new CancellationSignal();
    private static final int USER_ID = 0;
    private static final int ERR_MSG_ID = 0;
    private static final int HELP_MSG_ID = 0;
    private static final String HELP_STRING = "";
    private static final String ERR_STRING = "";
    private static final Face FACE =
            new Face(/* name= */"", /* faceId */ 0, /* deviceId= */ 0L);
    private static final int[] DISABLED_FEATURES = new int[] {0};
    private static final boolean DEBUG_CONSENT = false;
    private static final Surface PREVIEW_SURFACE = new Surface();
    private static final int HELP_CODE = 0;
    private static final CharSequence HELP_MESSAGE = "";
    private static final FaceEnrollCell CELL =
            new FaceEnrollCell(/* x= */ 0, /* y= */ 0, /* z= */ 0);
    private static final int STAGE = FaceEnrollStages.UNKNOWN;
    private static final float PAN = 0;
    private static final float TILT = 0;
    private static final float DISTANCE = 0;


    @Mock private FaceManager mFaceManager;
    @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;

    private FaceUpdater mFaceUpdater;
    private Context mContext;
    private FaceManager.EnrollmentCallback mEnrollmentCallback;
    private FaceManager.RemovalCallback mRemovalCallback;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mContext = ApplicationProvider.getApplicationContext();
        mFaceUpdater = new FaceUpdater(mContext, mFaceManager);
        mEnrollmentCallback = spy(new TestEnrollmentCallback());
        mRemovalCallback = spy(new TestRemovalCallback());
        SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
    }

    @Test
    public void enroll_firstVersion_onEnrollmentCallbacks_triggerGivenCallback() {
        ArgumentCaptor<FaceManager.EnrollmentCallback> callbackCaptor =
                ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class);
        mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback,
                DISABLED_FEATURES);
        verify(mFaceManager).enroll(
                eq(USER_ID),
                same(HARDWARE_AUTH_TOKEN),
                same(CANCELLATION_SIGNAL),
                callbackCaptor.capture(),
                same(DISABLED_FEATURES));
        FaceManager.EnrollmentCallback callback = callbackCaptor.getValue();

        callback.onEnrollmentError(ERR_MSG_ID, ERR_STRING);
        callback.onEnrollmentProgress(/* remaining= */ 2);
        callback.onEnrollmentHelp(HELP_MSG_ID, HELP_STRING);
        callback.onEnrollmentFrame(HELP_CODE, HELP_MESSAGE, CELL, STAGE, PAN, TILT, DISTANCE);

        verify(mEnrollmentCallback, atLeast(1)).onEnrollmentError(ERR_MSG_ID, ERR_STRING);
        verify(mEnrollmentCallback, atLeast(1)).onEnrollmentProgress(/* remaining= */ 2);
        verify(mEnrollmentCallback, atLeast(1)).onEnrollmentHelp(HELP_MSG_ID, HELP_STRING);
        verify(mEnrollmentCallback, atLeast(1))
                .onEnrollmentFrame(HELP_CODE, HELP_MESSAGE, CELL, STAGE, PAN, TILT, DISTANCE);
    }

    @Test
    public void enroll_firstVersion_onEnrollmentSuccess_invokedInteractionWithSafetyCenter() {
        ArgumentCaptor<FaceManager.EnrollmentCallback> callbackCaptor =
                ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class);
        mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback,
                DISABLED_FEATURES);
        verify(mFaceManager).enroll(
                eq(USER_ID),
                same(HARDWARE_AUTH_TOKEN),
                same(CANCELLATION_SIGNAL),
                callbackCaptor.capture(),
                same(DISABLED_FEATURES));
        FaceManager.EnrollmentCallback callback = callbackCaptor.getValue();

        callback.onEnrollmentProgress(/* remaining= */ 0);

        verify(mSafetyCenterManagerWrapper).isEnabled(mContext);
    }

    @Test
    public void enroll_firstVersion_onEnrollmentNotYetFinished_didntInvokeInteractionWithSafetyCenter() {
        ArgumentCaptor<FaceManager.EnrollmentCallback> callbackCaptor =
                ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class);
        mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback,
                DISABLED_FEATURES);
        verify(mFaceManager).enroll(
                eq(USER_ID),
                same(HARDWARE_AUTH_TOKEN),
                same(CANCELLATION_SIGNAL),
                callbackCaptor.capture(),
                same(DISABLED_FEATURES));
        FaceManager.EnrollmentCallback callback = callbackCaptor.getValue();

        callback.onEnrollmentProgress(/* remaining= */ 1);

        verify(mSafetyCenterManagerWrapper, never()).isEnabled(any());
    }

    @Test
    public void enroll_secondVersion_onEnrollmentCallbacks_triggerGivenCallback() {
        ArgumentCaptor<FaceManager.EnrollmentCallback> callbackCaptor =
                ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class);
        mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback,
                DISABLED_FEATURES, PREVIEW_SURFACE, DEBUG_CONSENT);
        verify(mFaceManager).enroll(
                eq(USER_ID),
                same(HARDWARE_AUTH_TOKEN),
                same(CANCELLATION_SIGNAL),
                callbackCaptor.capture(),
                same(DISABLED_FEATURES),
                same(PREVIEW_SURFACE),
                eq(DEBUG_CONSENT));
        FaceManager.EnrollmentCallback callback = callbackCaptor.getValue();

        callback.onEnrollmentError(ERR_MSG_ID, ERR_STRING);
        callback.onEnrollmentProgress(/* remaining= */ 2);
        callback.onEnrollmentHelp(HELP_MSG_ID, HELP_STRING);
        callback.onEnrollmentFrame(HELP_CODE, HELP_MESSAGE, CELL, STAGE, PAN, TILT, DISTANCE);

        verify(mEnrollmentCallback, atLeast(1)).onEnrollmentError(ERR_MSG_ID, ERR_STRING);
        verify(mEnrollmentCallback, atLeast(1)).onEnrollmentProgress(/* remaining= */ 2);
        verify(mEnrollmentCallback, atLeast(1)).onEnrollmentHelp(HELP_MSG_ID, HELP_STRING);
        verify(mEnrollmentCallback, atLeast(1))
                .onEnrollmentFrame(HELP_CODE, HELP_MESSAGE, CELL, STAGE, PAN, TILT, DISTANCE);
    }

    @Test
    public void enroll_secondVersion_onEnrollmentSuccess_invokedInteractionWithSafetyCenter() {
        ArgumentCaptor<FaceManager.EnrollmentCallback> callbackCaptor =
                ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class);
        mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback,
                DISABLED_FEATURES, PREVIEW_SURFACE, DEBUG_CONSENT);
        verify(mFaceManager).enroll(
                eq(USER_ID),
                same(HARDWARE_AUTH_TOKEN),
                same(CANCELLATION_SIGNAL),
                callbackCaptor.capture(),
                same(DISABLED_FEATURES),
                same(PREVIEW_SURFACE),
                eq(DEBUG_CONSENT));
        FaceManager.EnrollmentCallback callback = callbackCaptor.getValue();

        callback.onEnrollmentProgress(/* remaining= */ 0);

        verify(mSafetyCenterManagerWrapper).isEnabled(mContext);
    }

    @Test
    public void enroll_secondVersion_onEnrollmentNotYetFinished_didntInvokeInteractionWithSafetyCenter() {
        ArgumentCaptor<FaceManager.EnrollmentCallback> callbackCaptor =
                ArgumentCaptor.forClass(FaceManager.EnrollmentCallback.class);
        mFaceUpdater.enroll(USER_ID, HARDWARE_AUTH_TOKEN, CANCELLATION_SIGNAL, mEnrollmentCallback,
                DISABLED_FEATURES, PREVIEW_SURFACE, DEBUG_CONSENT);
        verify(mFaceManager).enroll(
                eq(USER_ID),
                same(HARDWARE_AUTH_TOKEN),
                same(CANCELLATION_SIGNAL),
                callbackCaptor.capture(),
                same(DISABLED_FEATURES),
                same(PREVIEW_SURFACE),
                eq(DEBUG_CONSENT));
        FaceManager.EnrollmentCallback callback = callbackCaptor.getValue();

        callback.onEnrollmentProgress(/* remaining= */ 1);

        verify(mSafetyCenterManagerWrapper, never()).isEnabled(any());
    }

    @Test
    public void remove_onRemovalCallbacks_triggerGivenCallback() {
        ArgumentCaptor<FaceManager.RemovalCallback> callbackCaptor =
                ArgumentCaptor.forClass(FaceManager.RemovalCallback.class);
        mFaceUpdater.remove(FACE, USER_ID, mRemovalCallback);
        verify(mFaceManager)
                .remove(same(FACE), eq(USER_ID), callbackCaptor.capture());
        FaceManager.RemovalCallback callback = callbackCaptor.getValue();

        callback.onRemovalSucceeded(FACE, /* remaining= */ 1);
        callback.onRemovalError(FACE, ERR_MSG_ID, ERR_STRING);

        verify(mRemovalCallback).onRemovalSucceeded(any(), eq(1));
        verify(mRemovalCallback).onRemovalError(FACE, ERR_MSG_ID, ERR_STRING);
    }

    @Test
    public void remove_onRemovalSuccess_invokedInteractionWithSafetyCenter() {
        ArgumentCaptor<FaceManager.RemovalCallback> callbackCaptor =
                ArgumentCaptor.forClass(FaceManager.RemovalCallback.class);
        mFaceUpdater.remove(FACE, USER_ID, mRemovalCallback);
        verify(mFaceManager)
                .remove(same(FACE), eq(USER_ID), callbackCaptor.capture());
        FaceManager.RemovalCallback callback = callbackCaptor.getValue();

        callback.onRemovalSucceeded(FACE, /* remaining= */ 0);

        verify(mSafetyCenterManagerWrapper).isEnabled(mContext);
    }

    public static class TestEnrollmentCallback extends FaceManager.EnrollmentCallback {
        @Override
        public void onEnrollmentError(int errMsgId, CharSequence errString) {}

        @Override
        public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {}

        @Override
        public void onEnrollmentProgress(int remaining) {}

        @Override
        public void onEnrollmentFrame(int helpCode, @Nullable CharSequence helpMessage,
                @Nullable FaceEnrollCell cell, int stage, float pan, float tilt, float distance) {}
    }

    public static class TestRemovalCallback extends FaceManager.RemovalCallback {
        @Override
        public void onRemovalError(Face fp, int errMsgId, CharSequence errString) {}

        @Override
        public void onRemovalSucceeded(@Nullable Face fp, int remaining) {}
    }
}