From c38ec1a51e61594f7bebdc673fd37fb9a7de2809 Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Thu, 3 Nov 2022 21:49:04 +0000 Subject: Restart after flag changes on screen off. On debug builds, restart as soon as the screen goes off. On release builds, restart after the device is plugged in, the screen is off, and the device has been idle for a few seconds. The flag app does not yet reflect this change, but it will allow multiple flags to be flipped. Bug: 257302229 Test: manual Change-Id: I9da58f9881973d69fc0e35b394ca42a4166ceb08 --- .../com/android/systemui/flags/FlagsModule.kt | 3 + .../com/android/systemui/flags/FlagsModule.kt | 3 + .../android/systemui/flags/FeatureFlagsDebug.java | 1 - .../systemui/flags/FeatureFlagsDebugRestarter.kt | 51 ++++++++ .../systemui/flags/FeatureFlagsReleaseRestarter.kt | 82 ++++++++++++ .../android/systemui/flags/FlagsCommonModule.kt | 11 -- .../android/systemui/flags/SystemExitRestarter.kt | 25 ++++ .../flags/FeatureFlagsDebugRestarterTest.kt | 69 ++++++++++ .../flags/FeatureFlagsReleaseRestarterTest.kt | 145 +++++++++++++++++++++ 9 files changed, 378 insertions(+), 12 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt create mode 100644 packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt create mode 100644 packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt 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> { 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) + } +} -- cgit v1.2.3-59-g8ed1b