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

Commit f0128995 authored by Chandru S's avatar Chandru S Committed by Android (Google) Code Review
Browse files

Merge "Add TrustRepository to provide a source of truth for trust grant changes" into tm-qpr-dev

parents 6dc9f563 25ebd557
Loading
Loading
Loading
Loading
+89 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.
 */

package com.android.keyguard.logging

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.TrustModel
import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject

/** Logging helper for trust repository. */
@SysUISingleton
class TrustRepositoryLogger
@Inject
constructor(
    @KeyguardUpdateMonitorLog private val logBuffer: LogBuffer,
) {
    fun onTrustChanged(
        enabled: Boolean,
        newlyUnlocked: Boolean,
        userId: Int,
        flags: Int,
        trustGrantedMessages: List<String>?
    ) {
        logBuffer.log(
            TAG,
            LogLevel.DEBUG,
            {
                bool1 = enabled
                bool2 = newlyUnlocked
                int1 = userId
                int2 = flags
                str1 = trustGrantedMessages?.joinToString()
            },
            {
                "onTrustChanged enabled: $bool1, newlyUnlocked: $bool2, " +
                    "userId: $int1, flags: $int2, grantMessages: $str1"
            }
        )
    }

    fun trustListenerRegistered() {
        logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#registerTrustListener")
    }

    fun trustListenerUnregistered() {
        logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#unregisterTrustListener")
    }

    fun trustModelEmitted(value: TrustModel) {
        logBuffer.log(
            TAG,
            LogLevel.DEBUG,
            {
                int1 = value.userId
                bool1 = value.isTrusted
            },
            { "trustModel emitted: userId: $int1 isTrusted: $bool1" }
        )
    }

    fun isCurrentUserTrusted(isCurrentUserTrusted: Boolean) {
        logBuffer.log(
            TAG,
            LogLevel.DEBUG,
            { bool1 = isCurrentUserTrusted },
            { "isCurrentUserTrusted emitted: $bool1" }
        )
    }

    companion object {
        const val TAG = "TrustRepositoryLog"
    }
}
+99 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.
 */

package com.android.systemui.keyguard.data.repository

import android.app.trust.TrustManager
import com.android.keyguard.logging.TrustRepositoryLogger
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.model.TrustModel
import com.android.systemui.user.data.repository.UserRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn

/** Encapsulates any state relevant to trust agents and trust grants. */
interface TrustRepository {
    /** Flow representing whether the current user is trusted. */
    val isCurrentUserTrusted: Flow<Boolean>
}

@SysUISingleton
class TrustRepositoryImpl
@Inject
constructor(
    @Application private val applicationScope: CoroutineScope,
    private val userRepository: UserRepository,
    private val trustManager: TrustManager,
    private val logger: TrustRepositoryLogger,
) : TrustRepository {
    private val latestTrustModelForUser = mutableMapOf<Int, TrustModel>()

    private val trust =
        conflatedCallbackFlow {
                val callback =
                    object : TrustManager.TrustListener {
                        override fun onTrustChanged(
                            enabled: Boolean,
                            newlyUnlocked: Boolean,
                            userId: Int,
                            flags: Int,
                            grantMsgs: List<String>?
                        ) {
                            logger.onTrustChanged(enabled, newlyUnlocked, userId, flags, grantMsgs)
                            trySendWithFailureLogging(
                                TrustModel(enabled, userId),
                                TrustRepositoryLogger.TAG,
                                "onTrustChanged"
                            )
                        }

                        override fun onTrustError(message: CharSequence?) = Unit

                        override fun onTrustManagedChanged(enabled: Boolean, userId: Int) = Unit
                    }
                trustManager.registerTrustListener(callback)
                logger.trustListenerRegistered()
                awaitClose {
                    logger.trustListenerUnregistered()
                    trustManager.unregisterTrustListener(callback)
                }
            }
            .onEach {
                latestTrustModelForUser[it.userId] = it
                logger.trustModelEmitted(it)
            }
            .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)

    override val isCurrentUserTrusted: Flow<Boolean>
        get() =
            combine(trust, userRepository.selectedUserInfo, ::Pair)
                .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
                .distinctUntilChanged()
                .onEach { logger.isCurrentUserTrusted(it) }
                .onStart { emit(false) }
}
+25 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.
 */

package com.android.systemui.keyguard.shared.model

