Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 72546267 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

Delay restarting SystemUI on debug builds.

When a flag changes, delay restarting systemui by 1 second after the
screen goes idle. This prevents a race condition with entering
dream/doze/aod.

Also, refactor `FeatureFlagsReleaseRestarter` and
`FeatureFlagsDebugRestarter` into `ConditionalRestarter`, since they
share a great deal of the same functionality. This also has the
nice effect of making the tests smaller and more focused.

Fixes: 268060399
Test: atest SystemUITest && manual testing
Change-Id: Iae7ddc5965a95c72d35caafc2c90b267be7c3a6d
parent aee5a433
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -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
    }
}
+17 −1
Original line number Diff line number Diff line
@@ -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
    }
}
+103 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.
@@ -17,85 +17,87 @@
package com.android.systemui.flags

import android.util.Log
import com.android.systemui.dagger.qualifiers.Application
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
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 SystemUI when the device appears idle. */
class FeatureFlagsReleaseRestarter
/** Restarts the process after all passed in [Condition]s are true. */
class ConditionalRestarter
@Inject
constructor(
    private val wakefulnessLifecycle: WakefulnessLifecycle,
    private val batteryController: BatteryController,
    @Background private val bgExecutor: DelayableExecutor,
    private val systemExitRestarter: SystemExitRestarter
    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 {
    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)
            }
        }
    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 plugged in and idle."
        )
        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 plugged in and idle."
        )
        Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting when 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)
    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()
                    }
        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
        } else {
            restartJob?.cancel()
            restartJob = null
        }
    }

    private fun restartNow() {
        Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
        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"
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -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"

+49 −0
Original line number Diff line number Diff line
/*
 * 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
    }
}
Loading