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

Commit de08da78 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Automerger Merge Worker
Browse files

Merge "[flexiglass] Auto-confirm, isPatternVisible are "refreshing flow"" into...

Merge "[flexiglass] Auto-confirm, isPatternVisible are "refreshing flow"" into udc-qpr-dev am: 3175375d

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24080896



Change-Id: I3cf8ecaf147b01f77aaff9a7ed0b30c864c669b2
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents f7dceb93 3175375d
Loading
Loading
Loading
Loading
+36 −34
Original line number Diff line number Diff line
@@ -228,6 +228,7 @@ internal fun PatternBouncer(
                }
            }
    ) {
        if (isAnimationEnabled) {
            // Draw lines between dots.
            selectedDots.forEachIndexed { index, dot ->
                if (index > 0) {
@@ -267,6 +268,7 @@ internal fun PatternBouncer(
                    )
                }
            }
        }

        // Draw each dot on the grid.
        dots.forEach { dot ->
+55 −30
Original line number Diff line number Diff line
@@ -14,8 +14,6 @@
 * limitations under the License.
 */

@file:OptIn(ExperimentalCoroutinesApi::class)

package com.android.systemui.authentication.data.repository

import com.android.internal.widget.LockPatternChecker
@@ -29,6 +27,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
import dagger.Binds
import dagger.Module
@@ -38,16 +37,14 @@ import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/** Defines interface for classes that can access authentication-related application state. */
@@ -156,31 +153,17 @@ constructor(
    }

    override val isAutoConfirmEnabled: StateFlow<Boolean> =
        userRepository.selectedUserInfo
            .map { it.id }
            .flatMapLatest { userId ->
                flow { emit(lockPatternUtils.isAutoPinConfirmEnabled(userId)) }
                    .flowOn(backgroundDispatcher)
            }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.WhileSubscribed(),
        refreshingFlow(
            initialValue = false,
            getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled,
        )

    override val hintedPinLength: Int = 6

    override val isPatternVisible: StateFlow<Boolean> =
        userRepository.selectedUserInfo
            .map { it.id }
            .flatMapLatest { userId ->
                flow { emit(lockPatternUtils.isVisiblePatternEnabled(userId)) }
                    .flowOn(backgroundDispatcher)
            }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.WhileSubscribed(),
        refreshingFlow(
            initialValue = true,
            getFreshValue = lockPatternUtils::isVisiblePatternEnabled,
        )

    private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
@@ -276,6 +259,48 @@ constructor(
            )
        }
    }

    /**
     * Returns a [StateFlow] that's automatically kept fresh. The passed-in [getFreshValue] is
     * invoked on a background thread every time the selected user is changed and every time a new
     * downstream subscriber is added to the flow.
     *
     * Initially, the flow will emit [initialValue] while it refreshes itself in the background by
     * invoking the [getFreshValue] function and emitting the fresh value when that's done.
     *
     * Every time the selected user is changed, the flow will re-invoke [getFreshValue] and emit the
     * new value.
     *
     * Every time a new downstream subscriber is added to the flow it first receives the latest
     * cached value that's either the [initialValue] or the latest previously fetched value. In
     * addition, adding a new downstream subscriber also triggers another [getFreshValue] call and a
     * subsequent emission of that newest value.
     */
    private fun <T> refreshingFlow(
        initialValue: T,
        getFreshValue: suspend (selectedUserId: Int) -> T,
    ): StateFlow<T> {
        val flow = MutableStateFlow(initialValue)
        applicationScope.launch {
            combine(
                    // Emits a value initially and every time the selected user is changed.
                    userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged(),
                    // Emits a value only when the number of downstream subscribers of this flow
                    // increases.
                    flow.subscriptionCount.pairwise(initialValue = 0).filter { (previous, current)
                        ->
                        current > previous
                    },
                ) { selectedUserId, _ ->
                    selectedUserId
                }
                .collect { selectedUserId ->
                    flow.value = withContext(backgroundDispatcher) { getFreshValue(selectedUserId) }
                }
        }

        return flow.asStateFlow()
    }
}

@Module
+3 −1
Original line number Diff line number Diff line
@@ -104,7 +104,9 @@ constructor(
            }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.Eagerly,
                // Make sure this is kept as WhileSubscribed or we can run into a bug where the
                // downstream continues to receive old/stale/cached values.
                started = SharingStarted.WhileSubscribed(),
                initialValue = null,
            )

+3 −1
Original line number Diff line number Diff line
@@ -60,7 +60,9 @@ class PinBouncerViewModel(
            }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.Eagerly,
                // Make sure this is kept as WhileSubscribed or we can run into a bug where the
                // downstream continues to receive old/stale/cached values.
                started = SharingStarted.WhileSubscribed(),
                initialValue = ActionButtonAppearance.Hidden,
            )

+113 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 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.
 */

@file:OptIn(ExperimentalCoroutinesApi::class)

package com.android.systemui.authentication.data.repository

import android.content.pm.UserInfo
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(JUnit4::class)
class AuthenticationRepositoryTest : SysuiTestCase() {

    @Mock private lateinit var lockPatternUtils: LockPatternUtils

    private val testUtils = SceneTestUtils(this)
    private val testScope = testUtils.testScope
    private val userRepository = FakeUserRepository()

    private lateinit var underTest: AuthenticationRepository

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        userRepository.setUserInfos(USER_INFOS)
        runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }

        underTest =
            AuthenticationRepositoryImpl(
                applicationScope = testScope.backgroundScope,
                getSecurityMode = { KeyguardSecurityModel.SecurityMode.PIN },
                backgroundDispatcher = testUtils.testDispatcher,
                userRepository = userRepository,
                keyguardRepository = testUtils.keyguardRepository,
                lockPatternUtils = lockPatternUtils,
            )
    }

    @Test
    fun isAutoConfirmEnabled() =
        testScope.runTest {
            whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[0].id)).thenReturn(true)
            whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[1].id)).thenReturn(false)

            val values by collectValues(underTest.isAutoConfirmEnabled)
            assertThat(values.first()).isFalse()
            assertThat(values.last()).isTrue()

            userRepository.setSelectedUserInfo(USER_INFOS[1])
            assertThat(values.last()).isFalse()
        }

    @Test
    fun isPatternVisible() =
        testScope.runTest {
            whenever(lockPatternUtils.isVisiblePatternEnabled(USER_INFOS[0].id)).thenReturn(false)
            whenever(lockPatternUtils.isVisiblePatternEnabled(USER_INFOS[1].id)).thenReturn(true)

            val values by collectValues(underTest.isPatternVisible)
            assertThat(values.first()).isTrue()
            assertThat(values.last()).isFalse()

            userRepository.setSelectedUserInfo(USER_INFOS[1])
            assertThat(values.last()).isTrue()
        }

    companion object {
        private val USER_INFOS =
            listOf(
                UserInfo(
                    /* id= */ 100,
                    /* name= */ "First user",
                    /* flags= */ 0,
                ),
                UserInfo(
                    /* id= */ 101,
                    /* name= */ "Second user",
                    /* flags= */ 0,
                ),
            )
    }
}