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

Commit 8ac797be authored by Michal Brzezinski's avatar Michal Brzezinski Committed by Michał Brzeziński
Browse files

Clean up of unfold_latency_tracking_fix flag

Fixes: 412377176
Test: SystemUI compiles and runs
Flag: EXEMPT removing unfold_latency_tracking_fix

Change-Id: I60e173971ea59c23f7fa3f0eef59ccc26eeac8ba
parent 490b6c88
Loading
Loading
Loading
Loading
+0 −10
Original line number Diff line number Diff line
@@ -1648,16 +1648,6 @@ flag {
   bug: "390179908"
}

flag {
    name: "unfold_latency_tracking_fix"
    namespace: "systemui"
    description: "New implementation to track unfold latency that excludes broken cases"
    bug: "390649568"
    metadata {
        purpose: PURPOSE_BUGFIX
   }
}

flag {
  name: "permission_helper_ui_rich_ongoing"
  namespace: "systemui"
+0 −249
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.unfold

import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.util.Log
import com.android.app.tracing.TraceUtils.traceAsync
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.instantForTrack
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.unfoldLatencyTrackingFix
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DeviceStateRepository
import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent
import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import com.android.systemui.util.Compile
import com.android.systemui.util.Utils.isDeviceFoldable
import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.race
import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.time.measureTimeMillis
import java.time.Duration
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withTimeout

/**
 * Old version of [DisplaySwitchLatencyTracker] tracking only [DisplaySwitchLatencyEvent]. Which
 * version is used for tracking depends on [unfoldLatencyTrackingFix] flag.
 */
@SysUISingleton
class NoCooldownDisplaySwitchLatencyTracker
@Inject
constructor(
    private val context: Context,
    private val deviceStateRepository: DeviceStateRepository,
    private val powerInteractor: PowerInteractor,
    private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
    private val animationStatusRepository: AnimationStatusRepository,
    private val keyguardInteractor: KeyguardInteractor,
    @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor,
    @Application private val applicationScope: CoroutineScope,
    private val displaySwitchLatencyLogger: DisplaySwitchLatencyLogger,
    private val systemClock: SystemClock,
    private val deviceStateManager: DeviceStateManager,
) : CoreStartable {

    private val backgroundDispatcher = singleThreadBgExecutor.asCoroutineDispatcher()
    private val isAodEnabled: Boolean
        get() = keyguardInteractor.isAodAvailable.value

    override fun start() {
        if (!isDeviceFoldable(context.resources, deviceStateManager)) {
            return
        }
        applicationScope.launch(context = backgroundDispatcher) {
            deviceStateRepository.state
                .pairwise()
                .filter {
                    // Start tracking only when the foldable device is
                    // folding(UNFOLDED/HALF_FOLDED -> FOLDED) or
                    // unfolding(FOLDED -> HALF_FOLD/UNFOLDED)
                    foldableDeviceState ->
                    foldableDeviceState.previousValue == DeviceState.FOLDED ||
                        foldableDeviceState.newValue == DeviceState.FOLDED
                }
                .flatMapLatest { foldableDeviceState ->
                    flow {
                        var displaySwitchLatencyEvent = DisplaySwitchLatencyEvent()
                        val toFoldableDeviceState = foldableDeviceState.newValue.toStatsInt()
                        displaySwitchLatencyEvent =
                            displaySwitchLatencyEvent.withBeforeFields(
                                foldableDeviceState.previousValue.toStatsInt()
                            )

                        val displaySwitchTimeMs =
                            measureTimeMillis(systemClock) {
                                try {
                                    withTimeout(SCREEN_EVENT_TIMEOUT) {
                                        traceAsync(TAG, "displaySwitch") {
                                            waitForDisplaySwitch(toFoldableDeviceState)
                                        }
                                    }
                                } catch (e: TimeoutCancellationException) {
                                    Log.e(TAG, "Wait for display switch timed out")
                                }
                            }

                        displaySwitchLatencyEvent =
                            displaySwitchLatencyEvent.withAfterFields(
                                toFoldableDeviceState,
                                displaySwitchTimeMs.toInt(),
                                getCurrentState(),
                            )
                        emit(displaySwitchLatencyEvent)
                    }
                }
                .collect { displaySwitchLatencyLogger.log(it) }
        }
    }

    private fun DeviceState.toStatsInt(): Int =
        when (this) {
            DeviceState.FOLDED -> FOLDABLE_DEVICE_STATE_CLOSED
            DeviceState.HALF_FOLDED -> FOLDABLE_DEVICE_STATE_HALF_OPEN
            DeviceState.UNFOLDED -> FOLDABLE_DEVICE_STATE_OPEN
            DeviceState.CONCURRENT_DISPLAY -> FOLDABLE_DEVICE_STATE_FLIPPED
            else -> FOLDABLE_DEVICE_STATE_UNKNOWN
        }

    private suspend fun waitForDisplaySwitch(toFoldableDeviceState: Int) {
        val isTransitionEnabled =
            unfoldTransitionInteractor.isAvailable &&
                animationStatusRepository.areAnimationsEnabled().first()
        if (shouldWaitForTransitionStart(toFoldableDeviceState, isTransitionEnabled)) {
            traceAsync(TAG, "waitForTransitionStart()") {
                unfoldTransitionInteractor.waitForTransitionStart()
            }
        } else {
            race({ waitForScreenTurnedOn() }, { waitForGoToSleepWithScreenOff() })
        }
    }

    private fun shouldWaitForTransitionStart(
        toFoldableDeviceState: Int,
        isTransitionEnabled: Boolean,
    ): Boolean = (toFoldableDeviceState != FOLDABLE_DEVICE_STATE_CLOSED && isTransitionEnabled)

    private suspend fun waitForScreenTurnedOn() {
        traceAsync(TAG, "waitForScreenTurnedOn()") {
            // dropping first as it's stateFlow and will always emit latest value but we're
            // only interested in new states
            powerInteractor.screenPowerState
                .drop(1)
                .filter { it == ScreenPowerState.SCREEN_ON }
                .first()
        }
    }

    private suspend fun waitForGoToSleepWithScreenOff() {
        traceAsync(TAG, "waitForGoToSleepWithScreenOff()") {
            powerInteractor.detailedWakefulness
                .filter { it.internalWakefulnessState == WakefulnessState.ASLEEP && !isAodEnabled }
                .first()
        }
    }

    private fun getCurrentState(): Int =
        when {
            isStateAod() -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD
            isStateScreenOff() -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF
            else -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__UNKNOWN
        }

    private fun isStateAod(): Boolean = (isAsleepDueToFold() && isAodEnabled)

    private fun isStateScreenOff(): Boolean = (isAsleepDueToFold() && !isAodEnabled)

    private fun isAsleepDueToFold(): Boolean {
        val lastWakefulnessEvent = powerInteractor.detailedWakefulness.value

        return (lastWakefulnessEvent.isAsleep() &&
            (lastWakefulnessEvent.lastSleepReason == WakeSleepReason.FOLD))
    }

    private inline fun log(msg: () -> String) {
        if (DEBUG) Log.d(TAG, msg())
    }

    private fun DisplaySwitchLatencyEvent.withBeforeFields(
        fromFoldableDeviceState: Int
    ): DisplaySwitchLatencyEvent {
        log { "fromFoldableDeviceState=$fromFoldableDeviceState" }
        instantForTrack(TAG) { "fromFoldableDeviceState=$fromFoldableDeviceState" }

        return copy(fromFoldableDeviceState = fromFoldableDeviceState)
    }

    private fun DisplaySwitchLatencyEvent.withAfterFields(
        toFoldableDeviceState: Int,
        displaySwitchTimeMs: Int,
        toState: Int,
    ): DisplaySwitchLatencyEvent {
        log {
            "toFoldableDeviceState=$toFoldableDeviceState, " +
                "toState=$toState, " +
                "latencyMs=$displaySwitchTimeMs"
        }
        instantForTrack(TAG) { "toFoldableDeviceState=$toFoldableDeviceState, toState=$toState" }

        return copy(
            toFoldableDeviceState = toFoldableDeviceState,
            latencyMs = displaySwitchTimeMs,
            toState = toState,
        )
    }

    companion object {
        private const val VALUE_UNKNOWN = -1
        private const val TAG = "DisplaySwitchLatency"
        private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
        private val SCREEN_EVENT_TIMEOUT = Duration.ofMillis(15000).toMillis()

        private const val FOLDABLE_DEVICE_STATE_UNKNOWN =
            SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_UNKNOWN
        const val FOLDABLE_DEVICE_STATE_CLOSED =
            SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_CLOSED
        const val FOLDABLE_DEVICE_STATE_HALF_OPEN =
            SysUiStatsLog
                .DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_HALF_OPENED
        private const val FOLDABLE_DEVICE_STATE_OPEN =
            SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_OPENED
        private const val FOLDABLE_DEVICE_STATE_FLIPPED =
            SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_FLIPPED
    }
}
+5 −13
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.systemui.unfold

import com.android.keyguard.KeyguardUnfoldTransition
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.unfoldLatencyTrackingFix
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shade.NotificationPanelUnfoldAnimationController
import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
@@ -40,7 +39,6 @@ import dagger.multibindings.IntoMap
import dagger.multibindings.IntoSet
import java.util.Optional
import javax.inject.Named
import javax.inject.Provider
import javax.inject.Qualifier
import javax.inject.Scope

@@ -73,6 +71,11 @@ abstract class SysUIUnfoldModule {
        impl: FoldableDisplaySwitchTrackingInteractor
    ): CoreStartable

    @Binds
    @IntoMap
    @ClassKey(DisplaySwitchLatencyTracker::class)
    abstract fun bindsDisplaySwitchLatencyTracker(impl: DisplaySwitchLatencyTracker): CoreStartable

    @Binds
    @SysUISingleton
    abstract fun provideDisplaySwitchTrackingInteractor(
@@ -101,15 +104,6 @@ abstract class SysUIUnfoldModule {
                Optional.of(factory.create(p1, p2, p3, p4))
            }
        }

        @Provides
        @IntoMap
        @ClassKey(DisplaySwitchLatencyTracker::class)
        fun provideDisplaySwitchLatencyTracker(
            noCoolDownVariant: Provider<NoCooldownDisplaySwitchLatencyTracker>,
            coolDownVariant: Provider<DisplaySwitchLatencyTracker>,
        ): CoreStartable =
            if (unfoldLatencyTrackingFix()) coolDownVariant.get() else noCoolDownVariant.get()
    }
}

