Merge "Add TrustRepository to provide a source of truth for trust grant changes" into tm-qpr-dev
This commit is contained in:
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) }
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user