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

Commit 02438edd authored by Kholoud Mohamed's avatar Kholoud Mohamed
Browse files

Fix incorrect password attempts messaging on headless

Bug: 257276847
Test: manual testing: provisioned a headless device to single_user DO
mode, set a max attempts for wipe policy, set a pin on the device,
attempted an incorrect pin and verified the warning message is for
wiping the device rather than removing the user.
Flag: ACONFIG android.app.admin.flags.headless_single_user_fixes DISABLED

Change-Id: I73daebe6bac421dfeaf750abfe2a96d60758bd7b
parent 17ba0c73
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -149,6 +149,7 @@ android_library {
        "device_state_flags_lib",
        "kotlinx_coroutines_android",
        "kotlinx_coroutines",
        "kotlinx_coroutines_guava",
        "//frameworks/libs/systemui:iconloader_base",
        "SystemUI-tags",
        "SystemUI-proto",
@@ -169,6 +170,7 @@ android_library {
        "androidx.compose.material_material-icons-extended",
        "androidx.activity_activity-compose",
        "androidx.compose.animation_animation-graphics",
        "device_policy_aconfig_flags_lib",
    ],
    libs: [
        "keepanno-annotations",
@@ -328,6 +330,7 @@ android_library {
        "androidx.activity_activity-compose",
        "androidx.compose.animation_animation-graphics",
        "TraceurCommon",
        "kotlinx_coroutines_guava",
    ],
}

@@ -409,6 +412,7 @@ android_app {
        "//frameworks/libs/systemui:compilelib",
        "SystemUI-tests-base",
        "androidx.compose.runtime_runtime",
        "SystemUI-core",
    ],
    libs: [
        "keepanno-annotations",
+43 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@

package com.android.keyguard

import android.app.admin.DevicePolicyManager
import android.app.admin.flags.Flags as DevicePolicyFlags
import android.content.res.Configuration
import android.media.AudioManager
import android.telephony.TelephonyManager
@@ -148,6 +150,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
    @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
    @Mock private lateinit var postureController: DevicePostureController
    @Mock private lateinit var devicePolicyManager: DevicePolicyManager

    @Captor
    private lateinit var swipeListenerArgumentCaptor:
@@ -274,6 +277,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
                mSelectedUserInteractor,
                deviceProvisionedController,
                faceAuthAccessibilityDelegate,
                devicePolicyManager,
                keyguardTransitionInteractor,
                { primaryBouncerInteractor },
            ) {
@@ -929,6 +933,45 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
        verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any())
    }

    @Test
    fun showAlmostAtWipeDialog_calledOnMainUser_setsCorrectUserType() {
        mSetFlagsRule.enableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
        val mainUserId = 10

        underTest.showMessageForFailedUnlockAttempt(
            /* userId = */ mainUserId,
            /* expiringUserId = */ mainUserId,
            /* mainUserId = */ mainUserId,
            /* remainingBeforeWipe = */ 1,
            /* failedAttempts = */ 1
        )

        verify(view)
            .showAlmostAtWipeDialog(any(), any(), eq(KeyguardSecurityContainer.USER_TYPE_PRIMARY))
    }

    @Test
    fun showAlmostAtWipeDialog_calledOnNonMainUser_setsCorrectUserType() {
        mSetFlagsRule.enableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
        val secondaryUserId = 10
        val mainUserId = 0

        underTest.showMessageForFailedUnlockAttempt(
            /* userId = */ secondaryUserId,
            /* expiringUserId = */ secondaryUserId,
            /* mainUserId = */ mainUserId,
            /* remainingBeforeWipe = */ 1,
            /* failedAttempts = */ 1
        )

        verify(view)
            .showAlmostAtWipeDialog(
                any(),
                any(),
                eq(KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER)
            )
    }

    private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
        get() {
            underTest.onViewAttached()
+8 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.systemui.authentication.domain.interactor

import android.app.admin.DevicePolicyManager
import android.app.admin.flags.Flags as DevicePolicyFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
@@ -32,6 +34,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -410,12 +414,16 @@ class AuthenticationInteractorTest : SysuiTestCase() {
        }

    @Test
    @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
    fun upcomingWipe() =
        testScope.runTest {
            val upcomingWipe by collectLastValue(underTest.upcomingWipe)
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
            val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
            val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
            kosmos.fakeUserRepository.asMainUser()
            kosmos.fakeAuthenticationRepository.profileWithMinFailedUnlockAttemptsForWipe =
                FakeUserRepository.MAIN_USER_ID

            underTest.authenticate(correctPin)
            assertThat(upcomingWipe).isNull()
+60 −22
Original line number Diff line number Diff line
@@ -35,12 +35,14 @@ import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;

import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.admin.flags.Flags;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.media.AudioManager;
import android.metrics.LogMaker;
import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
@@ -97,12 +99,15 @@ import com.android.systemui.util.ViewController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;

import com.google.common.util.concurrent.ListenableFuture;

import dagger.Lazy;

import kotlinx.coroutines.Job;

import java.io.File;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;

import javax.inject.Inject;
import javax.inject.Provider;
@@ -135,6 +140,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
    private final BouncerMessageInteractor mBouncerMessageInteractor;
    private int mTranslationY;
    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
    private final DevicePolicyManager mDevicePolicyManager;
    // Whether the volume keys should be handled by keyguard. If true, then
    // they will be handled here for specific media types such as music, otherwise
    // the audio service will bring up the volume dialog.
@@ -461,6 +467,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
            SelectedUserInteractor selectedUserInteractor,
            DeviceProvisionedController deviceProvisionedController,
            FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
            DevicePolicyManager devicePolicyManager,
            KeyguardTransitionInteractor keyguardTransitionInteractor,
            Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
            Provider<DeviceEntryInteractor> deviceEntryInteractor
@@ -496,6 +503,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
        mDeviceProvisionedController = deviceProvisionedController;
        mPrimaryBouncerInteractor = primaryBouncerInteractor;
        mDevicePolicyManager = devicePolicyManager;
    }

    @Override
@@ -1106,45 +1114,75 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard

        if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);

        final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
        final int failedAttemptsBeforeWipe =
                dpm.getMaximumFailedPasswordsForWipe(null, userId);
                mDevicePolicyManager.getMaximumFailedPasswordsForWipe(null, userId);

        final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0
                ? (failedAttemptsBeforeWipe - failedAttempts)
                : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
        if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
            // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
            // N attempts. Once we get below the grace period, we post this dialog every time as a
            // clear warning until the deletion fires.
            // Check which profile has the strictest policy for failed password attempts
            final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId);
            // The user has installed a DevicePolicyManager that requests a
            // user/profile to be wiped N attempts. Once we get below the grace period,
            // we post this dialog every time as a clear warning until the deletion
            // fires. Check which profile has the strictest policy for failed password
            // attempts.
            final int expiringUser =
                    mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(userId);
            ListenableFuture<Integer> getMainUserIdFuture =
                    mSelectedUserInteractor.getMainUserIdAsync();
            getMainUserIdFuture.addListener(() -> {
                Looper.prepare();
                Integer mainUser;
                try {
                    mainUser = getMainUserIdFuture.get();
                } catch (InterruptedException | ExecutionException e) {
                    // Nothing we can, keep using the system user as the primary
                    // user.
                    mainUser = null;
                }
                showMessageForFailedUnlockAttempt(
                        userId, expiringUser, mainUser, remainingBeforeWipe, failedAttempts);
                Looper.loop();
            }, ThreadUtils.getBackgroundExecutor());
        }
        mLockPatternUtils.reportFailedPasswordAttempt(userId);
        if (timeoutMs > 0) {
            mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
            if (!com.android.systemui.Flags.revampedBouncerMessages()) {
                mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils,
                        mSecurityModel.getSecurityMode(userId));
            }
        }
    }

    @VisibleForTesting
    void showMessageForFailedUnlockAttempt(int userId, int expiringUserId, Integer mainUserId,
            int remainingBeforeWipe, int failedAttempts) {
        int userType = USER_TYPE_PRIMARY;
            if (expiringUser == userId) {
        if (expiringUserId == userId) {
            int primaryUser = UserHandle.USER_SYSTEM;
            if (Flags.headlessSingleUserFixes()) {
                if (mainUserId != null) {
                    primaryUser = mainUserId;
                }
            }
            // TODO: http://b/23522538
                if (expiringUser != UserHandle.USER_SYSTEM) {
            if (expiringUserId != primaryUser) {
                userType = USER_TYPE_SECONDARY_USER;
            }
            } else if (expiringUser != UserHandle.USER_NULL) {
        } else if (expiringUserId != UserHandle.USER_NULL) {
            userType = USER_TYPE_WORK_PROFILE;
        } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
        if (remainingBeforeWipe > 0) {
                mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
            mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe,
                    userType);
        } else {
            // Too many attempts. The device will be wiped shortly.
                Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
            Slog.i(TAG, "Too many unlock attempts; user " + expiringUserId
                    + " will be wiped!");
            mView.showWipeDialog(failedAttempts, userType);
        }
    }
        mLockPatternUtils.reportFailedPasswordAttempt(userId);
        if (timeoutMs > 0) {
            mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
            if (!com.android.systemui.Flags.revampedBouncerMessages()) {
                mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils,
                        mSecurityModel.getSecurityMode(userId));
            }
        }
    }

    private void getCurrentSecurityController(
            KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback) {
+8 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.authentication.domain.interactor

import android.app.admin.flags.Flags
import android.os.UserHandle
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternView
@@ -288,9 +289,15 @@ constructor(
    private suspend fun getWipeTarget(): WipeTarget {
        // Check which profile has the strictest policy for failed authentication attempts.
        val userToBeWiped = repository.getProfileWithMinFailedUnlockAttemptsForWipe()
        val primaryUser =
            if (Flags.headlessSingleUserFixes()) {
                selectedUserInteractor.getMainUserId() ?: UserHandle.USER_SYSTEM
            } else {
                UserHandle.USER_SYSTEM
            }
        return when (userToBeWiped) {
            selectedUserInteractor.getSelectedUserId() ->
                if (userToBeWiped == UserHandle.USER_SYSTEM) {
                if (userToBeWiped == primaryUser) {
                    WipeTarget.WholeDevice
                } else {
                    WipeTarget.User
Loading