/** Represents the trust state */
data class TrustModel(
    /** If true, the system believes the environment to be trusted. */
    val isTrusted: Boolean,
    /** The user, for which the trust changed. */
    val userId: Int,
)
+163 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.
 */

package com.android.systemui.keyguard.data.repository

import android.app.trust.TrustManager
import android.content.pm.UserInfo
import androidx.test.filters.SmallTest
import com.android.keyguard.logging.TrustRepositoryLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogcatEchoTracker
import com.android.systemui.user.data.repository.FakeUserRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
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.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class TrustRepositoryTest : SysuiTestCase() {
    @Mock private lateinit var trustManager: TrustManager
    @Captor private lateinit var listenerCaptor: ArgumentCaptor<TrustManager.TrustListener>
    private lateinit var userRepository: FakeUserRepository
    private lateinit var testScope: TestScope
    private val users = listOf(UserInfo(1, "user 1", 0), UserInfo(2, "user 1", 0))

    private lateinit var underTest: TrustRepository

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        testScope = TestScope()
        userRepository = FakeUserRepository()
        userRepository.setUserInfos(users)

        val logger =
            TrustRepositoryLogger(
                LogBuffer("TestBuffer", 1, mock(LogcatEchoTracker::class.java), false)
            )
        underTest =
            TrustRepositoryImpl(testScope.backgroundScope, userRepository, trustManager, logger)
    }

    @Test
    fun isCurrentUserTrusted_whenTrustChanges_emitsLatestValue() =
        testScope.runTest {
            runCurrent()
            verify(trustManager).registerTrustListener(listenerCaptor.capture())
            val listener = listenerCaptor.value

            val currentUserId = users[0].id
            userRepository.setSelectedUserInfo(users[0])
            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)

            listener.onTrustChanged(true, false, currentUserId, 0, emptyList())
            assertThat(isCurrentUserTrusted()).isTrue()

            listener.onTrustChanged(false, false, currentUserId, 0, emptyList())

            assertThat(isCurrentUserTrusted()).isFalse()
        }

    @Test
    fun isCurrentUserTrusted_isFalse_byDefault() =
        testScope.runTest {
            runCurrent()

            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)

            assertThat(isCurrentUserTrusted()).isFalse()
        }

    @Test
    fun isCurrentUserTrusted_whenTrustChangesForDifferentUser_noop() =
        testScope.runTest {
            runCurrent()
            verify(trustManager).registerTrustListener(listenerCaptor.capture())
            userRepository.setSelectedUserInfo(users[0])
            val listener = listenerCaptor.value

            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
            // current user is trusted.
            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
            // some other user is not trusted.
            listener.onTrustChanged(false, false, users[1].id, 0, emptyList())

            assertThat(isCurrentUserTrusted()).isTrue()
        }

    @Test
    fun isCurrentUserTrusted_whenTrustChangesForCurrentUser_emitsNewValue() =
        testScope.runTest {
            runCurrent()
            verify(trustManager).registerTrustListener(listenerCaptor.capture())
            val listener = listenerCaptor.value
            userRepository.setSelectedUserInfo(users[0])

            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
            assertThat(isCurrentUserTrusted()).isTrue()

            listener.onTrustChanged(false, true, users[0].id, 0, emptyList())
            assertThat(isCurrentUserTrusted()).isFalse()
        }

    @Test
    fun isCurrentUserTrusted_whenUserChangesWithoutRecentTrustChange_defaultsToFalse() =
        testScope.runTest {
            runCurrent()
            verify(trustManager).registerTrustListener(listenerCaptor.capture())
            val listener = listenerCaptor.value
            userRepository.setSelectedUserInfo(users[0])
            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())

            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
            userRepository.setSelectedUserInfo(users[1])

            assertThat(isCurrentUserTrusted()).isFalse()
        }

    @Test
    fun isCurrentUserTrusted_trustChangesFirstBeforeUserInfoChanges_emitsCorrectValue() =
        testScope.runTest {
            runCurrent()
            verify(trustManager).registerTrustListener(listenerCaptor.capture())
            val listener = listenerCaptor.value
            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)

            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
            assertThat(isCurrentUserTrusted()).isFalse()

            userRepository.setSelectedUserInfo(users[0])

            assertThat(isCurrentUserTrusted()).isTrue()
        }
}