@@ -175,7 +169,5 @@ interface SysUIUnfoldComponent {

    fun getUnfoldKeyguardVisibilityManager(): UnfoldKeyguardVisibilityManager

    fun getUnfoldLatencyTracker(): UnfoldLatencyTracker

    fun getNaturalRotationUnfoldProgressProvider(): NaturalRotationUnfoldProgressProvider
}
+0 −1
Original line number Diff line number Diff line
@@ -44,7 +44,6 @@ constructor(
            c.getUnfoldTransitionWallpaperController().init()
            c.getUnfoldHapticsPlayer()
            c.getNaturalRotationUnfoldProgressProvider().init()
            c.getUnfoldLatencyTracker().init()
        }

        foldStateLoggingProviderOptional.ifPresent { obj: FoldStateLoggingProvider -> obj.init() }
+0 −178
Original line number Diff line number Diff line
/*
 * 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.unfold

import android.content.ContentResolver
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.os.Trace
import android.util.Log
import com.android.internal.util.LatencyTracker
import com.android.systemui.Flags.unfoldLatencyTrackingFix
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
import com.android.systemui.util.Compile
import com.android.systemui.util.Utils.isDeviceFoldable
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject

/**
 * Logs performance metrics regarding time to turn the inner screen on.
 *
 * This class assumes that [onFoldEvent] is always called before [onScreenTurnedOn].
 *
 * This should be used from only one process.
 *
 * For now, the focus is on the time the inner display is visible, but in the future, it is easily
 * possible to monitor the time to go from the inner screen to the outer.
 */
