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

Commit 90d40325 authored by Robert Snoeberger's avatar Robert Snoeberger
Browse files

Adds tests for UdfpsController

In particular, adding tests for new methods added in ag/12435691.

Bug: 165267247
Test: atest UdfpsControllerTest
Change-Id: I0c084c05a5738b4a15de6e363128237dc06e421f
parent a9f28aff
Loading
Loading
Loading
Loading
+44 −27
Original line number Diff line number Diff line
@@ -16,17 +16,16 @@

package com.android.systemui.biometrics;

import android.annotation.NonNull;
import static com.android.internal.util.Preconditions.checkNotNull;

import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
@@ -38,10 +37,16 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.WindowManager;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.BrightnessSynchronizer;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.settings.SystemSettings;

import java.io.FileWriter;
import java.io.IOException;
@@ -60,8 +65,8 @@ class UdfpsController implements DozeReceiver {

    private final FingerprintManager mFingerprintManager;
    private final WindowManager mWindowManager;
    private final ContentResolver mContentResolver;
    private final Handler mHandler;
    private final SystemSettings mSystemSettings;
    private final DelayableExecutor mFgExecutor;
    private final WindowManager.LayoutParams mLayoutParams;
    private final UdfpsView mView;
    // Debugfs path to control the high-brightness mode.
@@ -88,7 +93,7 @@ class UdfpsController implements DozeReceiver {
    // interrupt is being tracked and a timeout is used as a last resort to turn off high brightness
    // mode.
    private boolean mIsAodInterruptActive;
    private final Runnable mAodInterruptTimeoutAction = this::onCancelAodInterrupt;
    @Nullable private Runnable mCancelAodTimeoutAction;

    public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
        @Override
@@ -138,18 +143,27 @@ class UdfpsController implements DozeReceiver {

    @Inject
    UdfpsController(@NonNull Context context,
            @NonNull StatusBarStateController statusBarStateController) {
        mFingerprintManager = context.getSystemService(FingerprintManager.class);
        mWindowManager = context.getSystemService(WindowManager.class);
        mContentResolver = context.getContentResolver();
        mHandler = new Handler(Looper.getMainLooper());
            @Main Resources resources,
            LayoutInflater inflater,
            @Nullable FingerprintManager fingerprintManager,
            PowerManager powerManager,
            WindowManager windowManager,
            SystemSettings systemSettings,
            @NonNull StatusBarStateController statusBarStateController,
            @Main DelayableExecutor fgExecutor) {
        // The fingerprint manager is queried for UDFPS before this class is constructed, so the
        // fingerprint manager should never be null.
        mFingerprintManager = checkNotNull(fingerprintManager);
        mWindowManager = windowManager;
        mSystemSettings = systemSettings;
        mFgExecutor = fgExecutor;
        mLayoutParams = createLayoutParams(context);

        mView = (UdfpsView) LayoutInflater.from(context).inflate(R.layout.udfps_view, null, false);
        mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);

        mHbmPath = context.getResources().getString(R.string.udfps_hbm_sysfs_path);
        mHbmEnableCommand = context.getResources().getString(R.string.udfps_hbm_enable_command);
        mHbmDisableCommand = context.getResources().getString(R.string.udfps_hbm_disable_command);
        mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path);
        mHbmEnableCommand = resources.getString(R.string.udfps_hbm_enable_command);
        mHbmDisableCommand = resources.getString(R.string.udfps_hbm_disable_command);

        mHbmSupported = !TextUtils.isEmpty(mHbmPath);
        mView.setHbmSupported(mHbmSupported);
@@ -157,11 +171,11 @@ class UdfpsController implements DozeReceiver {

        // This range only consists of the minimum and maximum values, which only cover
        // non-high-brightness mode.
        float[] nitsRange = toFloatArray(context.getResources().obtainTypedArray(
        float[] nitsRange = toFloatArray(resources.obtainTypedArray(
                com.android.internal.R.array.config_screenBrightnessNits));

        // The last value of this range corresponds to the high-brightness mode.
        float[] nitsAutoBrightnessValues = toFloatArray(context.getResources().obtainTypedArray(
        float[] nitsAutoBrightnessValues = toFloatArray(resources.obtainTypedArray(
                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));

        mHbmNits = nitsAutoBrightnessValues[nitsAutoBrightnessValues.length - 1];
@@ -170,12 +184,12 @@ class UdfpsController implements DozeReceiver {
        // This range only consists of the minimum and maximum backlight values, which only apply
        // in non-high-brightness mode.
        float[] normalizedBacklightRange = normalizeBacklightRange(
                context.getResources().getIntArray(
                resources.getIntArray(
                        com.android.internal.R.array.config_screenBrightnessBacklight));

        mBacklightToNitsSpline = Spline.createSpline(normalizedBacklightRange, nitsRange);
        mNitsToHbmBacklightSpline = Spline.createSpline(hbmNitsRange, normalizedBacklightRange);
        mDefaultBrightness = obtainDefaultBrightness(context);
        mDefaultBrightness = obtainDefaultBrightness(powerManager);

        // TODO(b/160025856): move to the "dump" method.
        Log.v(TAG, String.format("ctor | mNitsRange: [%f, %f]", nitsRange[0], nitsRange[1]));
@@ -194,7 +208,7 @@ class UdfpsController implements DozeReceiver {
    }

    private void showUdfpsOverlay() {
        mHandler.post(() -> {
        mFgExecutor.execute(() -> {
            if (!mIsOverlayShowing) {
                try {
                    Log.v(TAG, "showUdfpsOverlay | adding window");
@@ -211,7 +225,7 @@ class UdfpsController implements DozeReceiver {
    }

    private void hideUdfpsOverlay() {
        mHandler.post(() -> {
        mFgExecutor.execute(() -> {
            if (mIsOverlayShowing) {
                Log.v(TAG, "hideUdfpsOverlay | removing window");
                mView.setOnTouchListener(null);
@@ -228,7 +242,7 @@ class UdfpsController implements DozeReceiver {
    // Returns a value in the range of [0, 255].
    private int computeScrimOpacity() {
        // Backlight setting can be NaN, -1.0f, and [0.0f, 1.0f].
        float backlightSetting = Settings.System.getFloatForUser(mContentResolver,
        float backlightSetting = mSystemSettings.getFloatForUser(
                Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBrightness,
                UserHandle.USER_CURRENT);

@@ -265,7 +279,8 @@ class UdfpsController implements DozeReceiver {
        // Since the sensor that triggers the AOD interrupt doesn't provide ACTION_UP/ACTION_CANCEL,
        // we need to be careful about not letting the screen accidentally remain in high brightness
        // mode. As a mitigation, queue a call to cancel the fingerprint scan.
        mHandler.postDelayed(mAodInterruptTimeoutAction, AOD_INTERRUPT_TIMEOUT_MILLIS);
        mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelAodInterrupt,
                AOD_INTERRUPT_TIMEOUT_MILLIS);
        // using a hard-coded value for major and minor until it is available from the sensor
        onFingerDown(screenX, screenY, 13.0f, 13.0f);
    }
@@ -280,7 +295,10 @@ class UdfpsController implements DozeReceiver {
        if (!mIsAodInterruptActive) {
            return;
        }
        mHandler.removeCallbacks(mAodInterruptTimeoutAction);
        if (mCancelAodTimeoutAction != null) {
            mCancelAodTimeoutAction.run();
            mCancelAodTimeoutAction = null;
        }
        mIsAodInterruptActive = false;
        onFingerUp();
    }
@@ -338,8 +356,7 @@ class UdfpsController implements DozeReceiver {
        return lp;
    }

    private static float obtainDefaultBrightness(Context context) {
        PowerManager powerManager = context.getSystemService(PowerManager.class);
    private static float obtainDefaultBrightness(PowerManager powerManager) {
        if (powerManager == null) {
            Log.e(TAG, "PowerManager is unavailable. Can't obtain default brightness.");
            return 0f;
+210 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.biometrics;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.PowerManager;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.WindowManager;

import androidx.test.filters.SmallTest;

import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.time.FakeSystemClock;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class UdfpsControllerTest extends SysuiTestCase {

    @Rule
    public MockitoRule rule = MockitoJUnit.rule();

    // Unit under test
    private UdfpsController mUdfpsController;

    // Dependencies
    @Mock
    private Resources mResources;
    @Mock
    private LayoutInflater mLayoutInflater;
    @Mock
    private FingerprintManager mFingerprintManager;
    @Mock
    private PowerManager mPowerManager;
    @Mock
    private WindowManager mWindowManager;
    @Mock
    private StatusBarStateController mStatusBarStateController;
    private FakeSettings mSystemSettings;
    private FakeExecutor mFgExecutor;

    // Stuff for configuring mocks
    @Mock
    private UdfpsView mUdfpsView;
    @Mock
    private TypedArray mBrightnessValues;
    @Mock
    private TypedArray mBrightnessBacklight;

    // Capture listeners so that they can be used to send events
    @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
    private IUdfpsOverlayController mOverlayController;
    @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;

    @Before
    public void setUp() {
        setUpResources();
        when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)).thenReturn(mUdfpsView);
        mSystemSettings = new FakeSettings();
        mFgExecutor = new FakeExecutor(new FakeSystemClock());
        mUdfpsController = new UdfpsController(
                mContext,
                mResources,
                mLayoutInflater,
                mFingerprintManager,
                mPowerManager,
                mWindowManager,
                mSystemSettings,
                mStatusBarStateController,
                mFgExecutor);
        verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
        mOverlayController = mOverlayCaptor.getValue();
    }

    private void setUpResources() {
        when(mBrightnessValues.length()).thenReturn(2);
        when(mBrightnessValues.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f);
        when(mBrightnessValues.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f);
        when(mResources.obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits))
                .thenReturn(mBrightnessValues);
        when(mBrightnessBacklight.length()).thenReturn(2);
        when(mBrightnessBacklight.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f);
        when(mBrightnessBacklight.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f);
        when(mResources.obtainTypedArray(
                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
                .thenReturn(mBrightnessBacklight);
        when(mResources.getIntArray(com.android.internal.R.array.config_screenBrightnessBacklight))
                .thenReturn(new int[]{1, 2});
    }

    @Test
    public void dozeTimeTick() {
        mUdfpsController.dozeTimeTick();
        verify(mUdfpsView).dozeTimeTick();
    }

    @Test
    public void showUdfpsOverlay_addsViewToWindow() throws RemoteException {
        mOverlayController.showUdfpsOverlay();
        mFgExecutor.runAllReady();
        verify(mWindowManager).addView(eq(mUdfpsView), any());
    }

    @Test
    public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException {
        mOverlayController.showUdfpsOverlay();
        mOverlayController.hideUdfpsOverlay();
        mFgExecutor.runAllReady();
        verify(mWindowManager).removeView(eq(mUdfpsView));
    }

    @Test
    public void fingerDown() throws RemoteException {
        // Configure UdfpsView to accept the ACTION_DOWN event
        when(mUdfpsView.isScrimShowing()).thenReturn(false);
        when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true);

        // GIVEN that the overlay is showing
        mOverlayController.showUdfpsOverlay();
        mFgExecutor.runAllReady();
        // WHEN ACTION_DOWN is received
        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
        event.recycle();
        // THEN the event is passed to the FingerprintManager
        verify(mFingerprintManager).onFingerDown(eq(0), eq(0), eq(0f), eq(0f));
        // AND the scrim and dot is shown
        verify(mUdfpsView).showScrimAndDot();
    }

    @Test
    public void aodInterrupt() throws RemoteException {
        // GIVEN that the overlay is showing
        mOverlayController.showUdfpsOverlay();
        mFgExecutor.runAllReady();
        // WHEN fingerprint is requested because of AOD interrupt
        mUdfpsController.onAodInterrupt(0, 0);
        // THEN the event is passed to the FingerprintManager
        verify(mFingerprintManager).onFingerDown(eq(0), eq(0), anyFloat(), anyFloat());
        // AND the scrim and dot is shown
        verify(mUdfpsView).showScrimAndDot();
    }

    @Test
    public void cancelAodInterrupt() throws RemoteException {
        // GIVEN AOD interrupt
        mOverlayController.showUdfpsOverlay();
        mFgExecutor.runAllReady();
        mUdfpsController.onAodInterrupt(0, 0);
        // WHEN it is cancelled
        mUdfpsController.onCancelAodInterrupt();
        // THEN the scrim and dot is hidden
        verify(mUdfpsView).hideScrimAndDot();
    }

    @Test
    public void aodInterruptTimeout() throws RemoteException {
        // GIVEN AOD interrupt
        mOverlayController.showUdfpsOverlay();
        mFgExecutor.runAllReady();
        mUdfpsController.onAodInterrupt(0, 0);
        // WHEN it times out
        mFgExecutor.advanceClockToNext();
        mFgExecutor.runAllReady();
        // THEN the scrim and dot is hidden
        verify(mUdfpsView).hideScrimAndDot();
    }
}