diff options
9 files changed, 378 insertions, 12 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 7b216017df7d..8323d0971ad7 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt @@ -34,6 +34,9 @@ abstract class FlagsModule { @Binds abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags + @Binds + abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter + @Module companion object { @JvmStatic 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 aef887667527..87beff76290d 100644 --- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt +++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt @@ -27,4 +27,7 @@ import dagger.Module abstract class FlagsModule { @Binds abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags + + @Binds + abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java index ec3fdecb919a..5dae0a21d588 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java @@ -336,7 +336,6 @@ public class FeatureFlagsDebug implements FeatureFlags { Log.i(TAG, "Android Restart Suppressed"); return; } - Log.i(TAG, "Restarting Android"); mRestarter.restart(); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt new file mode 100644 index 000000000000..3d9f62768a37 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt @@ -0,0 +1,51 @@ +/* + * 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 { + + val observer = + object : WakefulnessLifecycle.Observer { + override fun onFinishedGoingToSleep() { + Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change") + restartNow() + } + } + + override fun restart() { + Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting on next screen off.") + if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) { + restartNow() + } else { + wakefulnessLifecycle.addObserver(observer) + } + } + + private fun restartNow() { + systemExitRestarter.restart() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt new file mode 100644 index 000000000000..a3f0f665c056 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt @@ -0,0 +1,82 @@ +/* + * 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 shouldRestart = false + var pendingRestart: Runnable? = null + + val observer = + object : WakefulnessLifecycle.Observer { + override fun onFinishedGoingToSleep() { + maybeScheduleRestart() + } + } + + val batteryCallback = + object : BatteryController.BatteryStateChangeCallback { + override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { + maybeScheduleRestart() + } + } + + override fun restart() { + Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting when plugged in and idle.") + if (!shouldRestart) { + // Don't bother scheduling twice. + shouldRestart = true + wakefulnessLifecycle.addObserver(observer) + batteryController.addCallback(batteryCallback) + maybeScheduleRestart() + } + } + + private fun maybeScheduleRestart() { + 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") + systemExitRestarter.restart() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt index 18d7bcf9b3fc..8442230fc5b1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt @@ -15,7 +15,6 @@ */ package com.android.systemui.flags -import com.android.internal.statusbar.IStatusBarService import dagger.Module import dagger.Provides import javax.inject.Named @@ -32,15 +31,5 @@ interface FlagsCommonModule { fun providesAllFlags(): Map<Int, Flag<*>> { return FlagsFactory.knownFlags.map { it.value.id to it.value }.toMap() } - - @JvmStatic - @Provides - fun providesRestarter(barService: IStatusBarService): Restarter { - return object : Restarter { - override fun restart() { - barService.restart() - } - } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt new file mode 100644 index 000000000000..f1b1be47a84f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt @@ -0,0 +1,25 @@ +/* + * 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 javax.inject.Inject + +class SystemExitRestarter @Inject constructor() : Restarter { + override fun restart() { + System.exit(0) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt new file mode 100644 index 000000000000..1e7b1f26ba8c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt @@ -0,0 +1,69 @@ +/* + * 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 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 FeatureFlagsDebugRestarterTest : SysuiTestCase() { + private lateinit var restarter: FeatureFlagsDebugRestarter + + @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock private lateinit var systemExitRestarter: SystemExitRestarter + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter) + } + + @Test + fun testRestart_ImmediateWhenAsleep() { + whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) + restarter.restart() + verify(systemExitRestarter).restart() + } + + @Test + fun testRestart_WaitsForSceenOff() { + whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE) + + restarter.restart() + verify(systemExitRestarter, never()).restart() + + val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java) + verify(wakefulnessLifecycle).addObserver(captor.capture()) + + captor.value.onFinishedGoingToSleep() + + verify(systemExitRestarter).restart() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt new file mode 100644 index 000000000000..68ca48dd327d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt @@ -0,0 +1,145 @@ +/* + * 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.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.restart() + assertThat(executor.numPending()).isEqualTo(1) + } + + @Test + fun testRestart_RestartsWhenIdle() { + whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) + whenever(batteryController.isPluggedIn).thenReturn(true) + + restarter.restart() + verify(systemExitRestarter, never()).restart() + executor.advanceClockToLast() + executor.runAllReady() + verify(systemExitRestarter).restart() + } + + @Test + fun testRestart_NotScheduledWhenAwake() { + whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE) + whenever(batteryController.isPluggedIn).thenReturn(true) + + assertThat(executor.numPending()).isEqualTo(0) + restarter.restart() + 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.restart() + 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.restart() + restarter.restart() + 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.restart() + + 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.restart() + + 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) + } +} |