@SysUIUnfoldScope
class UnfoldLatencyTracker
@Inject
constructor(
    private val latencyTracker: LatencyTracker,
    private val deviceStateManager: DeviceStateManager,
    private val transitionProgressProvider: Optional<UnfoldTransitionProgressProvider>,
    @UiBackground private val uiBgExecutor: Executor,
    private val context: Context,
    private val contentResolver: ContentResolver,
    private val screenLifecycle: ScreenLifecycle,
) : ScreenLifecycle.Observer, TransitionProgressListener {

    private var folded: Boolean? = null
    private var isTransitionEnabled: Boolean? = null
    private val foldStateListener = FoldStateListener(context)
    private var unfoldInProgress = false
    private val isFoldable: Boolean = isDeviceFoldable(context.resources, deviceStateManager)

    /** Registers for relevant events only if the device is foldable. */
    fun init() {
        if (unfoldLatencyTrackingFix() || !isFoldable) {
            return
        }
        deviceStateManager.registerCallback(uiBgExecutor, foldStateListener)
        screenLifecycle.addObserver(this)
        if (transitionProgressProvider.isPresent) {
            // Might not be present if the device is not a foldable device or unfold transition
            // is disabled in the device configuration
            transitionProgressProvider.get().addCallback(this)
        }
    }

    /**
     * To be called when the screen becomes visible.
     *
     * This is safe to call also when unsure whether the device is not a foldable, as it emits the
     * end action event only if we previously received a fold state.
     */
    override fun onScreenTurnedOn() {
        if (DEBUG) {
            Log.d(
                TAG,
                "onScreenTurnedOn: folded = $folded, isTransitionEnabled = $isTransitionEnabled",
            )
        }

        // We use onScreenTurnedOn event to finish tracking only if we are not playing
        // the unfold animation (e.g. it could be disabled because of battery saver).
        // When animation is enabled finishing of the tracking will be done in onTransitionStarted.
        if (folded == false && isTransitionEnabled == false) {
            onUnfoldEnded()

            if (DEBUG) {
                Log.d(TAG, "onScreenTurnedOn: ending ACTION_SWITCH_DISPLAY_UNFOLD")
            }
        }
    }

    /**
     * This callback is used to end the metric when the unfold animation is enabled because it could
     * add an additional delay to synchronize with launcher.
     */
    override fun onTransitionStarted() {
        if (DEBUG) {
            Log.d(
                TAG,
                "onTransitionStarted: folded = $folded, isTransitionEnabled = $isTransitionEnabled",
            )
        }

        if (folded == false && isTransitionEnabled == true) {
            onUnfoldEnded()

            if (DEBUG) {
                Log.d(TAG, "onTransitionStarted: ending ACTION_SWITCH_DISPLAY_UNFOLD")
            }
        }
    }

    private fun onUnfoldStarted() {
        if (unfoldInProgress) return
        unfoldInProgress = true
        // As LatencyTracker might be disabled, let's also log a parallel slice to the trace to be
        // able to debug all cases.
        latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
        Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, UNFOLD_IN_PROGRESS_TRACE_NAME, /* cookie= */ 0)
    }

    private fun onUnfoldEnded() {
        if (!unfoldInProgress) return
        unfoldInProgress = false
        latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
        Trace.endAsyncSection(UNFOLD_IN_PROGRESS_TRACE_NAME, 0)
    }

    private fun onFoldEvent(folded: Boolean) {
        val oldFolded = this.folded

        if (oldFolded != folded) {
            this.folded = folded

            if (DEBUG) {
                Log.d(TAG, "Received onFoldEvent = $folded")
            }

            // Do not start tracking when oldFolded is null, this means that this is the first
            // onFoldEvent after booting the device or starting SystemUI and not actual folding or
            // unfolding the device.
            if (oldFolded != null && !folded) {
                // Unfolding started
                onUnfoldStarted()
                isTransitionEnabled =
                    transitionProgressProvider.isPresent && contentResolver.areAnimationsEnabled()

                if (DEBUG) {
                    Log.d(
                        TAG,
                        "Starting ACTION_SWITCH_DISPLAY_UNFOLD, " +
                            "isTransitionEnabled = $isTransitionEnabled",
                    )
                }
            }
        }
    }

    private inner class FoldStateListener(context: Context) :
        DeviceStateManager.FoldStateListener(context, { onFoldEvent(it) })
}

private const val TAG = "UnfoldLatencyTracker"
private const val UNFOLD_IN_PROGRESS_TRACE_NAME = "Switch displays during unfold"
private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
Loading