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

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

Change-Id: Ia486fbde0e761669d97d68647c6cd083d0b3b0c2
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Chandru S
2023-01-18 17:06:45 +00:00
committed by Automerger Merge Worker
4 changed files with 376 additions and 0 deletions

View File

@@ -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"
}
}

View File

@@ -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) }
}

View File

@@ -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,
)

View File

@@ -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()
}
}