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

Commit b2a04d02 authored by Haining Chen's avatar Haining Chen Committed by Automerger Merge Worker
Browse files

Merge "Add unit tests for LockSettingsStrongAuth" into rvc-dev am: 01f08dce am: eb8a8da0

Change-Id: Ic9124029b0fa303fdc4d6413c517947d69b136ed
parents 9fc00680 eb8a8da0
Loading
Loading
Loading
Loading
+79 −24
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.app.admin.DevicePolicyManager;
import android.app.trust.IStrongAuthTracker;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -36,6 +37,7 @@ import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;

@@ -57,11 +59,14 @@ public class LockSettingsStrongAuth {
    private static final int MSG_STRONG_BIOMETRIC_UNLOCK = 8;
    private static final int MSG_SCHEDULE_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT = 9;

    private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
    @VisibleForTesting
    protected static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
            "LockSettingsStrongAuth.timeoutForUser";
    private static final String NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG =
    @VisibleForTesting
    protected static final String NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG =
            "LockSettingsPrimaryAuth.nonStrongBiometricTimeoutForUser";
    private static final String NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG =
    @VisibleForTesting
    protected static final String NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG =
            "LockSettingsPrimaryAuth.nonStrongBiometricIdleTimeoutForUser";

    /**
@@ -73,28 +78,71 @@ public class LockSettingsStrongAuth {
            4 * 60 * 60 * 1000; // 4h

    private final RemoteCallbackList<IStrongAuthTracker> mTrackers = new RemoteCallbackList<>();
    private final SparseIntArray mStrongAuthForUser = new SparseIntArray();
    private final SparseBooleanArray mIsNonStrongBiometricAllowedForUser = new SparseBooleanArray();
    private final ArrayMap<Integer, StrongAuthTimeoutAlarmListener>
    @VisibleForTesting
    protected final SparseIntArray mStrongAuthForUser = new SparseIntArray();
    @VisibleForTesting
    protected final SparseBooleanArray mIsNonStrongBiometricAllowedForUser =
            new SparseBooleanArray();
    @VisibleForTesting
    protected final ArrayMap<Integer, StrongAuthTimeoutAlarmListener>
            mStrongAuthTimeoutAlarmListenerForUser = new ArrayMap<>();
    // Track non-strong biometric timeout
    private final ArrayMap<Integer, NonStrongBiometricTimeoutAlarmListener>
    @VisibleForTesting
    protected final ArrayMap<Integer, NonStrongBiometricTimeoutAlarmListener>
            mNonStrongBiometricTimeoutAlarmListener = new ArrayMap<>();
    // Track non-strong biometric idle timeout
    private final ArrayMap<Integer, NonStrongBiometricIdleTimeoutAlarmListener>
    @VisibleForTesting
    protected final ArrayMap<Integer, NonStrongBiometricIdleTimeoutAlarmListener>
            mNonStrongBiometricIdleTimeoutAlarmListener = new ArrayMap<>();

    private final int mDefaultStrongAuthFlags;
    private final boolean mDefaultIsNonStrongBiometricAllowed = true;

    private final Context mContext;

    private AlarmManager mAlarmManager;
    private final Injector mInjector;
    private final AlarmManager mAlarmManager;

    public LockSettingsStrongAuth(Context context) {
        this(context, new Injector());
    }

    @VisibleForTesting
    protected LockSettingsStrongAuth(Context context, Injector injector) {
        mContext = context;
        mDefaultStrongAuthFlags = StrongAuthTracker.getDefaultFlags(context);
        mAlarmManager = context.getSystemService(AlarmManager.class);
        mInjector = injector;
        mDefaultStrongAuthFlags = mInjector.getDefaultStrongAuthFlags(context);
        mAlarmManager = mInjector.getAlarmManager(context);
    }

    /**
     * Class for injecting dependencies into LockSettingsStrongAuth.
     */
    @VisibleForTesting
    public static class Injector {

        /**
         * Allows to mock AlarmManager for testing.
         */
        @VisibleForTesting
        public AlarmManager getAlarmManager(Context context) {
            return context.getSystemService(AlarmManager.class);
        }

        /**
         * Allows to get different default StrongAuthFlags for testing.
         */
        @VisibleForTesting
        public int getDefaultStrongAuthFlags(Context context) {
            return StrongAuthTracker.getDefaultFlags(context);
        }

        /**
         * Allows to get different triggerAtMillis values when setting alarms for testing.
         */
        @VisibleForTesting
        public long getNextAlarmTimeMs(long timeout) {
            return SystemClock.elapsedRealtime() + timeout;
        }
    }

    private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) {
@@ -186,7 +234,8 @@ public class LockSettingsStrongAuth {
    private void handleScheduleStrongAuthTimeout(int userId) {
        final DevicePolicyManager dpm =
                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
        long when = SystemClock.elapsedRealtime() + dpm.getRequiredStrongAuthTimeout(null, userId);
        long nextAlarmTime =
                mInjector.getNextAlarmTimeMs(dpm.getRequiredStrongAuthTimeout(null, userId));
        // cancel current alarm listener for the user (if there was one)
        StrongAuthTimeoutAlarmListener alarm = mStrongAuthTimeoutAlarmListenerForUser.get(userId);
        if (alarm != null) {
@@ -196,8 +245,8 @@ public class LockSettingsStrongAuth {
            mStrongAuthTimeoutAlarmListenerForUser.put(userId, alarm);
        }
        // schedule a new alarm listener for the user
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, STRONG_AUTH_TIMEOUT_ALARM_TAG,
                alarm, mHandler);
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
                STRONG_AUTH_TIMEOUT_ALARM_TAG, alarm, mHandler);

        // cancel current non-strong biometric alarm listener for the user (if there was one)
        cancelNonStrongBiometricAlarmListener(userId);
@@ -209,7 +258,7 @@ public class LockSettingsStrongAuth {

    private void handleScheduleNonStrongBiometricTimeout(int userId) {
        if (DEBUG) Slog.d(TAG, "handleScheduleNonStrongBiometricTimeout for userId=" + userId);
        long when = SystemClock.elapsedRealtime() + DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS;
        long nextAlarmTime = mInjector.getNextAlarmTimeMs(DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS);
        NonStrongBiometricTimeoutAlarmListener alarm = mNonStrongBiometricTimeoutAlarmListener
                .get(userId);
        if (alarm != null) {
@@ -226,7 +275,7 @@ public class LockSettingsStrongAuth {
            alarm = new NonStrongBiometricTimeoutAlarmListener(userId);
            mNonStrongBiometricTimeoutAlarmListener.put(userId, alarm);
            // schedule a new alarm listener for the user
            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when,
            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
                    NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG, alarm, mHandler);
        }

@@ -268,7 +317,8 @@ public class LockSettingsStrongAuth {
        }
    }

    private void setIsNonStrongBiometricAllowed(boolean allowed, int userId) {
    @VisibleForTesting
    protected void setIsNonStrongBiometricAllowed(boolean allowed, int userId) {
        if (DEBUG) {
            Slog.d(TAG, "setIsNonStrongBiometricAllowed for allowed=" + allowed
                    + ", userId=" + userId);
@@ -302,7 +352,8 @@ public class LockSettingsStrongAuth {

    private void handleScheduleNonStrongBiometricIdleTimeout(int userId) {
        if (DEBUG) Slog.d(TAG, "handleScheduleNonStrongBiometricIdleTimeout for userId=" + userId);
        long when = SystemClock.elapsedRealtime() + DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS;
        long nextAlarmTime =
                mInjector.getNextAlarmTimeMs(DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS);
        // cancel current alarm listener for the user (if there was one)
        NonStrongBiometricIdleTimeoutAlarmListener alarm =
                mNonStrongBiometricIdleTimeoutAlarmListener.get(userId);
@@ -315,7 +366,7 @@ public class LockSettingsStrongAuth {
        }
        // schedule a new alarm listener for the user
        if (DEBUG) Slog.d(TAG, "Schedule a new alarm for non-strong biometric idle timeout");
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when,
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
                NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG, alarm, mHandler);
    }

@@ -435,7 +486,8 @@ public class LockSettingsStrongAuth {
    /**
     * Alarm of fallback timeout for primary auth
     */
    private class StrongAuthTimeoutAlarmListener implements OnAlarmListener {
    @VisibleForTesting
    protected class StrongAuthTimeoutAlarmListener implements OnAlarmListener {

        private final int mUserId;

@@ -452,7 +504,8 @@ public class LockSettingsStrongAuth {
    /**
     * Alarm of fallback timeout for non-strong biometric (i.e. weak or convenience)
     */
    private class NonStrongBiometricTimeoutAlarmListener implements OnAlarmListener {
    @VisibleForTesting
    protected class NonStrongBiometricTimeoutAlarmListener implements OnAlarmListener {

        private final int mUserId;

@@ -469,7 +522,8 @@ public class LockSettingsStrongAuth {
    /**
     * Alarm of idle timeout for non-strong biometric (i.e. weak or convenience biometric)
     */
    private class NonStrongBiometricIdleTimeoutAlarmListener implements OnAlarmListener {
    @VisibleForTesting
    protected class NonStrongBiometricIdleTimeoutAlarmListener implements OnAlarmListener {

        private final int mUserId;

@@ -484,7 +538,8 @@ public class LockSettingsStrongAuth {
        }
    }

    private final Handler mHandler = new Handler() {
    @VisibleForTesting
    protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
+252 −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.server.locksettings;

import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
import static com.android.server.locksettings.LockSettingsStrongAuth.DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS;
import static com.android.server.locksettings.LockSettingsStrongAuth.DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS;
import static com.android.server.locksettings.LockSettingsStrongAuth.NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG;
import static com.android.server.locksettings.LockSettingsStrongAuth.NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG;
import static com.android.server.locksettings.LockSettingsStrongAuth.STRONG_AUTH_TIMEOUT_ALARM_TAG;

import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;

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

import android.app.AlarmManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.util.Log;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;

import com.android.server.locksettings.LockSettingsStrongAuth.NonStrongBiometricIdleTimeoutAlarmListener;
import com.android.server.locksettings.LockSettingsStrongAuth.NonStrongBiometricTimeoutAlarmListener;
import com.android.server.locksettings.LockSettingsStrongAuth.StrongAuthTimeoutAlarmListener;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@SmallTest
public class LockSettingsStrongAuthTest {

    private static final String TAG = LockSettingsStrongAuthTest.class.getSimpleName();

    private static final int PRIMARY_USER_ID = 0;

    private LockSettingsStrongAuth mStrongAuth;
    private final int mDefaultStrongAuthFlags = STRONG_AUTH_NOT_REQUIRED;
    private final boolean mDefaultIsNonStrongBiometricAllowed = true;

    @Mock
    private Context mContext;
    @Mock
    private LockSettingsStrongAuth.Injector mInjector;
    @Mock
    private AlarmManager mAlarmManager;
    @Mock
    private DevicePolicyManager mDPM;

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

        when(mInjector.getAlarmManager(mContext)).thenReturn(mAlarmManager);
        when(mInjector.getDefaultStrongAuthFlags(mContext)).thenReturn(mDefaultStrongAuthFlags);
        when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(mDPM);

        mStrongAuth = new LockSettingsStrongAuth(mContext, mInjector);
    }

    @Test
    public void testScheduleNonStrongBiometricIdleTimeout() {
        final long nextAlarmTime = 1000;
        when(mInjector.getNextAlarmTimeMs(DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS))
                .thenReturn(nextAlarmTime);
        mStrongAuth.scheduleNonStrongBiometricIdleTimeout(PRIMARY_USER_ID);

        waitForIdle();
        NonStrongBiometricIdleTimeoutAlarmListener alarm = mStrongAuth
                .mNonStrongBiometricIdleTimeoutAlarmListener.get(PRIMARY_USER_ID);
        // verify that a new alarm for idle timeout is added for the user
        assertNotNull(alarm);
        // verify that the alarm is scheduled
        verifyAlarm(nextAlarmTime, NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG, alarm);
    }

    @Test
    public void testSetIsNonStrongBiometricAllowed_disallowed() {
        mStrongAuth.setIsNonStrongBiometricAllowed(false /* allowed */, PRIMARY_USER_ID);

        waitForIdle();
        // verify that unlocking with non-strong biometrics is not allowed
        assertFalse(mStrongAuth.mIsNonStrongBiometricAllowedForUser
                .get(PRIMARY_USER_ID, mDefaultIsNonStrongBiometricAllowed));
    }

    @Test
    public void testReportSuccessfulBiometricUnlock_nonStrongBiometric_fallbackTimeout() {
        final long nextAlarmTime = 1000;
        when(mInjector.getNextAlarmTimeMs(DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS))
                .thenReturn(nextAlarmTime);
        mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);

        waitForIdle();
        NonStrongBiometricTimeoutAlarmListener alarm =
                mStrongAuth.mNonStrongBiometricTimeoutAlarmListener.get(PRIMARY_USER_ID);
        // verify that a new alarm for fallback timeout is added for the user
        assertNotNull(alarm);
        // verify that the alarm is scheduled
        verifyAlarm(nextAlarmTime, NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG, alarm);
    }

    @Test
    public void testRequireStrongAuth_nonStrongBiometric_fallbackTimeout() {
        mStrongAuth.requireStrongAuth(
                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT /* strongAuthReason */,
                PRIMARY_USER_ID);

        waitForIdle();
        // verify that the StrongAuthFlags for the user contains the expected flag
        final int expectedFlag = STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
        verifyStrongAuthFlags(expectedFlag, PRIMARY_USER_ID);
    }

    @Test
    public void testReportSuccessfulBiometricUnlock_nonStrongBiometric_cancelIdleTimeout() {
        // lock device and schedule an alarm for non-strong biometric idle timeout
        mStrongAuth.scheduleNonStrongBiometricIdleTimeout(PRIMARY_USER_ID);
        // unlock with non-strong biometric
        mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);

        waitForIdle();

        // verify that the current alarm for idle timeout is cancelled after a successful unlock
        verify(mAlarmManager).cancel(any(NonStrongBiometricIdleTimeoutAlarmListener.class));
    }

    @Test
    public void testReportSuccessfulBiometricUnlock_strongBio_cancelAlarmsAndAllowNonStrongBio() {
        setupAlarms(PRIMARY_USER_ID);
        mStrongAuth.reportSuccessfulBiometricUnlock(true /* isStrongBiometric */, PRIMARY_USER_ID);

        waitForIdle();
        // verify that unlocking with strong biometric cancels alarms for fallback and idle timeout
        // and re-allow unlocking with non-strong biometric
        verifyAlarmsCancelledAndNonStrongBiometricAllowed(PRIMARY_USER_ID);
    }

    @Test
    public void testReportSuccessfulStrongAuthUnlock_schedulePrimaryAuthTimeout() {
        final long nextAlarmTime = 1000;
        when(mInjector.getNextAlarmTimeMs(mDPM.getRequiredStrongAuthTimeout(null, PRIMARY_USER_ID)))
                .thenReturn(nextAlarmTime);
        mStrongAuth.reportSuccessfulStrongAuthUnlock(PRIMARY_USER_ID);

        waitForIdle();
        StrongAuthTimeoutAlarmListener alarm =
                mStrongAuth.mStrongAuthTimeoutAlarmListenerForUser.get(PRIMARY_USER_ID);
        // verify that a new alarm for primary auth timeout is added for the user
        assertNotNull(alarm);
        // verify that the alarm is scheduled
        verifyAlarm(nextAlarmTime, STRONG_AUTH_TIMEOUT_ALARM_TAG, alarm);
    }

    @Test
    public void testReportSuccessfulStrongAuthUnlock_cancelAlarmsAndAllowNonStrongBio() {
        setupAlarms(PRIMARY_USER_ID);
        mStrongAuth.reportSuccessfulStrongAuthUnlock(PRIMARY_USER_ID);

        waitForIdle();
        // verify that unlocking with primary auth (PIN/pattern/password) cancels alarms
        // for fallback and idle timeout and re-allow unlocking with non-strong biometric
        verifyAlarmsCancelledAndNonStrongBiometricAllowed(PRIMARY_USER_ID);
    }

    @Test
    public void testFallbackTimeout_convenienceBiometric_weakBiometric() {
        // assume that unlock with convenience biometric
        mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
        // assume that unlock again with weak biometric
        mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);

        waitForIdle();
        // verify that the fallback alarm scheduled when unlocking with convenience biometric is
        // not affected when unlocking again with weak biometric
        verify(mAlarmManager, never()).cancel(any(NonStrongBiometricTimeoutAlarmListener.class));
        assertNotNull(mStrongAuth.mNonStrongBiometricTimeoutAlarmListener.get(PRIMARY_USER_ID));
    }

    private void verifyAlarm(long when, String tag, AlarmManager.OnAlarmListener alarm) {
        verify(mAlarmManager).set(
                eq(AlarmManager.ELAPSED_REALTIME),
                eq(when),
                eq(tag),
                eq(alarm),
                eq(mStrongAuth.mHandler));
    }

    private void verifyStrongAuthFlags(int reason, int userId) {
        final int flags = mStrongAuth.mStrongAuthForUser.get(userId, mDefaultStrongAuthFlags);
        Log.d(TAG, "verifyStrongAuthFlags:"
                + " reason=" + Integer.toHexString(reason)
                + " userId=" + userId
                + " flags=" + Integer.toHexString(flags));
        assertTrue(containsFlag(flags, reason));
    }

    private void setupAlarms(int userId) {
        // schedule (a) an alarm for non-strong biometric fallback timeout and (b) an alarm for
        // non-strong biometric idle timeout, so later we can verify that unlocking with
        // strong biometric or primary auth will cancel those alarms
        mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
        mStrongAuth.scheduleNonStrongBiometricIdleTimeout(PRIMARY_USER_ID);
    }

    private void verifyAlarmsCancelledAndNonStrongBiometricAllowed(int userId) {
        // verify that the current alarm for non-strong biometric fallback timeout is cancelled and
        // removed
        verify(mAlarmManager).cancel(any(NonStrongBiometricTimeoutAlarmListener.class));
        assertNull(mStrongAuth.mNonStrongBiometricTimeoutAlarmListener.get(userId));

        // verify that the current alarm for non-strong biometric idle timeout is cancelled
        verify(mAlarmManager).cancel(any(NonStrongBiometricIdleTimeoutAlarmListener.class));

        // verify that unlocking with non-strong biometrics is allowed
        assertTrue(mStrongAuth.mIsNonStrongBiometricAllowedForUser
                .get(userId, mDefaultIsNonStrongBiometricAllowed));
    }

    private static boolean containsFlag(int haystack, int needle) {
        return (haystack & needle) != 0;
    }

    private static void waitForIdle() {
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
    }
}