Adds power repository and interactor.
UserSwitcherActivity refactor: CL 3/7 Adds a repository and an interactor to expose screen on/off state to the rest of the app. This is needed by the user switcher as it finishes the activity if the screen turns off. Bug: 243844359 Test: Included unit tests. Change-Id: If428cae7be2c25639d43a56717b7e6e9f0b86bc5
This commit is contained in:
@@ -20,13 +20,18 @@ import com.android.systemui.power.EnhancedEstimates;
|
||||
import com.android.systemui.power.EnhancedEstimatesImpl;
|
||||
import com.android.systemui.power.PowerNotificationWarnings;
|
||||
import com.android.systemui.power.PowerUI;
|
||||
import com.android.systemui.power.data.repository.PowerRepositoryModule;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
|
||||
|
||||
/** Dagger Module for code in the power package. */
|
||||
@Module
|
||||
@Module(
|
||||
includes = {
|
||||
PowerRepositoryModule.class,
|
||||
}
|
||||
)
|
||||
public interface PowerModule {
|
||||
/** */
|
||||
@Binds
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.power.data.repository
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.PowerManager
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Defines interface for classes that act as source of truth for power-related data. */
|
||||
interface PowerRepository {
|
||||
/** Whether the device is interactive. Starts with the current state. */
|
||||
val isInteractive: Flow<Boolean>
|
||||
}
|
||||
|
||||
@SysUISingleton
|
||||
class PowerRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
manager: PowerManager,
|
||||
dispatcher: BroadcastDispatcher,
|
||||
) : PowerRepository {
|
||||
|
||||
override val isInteractive: Flow<Boolean> = conflatedCallbackFlow {
|
||||
fun send() {
|
||||
trySendWithFailureLogging(manager.isInteractive, TAG)
|
||||
}
|
||||
|
||||
val receiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
send()
|
||||
}
|
||||
}
|
||||
|
||||
dispatcher.registerReceiver(
|
||||
receiver,
|
||||
IntentFilter().apply {
|
||||
addAction(Intent.ACTION_SCREEN_ON)
|
||||
addAction(Intent.ACTION_SCREEN_OFF)
|
||||
},
|
||||
)
|
||||
send()
|
||||
|
||||
awaitClose { dispatcher.unregisterReceiver(receiver) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PowerRepository"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.power.data.repository
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
||||
@Module
|
||||
interface PowerRepositoryModule {
|
||||
@Binds fun bindRepository(impl: PowerRepositoryImpl): PowerRepository
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.power.domain.interactor
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.power.data.repository.PowerRepository
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Hosts business logic for interacting with the power system. */
|
||||
@SysUISingleton
|
||||
class PowerInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
repository: PowerRepository,
|
||||
) {
|
||||
/** Whether the screen is on or off. */
|
||||
val isInteractive: Flow<Boolean> = repository.isInteractive
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.power.data.repository
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
class FakePowerRepository(
|
||||
initialInteractive: Boolean = true,
|
||||
) : PowerRepository {
|
||||
|
||||
private val _isInteractive = MutableStateFlow(initialInteractive)
|
||||
override val isInteractive: Flow<Boolean> = _isInteractive.asStateFlow()
|
||||
|
||||
fun setInteractive(value: Boolean) {
|
||||
_isInteractive.value = value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.power.data.repository
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.PowerManager
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
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.anyInt
|
||||
import org.mockito.Mockito.isNull
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class PowerRepositoryImplTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var manager: PowerManager
|
||||
@Mock private lateinit var dispatcher: BroadcastDispatcher
|
||||
@Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
|
||||
@Captor private lateinit var filterCaptor: ArgumentCaptor<IntentFilter>
|
||||
|
||||
private lateinit var underTest: PowerRepositoryImpl
|
||||
|
||||
private var isInteractive = true
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
isInteractive = true
|
||||
whenever(manager.isInteractive).then { isInteractive }
|
||||
|
||||
underTest = PowerRepositoryImpl(manager = manager, dispatcher = dispatcher)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isInteractive - registers for broadcasts`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val job = underTest.isInteractive.onEach {}.launchIn(this)
|
||||
|
||||
verifyRegistered()
|
||||
assertThat(filterCaptor.value.hasAction(Intent.ACTION_SCREEN_ON)).isTrue()
|
||||
assertThat(filterCaptor.value.hasAction(Intent.ACTION_SCREEN_OFF)).isTrue()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isInteractive - unregisters from broadcasts`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val job = underTest.isInteractive.onEach {}.launchIn(this)
|
||||
verifyRegistered()
|
||||
|
||||
job.cancel()
|
||||
|
||||
verify(dispatcher).unregisterReceiver(receiverCaptor.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isInteractive - emits initial true value if screen was on`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
isInteractive = true
|
||||
var value: Boolean? = null
|
||||
val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
|
||||
|
||||
verifyRegistered()
|
||||
|
||||
assertThat(value).isTrue()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isInteractive - emits initial false value if screen was off`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
isInteractive = false
|
||||
var value: Boolean? = null
|
||||
val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
|
||||
|
||||
verifyRegistered()
|
||||
|
||||
assertThat(value).isFalse()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isInteractive - emits true when the screen turns on`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
var value: Boolean? = null
|
||||
val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
|
||||
verifyRegistered()
|
||||
|
||||
isInteractive = true
|
||||
receiverCaptor.value.onReceive(context, Intent(Intent.ACTION_SCREEN_ON))
|
||||
|
||||
assertThat(value).isTrue()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isInteractive - emits false when the screen turns off`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
var value: Boolean? = null
|
||||
val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
|
||||
verifyRegistered()
|
||||
|
||||
isInteractive = false
|
||||
receiverCaptor.value.onReceive(context, Intent(Intent.ACTION_SCREEN_OFF))
|
||||
|
||||
assertThat(value).isFalse()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isInteractive - emits correctly over time`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val values = mutableListOf<Boolean>()
|
||||
val job = underTest.isInteractive.onEach(values::add).launchIn(this)
|
||||
verifyRegistered()
|
||||
|
||||
isInteractive = false
|
||||
receiverCaptor.value.onReceive(context, Intent(Intent.ACTION_SCREEN_OFF))
|
||||
isInteractive = true
|
||||
receiverCaptor.value.onReceive(context, Intent(Intent.ACTION_SCREEN_ON))
|
||||
isInteractive = false
|
||||
receiverCaptor.value.onReceive(context, Intent(Intent.ACTION_SCREEN_OFF))
|
||||
|
||||
assertThat(values).isEqualTo(listOf(true, false, true, false))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
private fun verifyRegistered() {
|
||||
// We must verify with all arguments, even those that are optional because they have default
|
||||
// values because Mockito is forcing us to. Once we can use mockito-kotlin, we should be
|
||||
// able to remove this.
|
||||
verify(dispatcher)
|
||||
.registerReceiver(
|
||||
capture(receiverCaptor),
|
||||
capture(filterCaptor),
|
||||
isNull(),
|
||||
isNull(),
|
||||
anyInt(),
|
||||
isNull(),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.power.domain.interactor
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.power.data.repository.FakePowerRepository
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class PowerInteractorTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var underTest: PowerInteractor
|
||||
private lateinit var repository: FakePowerRepository
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
repository =
|
||||
FakePowerRepository(
|
||||
initialInteractive = true,
|
||||
)
|
||||
underTest = PowerInteractor(repository = repository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isInteractive - screen turns off`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
repository.setInteractive(true)
|
||||
var value: Boolean? = null
|
||||
val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
|
||||
|
||||
repository.setInteractive(false)
|
||||
|
||||
assertThat(value).isFalse()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isInteractive - becomes interactive`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
repository.setInteractive(false)
|
||||
var value: Boolean? = null
|
||||
val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
|
||||
|
||||
repository.setInteractive(true)
|
||||
|
||||
assertThat(value).isTrue()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user