diff options
12 files changed, 469 insertions, 335 deletions
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt index 8323d0971ad7..f005bab55de6 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt @@ -23,6 +23,8 @@ import com.android.systemui.util.settings.SettingsUtilModule import dagger.Binds import dagger.Module import dagger.Provides +import dagger.multibindings.IntoSet +import javax.inject.Named @Module(includes = [ FeatureFlagsDebugStartableModule::class, @@ -35,7 +37,8 @@ abstract class FlagsModule { abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags @Binds - abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter + @IntoSet + abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition @Module companion object { @@ -44,5 +47,10 @@ abstract class FlagsModule { fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager { return FlagManager(context, handler) } + + @JvmStatic + @Provides + @Named(ConditionalRestarter.RESTART_DELAY) + fun provideRestartDelaySec(): Long = 1 } } diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt index 87beff76290d..927d4604b823 100644 --- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt +++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt @@ -18,6 +18,9 @@ package com.android.systemui.flags import dagger.Binds import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoSet +import javax.inject.Named @Module(includes = [ FeatureFlagsReleaseStartableModule::class, @@ -29,5 +32,18 @@ abstract class FlagsModule { abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags @Binds - abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter + @IntoSet + abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition + + @Binds + @IntoSet + abstract fun bindsPluggedInCondition(impl: PluggedInCondition): ConditionalRestarter.Condition + + @Module + companion object { + @JvmStatic + @Provides + @Named(ConditionalRestarter.RESTART_DELAY) + fun provideRestartDelaySec(): Long = 30 + } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt new file mode 100644 index 000000000000..b20e33a63776 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt @@ -0,0 +1,103 @@ +/* + * 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.flags + +import android.util.Log +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Named +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +/** Restarts the process after all passed in [Condition]s are true. */ +class ConditionalRestarter +@Inject +constructor( + private val systemExitRestarter: SystemExitRestarter, + private val conditions: Set<@JvmSuppressWildcards Condition>, + @Named(RESTART_DELAY) private val restartDelaySec: Long, + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) : Restarter { + + private var restartJob: Job? = null + private var pendingReason = "" + private var androidRestartRequested = false + + override fun restartSystemUI(reason: String) { + Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting when idle.") + scheduleRestart(reason) + } + + override fun restartAndroid(reason: String) { + Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting when idle.") + androidRestartRequested = true + scheduleRestart(reason) + } + + private fun scheduleRestart(reason: String = "") { + pendingReason = if (reason.isEmpty()) pendingReason else reason + + if (conditions.all { c -> c.canRestartNow(this::scheduleRestart) }) { + if (restartJob == null) { + restartJob = + applicationScope.launch(backgroundDispatcher) { + delay(TimeUnit.SECONDS.toMillis(restartDelaySec)) + restartNow() + } + } + } else { + restartJob?.cancel() + restartJob = null + } + } + + private fun restartNow() { + if (androidRestartRequested) { + systemExitRestarter.restartAndroid(pendingReason) + } else { + systemExitRestarter.restartSystemUI(pendingReason) + } + } + + interface Condition { + /** + * Should return true if the system is ready to restart. + * + * A call to this function means that we want to restart and are waiting for this condition + * to return true. + * + * retryFn should be cached if it is _not_ ready to restart, and later called when it _is_ + * ready to restart. At that point, this method will be called again to verify that the + * system is ready. + * + * Multiple calls to an instance of this method may happen for a single restart attempt if + * multiple [Condition]s are being checked. If any one [Condition] returns false, all the + * [Condition]s will need to be rechecked on the next restart attempt. + */ + fun canRestartNow(retryFn: () -> Unit): Boolean + } + + companion object { + const val RESTART_DELAY = "restarter_restart_delay" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt deleted file mode 100644 index a6956a443e46..000000000000 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.flags - -import android.util.Log -import com.android.systemui.keyguard.WakefulnessLifecycle -import javax.inject.Inject - -/** Restarts SystemUI when the screen is locked. */ -class FeatureFlagsDebugRestarter -@Inject -constructor( - private val wakefulnessLifecycle: WakefulnessLifecycle, - private val systemExitRestarter: SystemExitRestarter, -) : Restarter { - - private var androidRestartRequested = false - private var pendingReason = "" - - val observer = - object : WakefulnessLifecycle.Observer { - override fun onFinishedGoingToSleep() { - Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change") - restartNow() - } - } - - override fun restartSystemUI(reason: String) { - Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.") - Log.i(FeatureFlagsDebug.TAG, reason) - scheduleRestart(reason) - } - - override fun restartAndroid(reason: String) { - Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.") - androidRestartRequested = true - scheduleRestart(reason) - } - - fun scheduleRestart(reason: String) { - pendingReason = reason - if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) { - restartNow() - } else { - wakefulnessLifecycle.addObserver(observer) - } - } - - private fun restartNow() { - if (androidRestartRequested) { - systemExitRestarter.restartAndroid(pendingReason) - } else { - systemExitRestarter.restartSystemUI(pendingReason) - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt deleted file mode 100644 index c08266caf147..000000000000 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.flags - -import android.util.Log -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.keyguard.WakefulnessLifecycle -import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP -import com.android.systemui.statusbar.policy.BatteryController -import com.android.systemui.util.concurrency.DelayableExecutor -import java.util.concurrent.TimeUnit -import javax.inject.Inject - -/** Restarts SystemUI when the device appears idle. */ -class FeatureFlagsReleaseRestarter -@Inject -constructor( - private val wakefulnessLifecycle: WakefulnessLifecycle, - private val batteryController: BatteryController, - @Background private val bgExecutor: DelayableExecutor, - private val systemExitRestarter: SystemExitRestarter -) : Restarter { - var listenersAdded = false - var pendingRestart: Runnable? = null - private var pendingReason = "" - var androidRestartRequested = false - - val observer = - object : WakefulnessLifecycle.Observer { - override fun onFinishedGoingToSleep() { - scheduleRestart(pendingReason) - } - } - - val batteryCallback = - object : BatteryController.BatteryStateChangeCallback { - override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { - scheduleRestart(pendingReason) - } - } - - override fun restartSystemUI(reason: String) { - Log.d( - FeatureFlagsDebug.TAG, - "SystemUI Restart requested. Restarting when plugged in and idle." - ) - scheduleRestart(reason) - } - - override fun restartAndroid(reason: String) { - Log.d( - FeatureFlagsDebug.TAG, - "Android Restart requested. Restarting when plugged in and idle." - ) - androidRestartRequested = true - scheduleRestart(reason) - } - - private fun scheduleRestart(reason: String) { - // Don't bother adding listeners twice. - pendingReason = reason - if (!listenersAdded) { - listenersAdded = true - wakefulnessLifecycle.addObserver(observer) - batteryController.addCallback(batteryCallback) - } - if ( - wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn - ) { - if (pendingRestart == null) { - pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS) - } - } else if (pendingRestart != null) { - pendingRestart?.run() - pendingRestart = null - } - } - - private fun restartNow() { - Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change") - if (androidRestartRequested) { - systemExitRestarter.restartAndroid(pendingReason) - } else { - systemExitRestarter.restartSystemUI(pendingReason) - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt index 0054d266c283..3c5012559a89 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.flags +import dagger.Binds import dagger.Module import dagger.Provides import javax.inject.Named @@ -22,6 +23,8 @@ import javax.inject.Named /** Module containing shared code for all FeatureFlag implementations. */ @Module interface FlagsCommonModule { + @Binds fun bindsRestarter(impl: ConditionalRestarter): Restarter + companion object { const val ALL_FLAGS = "all_flags" diff --git a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt new file mode 100644 index 000000000000..3120638cb17f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt @@ -0,0 +1,49 @@ +/* + * 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.flags + +import com.android.systemui.statusbar.policy.BatteryController +import javax.inject.Inject + +/** Returns true when the device is plugged in. */ +class PluggedInCondition +@Inject +constructor( + private val batteryController: BatteryController, +) : ConditionalRestarter.Condition { + + var listenersAdded = false + var retryFn: (() -> Unit)? = null + + val batteryCallback = + object : BatteryController.BatteryStateChangeCallback { + override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { + retryFn?.invoke() + } + } + + override fun canRestartNow(retryFn: () -> Unit): Boolean { + if (!listenersAdded) { + listenersAdded = true + batteryController.addCallback(batteryCallback) + } + + this.retryFn = retryFn + + return batteryController.isPluggedIn + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt new file mode 100644 index 000000000000..49e61afbdcd6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt @@ -0,0 +1,49 @@ +/* + * 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.flags + +import com.android.systemui.keyguard.WakefulnessLifecycle +import javax.inject.Inject + +/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */ +class ScreenIdleCondition +@Inject +constructor( + private val wakefulnessLifecycle: WakefulnessLifecycle, +) : ConditionalRestarter.Condition { + + var listenersAdded = false + var retryFn: (() -> Unit)? = null + + val observer = + object : WakefulnessLifecycle.Observer { + override fun onFinishedGoingToSleep() { + retryFn?.invoke() + } + } + + override fun canRestartNow(retryFn: () -> Unit): Boolean { + if (!listenersAdded) { + listenersAdded = true + wakefulnessLifecycle.addObserver(observer) + } + + this.retryFn = retryFn + + return wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt new file mode 100644 index 000000000000..0e14591c5f53 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt @@ -0,0 +1,140 @@ +/* + * 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.flags + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +/** + * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()! + */ +@SmallTest +class ConditionalRestarterTest : SysuiTestCase() { + private lateinit var restarter: ConditionalRestarter + + @Mock private lateinit var systemExitRestarter: SystemExitRestarter + + val restartDelayMs = 0L + val dispatcher = StandardTestDispatcher() + val testScope = TestScope(dispatcher) + + val conditionA = FakeCondition() + val conditionB = FakeCondition() + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + restarter = + ConditionalRestarter( + systemExitRestarter, + setOf(conditionA, conditionB), + restartDelayMs, + testScope, + dispatcher + ) + } + + @Test + fun restart_ImmediatelySatisfied() = + testScope.runTest { + conditionA.canRestart = true + conditionB.canRestart = true + restarter.restartSystemUI("Restart for test") + advanceUntilIdle() + verify(systemExitRestarter).restartSystemUI(any()) + } + + @Test + fun restart_WaitsForConditionA() = + testScope.runTest { + conditionA.canRestart = false + conditionB.canRestart = true + + restarter.restartSystemUI("Restart for test") + advanceUntilIdle() + // No restart occurs yet. + verify(systemExitRestarter, never()).restartSystemUI(any()) + + conditionA.canRestart = true + conditionA.retryFn?.invoke() + advanceUntilIdle() + verify(systemExitRestarter).restartSystemUI(any()) + } + + @Test + fun restart_WaitsForConditionB() = + testScope.runTest { + conditionA.canRestart = true + conditionB.canRestart = false + + restarter.restartSystemUI("Restart for test") + advanceUntilIdle() + // No restart occurs yet. + verify(systemExitRestarter, never()).restartSystemUI(any()) + + conditionB.canRestart = true + conditionB.retryFn?.invoke() + advanceUntilIdle() + verify(systemExitRestarter).restartSystemUI(any()) + } + + @Test + fun restart_WaitsForAllConditions() = + testScope.runTest { + conditionA.canRestart = true + conditionB.canRestart = false + + restarter.restartSystemUI("Restart for test") + advanceUntilIdle() + // No restart occurs yet. + verify(systemExitRestarter, never()).restartSystemUI(any()) + + // B becomes true, but A is now false + conditionA.canRestart = false + conditionB.canRestart = true + conditionB.retryFn?.invoke() + advanceUntilIdle() + // No restart occurs yet. + verify(systemExitRestarter, never()).restartSystemUI(any()) + + conditionA.canRestart = true + conditionA.retryFn?.invoke() + advanceUntilIdle() + verify(systemExitRestarter).restartSystemUI(any()) + } + + class FakeCondition : ConditionalRestarter.Condition { + var retryFn: (() -> Unit)? = null + var canRestart = false + + override fun canRestartNow(retryFn: () -> Unit): Boolean { + this.retryFn = retryFn + + return canRestart + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt deleted file mode 100644 index 6060afe495f5..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2021 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.flags - -import android.test.suitebuilder.annotation.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.keyguard.WakefulnessLifecycle -import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP -import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE -import com.android.systemui.statusbar.policy.BatteryController -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.time.FakeSystemClock -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Test -import org.mockito.ArgumentCaptor -import org.mockito.Mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations - -/** - * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()! - */ -@SmallTest -class FeatureFlagsReleaseRestarterTest : SysuiTestCase() { - private lateinit var restarter: FeatureFlagsReleaseRestarter - - @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock private lateinit var batteryController: BatteryController - @Mock private lateinit var systemExitRestarter: SystemExitRestarter - private val executor = FakeExecutor(FakeSystemClock()) - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - restarter = - FeatureFlagsReleaseRestarter( - wakefulnessLifecycle, - batteryController, - executor, - systemExitRestarter - ) - } - - @Test - fun testRestart_ScheduledWhenReady() { - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) - whenever(batteryController.isPluggedIn).thenReturn(true) - - assertThat(executor.numPending()).isEqualTo(0) - restarter.restartSystemUI("Restart for test") - assertThat(executor.numPending()).isEqualTo(1) - } - - @Test - fun testRestart_RestartsWhenIdle() { - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) - whenever(batteryController.isPluggedIn).thenReturn(true) - - restarter.restartSystemUI("Restart for test") - verify(systemExitRestarter, never()).restartSystemUI("Restart for test") - executor.advanceClockToLast() - executor.runAllReady() - verify(systemExitRestarter).restartSystemUI(any()) - } - - @Test - fun testRestart_NotScheduledWhenAwake() { - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE) - whenever(batteryController.isPluggedIn).thenReturn(true) - - assertThat(executor.numPending()).isEqualTo(0) - restarter.restartSystemUI("Restart for test") - assertThat(executor.numPending()).isEqualTo(0) - } - - @Test - fun testRestart_NotScheduledWhenNotPluggedIn() { - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) - whenever(batteryController.isPluggedIn).thenReturn(false) - - assertThat(executor.numPending()).isEqualTo(0) - restarter.restartSystemUI("Restart for test") - assertThat(executor.numPending()).isEqualTo(0) - } - - @Test - fun testRestart_NotDoubleSheduled() { - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) - whenever(batteryController.isPluggedIn).thenReturn(true) - - assertThat(executor.numPending()).isEqualTo(0) - restarter.restartSystemUI("Restart for test") - restarter.restartSystemUI("Restart for test") - assertThat(executor.numPending()).isEqualTo(1) - } - - @Test - fun testWakefulnessLifecycle_CanRestart() { - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE) - whenever(batteryController.isPluggedIn).thenReturn(true) - assertThat(executor.numPending()).isEqualTo(0) - restarter.restartSystemUI("Restart for test") - - val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java) - verify(wakefulnessLifecycle).addObserver(captor.capture()) - - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) - - captor.value.onFinishedGoingToSleep() - assertThat(executor.numPending()).isEqualTo(1) - } - - @Test - fun testBatteryController_CanRestart() { - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) - whenever(batteryController.isPluggedIn).thenReturn(false) - assertThat(executor.numPending()).isEqualTo(0) - restarter.restartSystemUI("Restart for test") - - val captor = - ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java) - verify(batteryController).addCallback(captor.capture()) - - whenever(batteryController.isPluggedIn).thenReturn(true) - - captor.value.onBatteryLevelChanged(0, true, true) - assertThat(executor.numPending()).isEqualTo(1) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt new file mode 100644 index 000000000000..647b05a77b90 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt @@ -0,0 +1,76 @@ +/* + * 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.flags + +import android.test.suitebuilder.annotation.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.policy.BatteryController +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +/** + * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()! + */ +@SmallTest +class PluggedInConditionTest : SysuiTestCase() { + private lateinit var condition: PluggedInCondition + + @Mock private lateinit var batteryController: BatteryController + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + condition = PluggedInCondition(batteryController) + } + + @Test + fun testCondition_unplugged() { + whenever(batteryController.isPluggedIn).thenReturn(false) + + assertThat(condition.canRestartNow({})).isFalse() + } + + @Test + fun testCondition_pluggedIn() { + whenever(batteryController.isPluggedIn).thenReturn(true) + + assertThat(condition.canRestartNow({})).isTrue() + } + + @Test + fun testCondition_invokesRetry() { + whenever(batteryController.isPluggedIn).thenReturn(false) + var retried = false + val retryFn = { retried = true } + + // No restart yet, but we do register a listener now. + assertThat(condition.canRestartNow(retryFn)).isFalse() + val captor = + ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java) + verify(batteryController).addCallback(captor.capture()) + + whenever(batteryController.isPluggedIn).thenReturn(true) + + captor.value.onBatteryLevelChanged(0, true, true) + assertThat(retried).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt index 686782f59355..f7a773ea30ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -20,12 +20,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE -import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.Mock -import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -34,37 +33,45 @@ import org.mockito.MockitoAnnotations * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()! */ @SmallTest -class FeatureFlagsDebugRestarterTest : SysuiTestCase() { - private lateinit var restarter: FeatureFlagsDebugRestarter +class ScreenIdleConditionTest : SysuiTestCase() { + private lateinit var condition: ScreenIdleCondition @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock private lateinit var systemExitRestarter: SystemExitRestarter @Before fun setup() { MockitoAnnotations.initMocks(this) - restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter) + condition = ScreenIdleCondition(wakefulnessLifecycle) } @Test - fun testRestart_ImmediateWhenAsleep() { + fun testCondition_awake() { + whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE) + + assertThat(condition.canRestartNow {}).isFalse() + } + + @Test + fun testCondition_asleep() { whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) - restarter.restartSystemUI("Restart for test") - verify(systemExitRestarter).restartSystemUI(any()) + + assertThat(condition.canRestartNow {}).isTrue() } @Test - fun testRestart_WaitsForSceenOff() { + fun testCondition_invokesRetry() { whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE) + var retried = false + val retryFn = { retried = true } - restarter.restartSystemUI("Restart for test") - verify(systemExitRestarter, never()).restartSystemUI(any()) - + // No restart yet, but we do register a listener now. + assertThat(condition.canRestartNow(retryFn)).isFalse() val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java) verify(wakefulnessLifecycle).addObserver(captor.capture()) - captor.value.onFinishedGoingToSleep() + whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) - verify(systemExitRestarter).restartSystemUI(any()) + captor.value.onFinishedGoingToSleep() + assertThat(retried).isTrue() } } |