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

Commit 199da101 authored by Beverly's avatar Beverly Committed by Beverly Tai
Browse files

Update lock icon location if auth setup is delayed

+ Update javadoc

Test: atest LockIconViewControllerTest
Fixes: 196751321
Change-Id: I003bec46b26a2c8ac8bae6d1d26c91645c4ae72f
parent 0d1290e7
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.widget.FrameLayout;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
@@ -79,7 +80,11 @@ public class LockIconView extends FrameLayout implements Dumpable {
        mLockIcon.setImageDrawable(drawable);
    }

    void setCenterLocation(@NonNull PointF center, int radius) {
    /**
     * Set the location of the lock icon.
     */
    @VisibleForTesting
    public void setCenterLocation(@NonNull PointF center, int radius) {
        mLockIconCenter = center;
        mRadius = radius;

+13 −5
Original line number Diff line number Diff line
@@ -68,7 +68,8 @@ import javax.inject.Inject;
/**
 * Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
 *
 * This view will only be shown if the user has UDFPS or FaceAuth enrolled
 * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock
 * icon will show a set distance from the bottom of the device.
 */
@StatusBarComponent.StatusBarScope
public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
@@ -172,15 +173,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme

    @Override
    protected void onInit() {
        mAuthController.addCallback(mAuthControllerCallback);
        mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;

        mView.setAccessibilityDelegate(mAccessibilityDelegate);
    }

    @Override
    protected void onViewAttached() {
        // we check this here instead of onInit since the FingerprintManager + FaceManager may not
        // have started up yet onInit
        mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;

        updateConfiguration();
        updateKeyguardShowing();
        mUserUnlockedWithBiometric = false;
@@ -584,4 +584,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
    public void setAlpha(float alpha) {
        mView.setAlpha(alpha);
    }

    private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
        @Override
        public void onAllAuthenticatorsRegistered() {
            mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
            updateConfiguration();
        }
    };
}
+5 −1
Original line number Diff line number Diff line
@@ -786,7 +786,11 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
                .build(sensorIds, credentialAllowed, mFpProps, mFaceProps);
    }

    interface Callback {
    /**
     * AuthController callback used to receive signal for when biometric authenticators are
     * registered.
     */
    public interface Callback {
        /**
         * Called when authenticators are registered. If authenticators are already
         * registered before this call, this callback will never be triggered.
+189 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.systemui.keyguard;

import static junit.framework.Assert.assertEquals;

import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.accessibility.AccessibilityManager;

import androidx.test.filters.SmallTest;

import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.LockIconView;
import com.android.keyguard.LockIconViewController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;

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

import java.util.ArrayList;
import java.util.List;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class LockIconViewControllerTest extends SysuiTestCase {
    private @Mock LockIconView mLockIconView;
    private @Mock Context mContext;
    private @Mock Resources mResources;
    private @Mock DisplayMetrics mDisplayMetrics;
    private @Mock StatusBarStateController mStatusBarStateController;
    private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    private @Mock KeyguardViewController mKeyguardViewController;
    private @Mock KeyguardStateController mKeyguardStateController;
    private @Mock FalsingManager mFalsingManager;
    private @Mock AuthController mAuthController;
    private @Mock DumpManager mDumpManager;
    private @Mock AccessibilityManager mAccessibilityManager;
    private @Mock ConfigurationController mConfigurationController;
    private @Mock DelayableExecutor mDelayableExecutor;
    private @Mock Vibrator mVibrator;
    private @Mock AuthRippleController mAuthRippleController;

    private LockIconViewController mLockIconViewController;

    // Capture listeners so that they can be used to send events
    @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
            ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
    private View.OnAttachStateChangeListener mAttachListener;

    @Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
    private AuthController.Callback mAuthControllerCallback;

    @Captor private ArgumentCaptor<PointF> mPointCaptor;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        when(mLockIconView.getResources()).thenReturn(mResources);
        when(mLockIconView.getContext()).thenReturn(mContext);
        when(mContext.getResources()).thenReturn(mResources);
        when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);

        mLockIconViewController = new LockIconViewController(
                mLockIconView,
                mStatusBarStateController,
                mKeyguardUpdateMonitor,
                mKeyguardViewController,
                mKeyguardStateController,
                mFalsingManager,
                mAuthController,
                mDumpManager,
                mAccessibilityManager,
                mConfigurationController,
                mDelayableExecutor,
                mVibrator,
                mAuthRippleController
        );
    }

    @Test
    public void testUpdateFingerprintLocationOnInit() {
        // GIVEN fp sensor location is available pre-init
        final PointF udfpsLocation = new PointF(50, 75);
        final int radius = 33;
        final FingerprintSensorPropertiesInternal fpProps =
                new FingerprintSensorPropertiesInternal(
                        /* sensorId */ 0,
                        /* strength */ 0,
                        /* max enrollments per user */ 5,
                        /* component info */ new ArrayList<>(),
                        /* sensorType */ 3,
                        /* resetLockoutRequiresHwToken */ false,
                        (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
        when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
        when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));

        // WHEN lock icon view controller is initialized and attached
        mLockIconViewController.init();
        captureAttachListener();
        mAttachListener.onViewAttachedToWindow(null);

        // THEN lock icon view location is updated with the same coordinates as fpProps
        verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius));
        assertEquals(udfpsLocation, mPointCaptor.getValue());
    }

    @Test
    public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() {
        // GIVEN fp sensor location is not available pre-init
        when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
        when(mAuthController.getUdfpsProps()).thenReturn(null);
        mLockIconViewController.init();

        // GIVEN fp sensor location is available post-init
        captureAuthControllerCallback();
        final PointF udfpsLocation = new PointF(50, 75);
        final int radius = 33;
        final FingerprintSensorPropertiesInternal fpProps =
                new FingerprintSensorPropertiesInternal(
                        /* sensorId */ 0,
                        /* strength */ 0,
                        /* max enrollments per user */ 5,
                        /* component info */ new ArrayList<>(),
                        /* sensorType */ 3,
                        /* resetLockoutRequiresHwToken */ false,
                        (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
        when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
        when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));

        // WHEN all authenticators are registered
        mAuthControllerCallback.onAllAuthenticatorsRegistered();

        // THEN lock icon view location is updated with the same coordinates as fpProps
        verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius));
        assertEquals(udfpsLocation, mPointCaptor.getValue());
    }

    private void captureAuthControllerCallback() {
        verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
        mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
    }

    private void captureAttachListener() {
        verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture());
        mAttachListener = mAttachCaptor.getValue();
    }
}