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
This commit is contained in:
Dave Mankoff
2022-11-03 21:49:04 +00:00
parent 5d076fc249
commit c38ec1a51e
9 changed files with 378 additions and 12 deletions

View File

@@ -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

View File

@@ -27,4 +27,7 @@ import dagger.Module
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
@Binds
abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
}

View File

@@ -336,7 +336,6 @@ public class FeatureFlagsDebug implements FeatureFlags {
Log.i(TAG, "Android Restart Suppressed");
return;
}
Log.i(TAG, "Restarting Android");
mRestarter.restart();
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}
}
}
}

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}