diff options
| author | 2023-01-18 17:02:44 +0000 | |
|---|---|---|
| committer | 2023-01-18 17:02:44 +0000 | |
| commit | f0128995740fe3ccff8fbb2a20e0fc18c4e4baed (patch) | |
| tree | 3b83fac3cfd87f904d1a2b97c56795bd5ba2e0dd | |
| parent | 6dc9f563f855014f380708e60b0545fab21bf81c (diff) | |
| parent | 25ebd55719eb1d0921a1f6bf1a390e1125faca76 (diff) | |
Merge "Add TrustRepository to provide a source of truth for trust grant changes" into tm-qpr-dev
4 files changed, 376 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt new file mode 100644 index 000000000000..249b3fe97d81 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt @@ -0,0 +1,89 @@ +/* + * 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" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt new file mode 100644 index 000000000000..d90f328719bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt @@ -0,0 +1,99 @@ +/* + * 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) } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt new file mode 100644 index 000000000000..4fd14b1ce087 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt @@ -0,0 +1,25 @@ +/* + * 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, +) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt new file mode 100644 index 000000000000..4b069051423c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt @@ -0,0 +1,163 @@ +/* + * 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() + } +} |