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

Commit d758582e authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topics "ACTION_SHADE_WINDOW_DISPLAY_CHANGE", "track_tracer" into main

* changes:
  Introduce latency tracking for shade display change
  Refactor ShadeTraceLogger to use TrackTracer
  Introduce waitUntilNextDoFrameDone
  Add ACTION_SHADE_WINDOW_DISPLAY_CHANGE to LatencyTracker
parents 406be144 360c7f7a
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPOR
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHADE_WINDOW_DISPLAY_CHANGE;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION;
@@ -257,6 +258,14 @@ public class LatencyTracker {
     */
    public static final int ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME = 28;

    /**
     * Time it takes for the shade window to move display after a user interaction.
     * <p>
     * This starts when the user does an interaction that triggers the window reparenting, and
     * finishes after the first doFrame done with the new display configuration.
     */
    public static final int ACTION_SHADE_WINDOW_DISPLAY_CHANGE = 29;

    private static final int[] ACTIONS_ALL = {
        ACTION_EXPAND_PANEL,
        ACTION_TOGGLE_RECENTS,
@@ -287,6 +296,7 @@ public class LatencyTracker {
        ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
        ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
        ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME,
        ACTION_SHADE_WINDOW_DISPLAY_CHANGE,
    };

    /** @hide */
@@ -320,6 +330,7 @@ public class LatencyTracker {
        ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
        ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
        ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME,
        ACTION_SHADE_WINDOW_DISPLAY_CHANGE,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Action {
@@ -356,6 +367,7 @@ public class LatencyTracker {
            UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
            UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
            UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME,
            UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHADE_WINDOW_DISPLAY_CHANGE,
    };

    private final Object mLock = new Object();
@@ -554,6 +566,8 @@ public class LatencyTracker {
                return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN";
            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME:
                return "ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME";
            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHADE_WINDOW_DISPLAY_CHANGE:
                return "ACTION_SHADE_WINDOW_DISPLAY_CHANGE";
            default:
                throw new IllegalArgumentException("Invalid action");
        }
+130 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.shade

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.latencyTracker
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.common.ui.view.fakeChoreographerUtils
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.testKosmos
import kotlin.test.Test
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.any

@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeDisplayChangeLatencyTrackerTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val configurationRepository = kosmos.fakeConfigurationRepository
    private val latencyTracker = kosmos.latencyTracker
    private val testScope = kosmos.testScope
    private val choreographerUtils = kosmos.fakeChoreographerUtils

    private val underTest = kosmos.shadeDisplayChangeLatencyTracker

    @Test
    fun onShadeDisplayChanging_afterMovedToDisplayAndDoFrameCompleted_atomReported() =
        testScope.runTest {
            underTest.onShadeDisplayChanging(1)

            verify(latencyTracker).onActionStart(any())
            verify(latencyTracker, never()).onActionEnd(any())

            sendOnMovedToDisplay(1)
            choreographerUtils.completeDoFrame()

            verify(latencyTracker).onActionEnd(any())
        }

    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun onChange_doFrameTimesOut_previousCancelled() =
        testScope.runTest {
            underTest.onShadeDisplayChanging(1)

            verify(latencyTracker).onActionStart(any())
            verify(latencyTracker, never()).onActionEnd(any())

            sendOnMovedToDisplay(1)
            advanceTimeBy(100.seconds)

            verify(latencyTracker, never()).onActionEnd(any())
            verify(latencyTracker).onActionCancel(any())
        }

    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun onChange_onMovedToDisplayTimesOut_cancelled() =
        testScope.runTest {
            underTest.onShadeDisplayChanging(1)

            verify(latencyTracker).onActionStart(any())

            choreographerUtils.completeDoFrame()
            advanceTimeBy(100.seconds)

            verify(latencyTracker).onActionCancel(any())
        }

    @Test
    fun onChange_whilePreviousWasInProgress_previousCancelledAndNewStarted() =
        testScope.runTest {
            underTest.onShadeDisplayChanging(1)

            verify(latencyTracker).onActionStart(any())

            underTest.onShadeDisplayChanging(2)

            verify(latencyTracker).onActionCancel(any())
            verify(latencyTracker, times(2)).onActionStart(any())
        }

    @Test
    fun onChange_multiple_multipleReported() =
        testScope.runTest {
            underTest.onShadeDisplayChanging(1)
            verify(latencyTracker).onActionStart(any())

            sendOnMovedToDisplay(1)
            choreographerUtils.completeDoFrame()

            verify(latencyTracker).onActionEnd(any())

            underTest.onShadeDisplayChanging(0)

            sendOnMovedToDisplay(0)
            choreographerUtils.completeDoFrame()

            verify(latencyTracker, times(2)).onActionStart(any())
            verify(latencyTracker, times(2)).onActionEnd(any())
        }

    private fun sendOnMovedToDisplay(displayId: Int) {
        configurationRepository.onMovedToDisplay(displayId)
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
    private val positionRepository = kosmos.fakeShadeDisplaysRepository
    private val shadeContext = kosmos.mockedWindowContext
    private val resources = kosmos.mockResources
    private val latencyTracker = kosmos.mockedShadeDisplayChangeLatencyTracker
    private val configuration = mock<Configuration>()
    private val display = mock<Display>()

@@ -81,4 +82,14 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {

        verify(shadeContext).reparentToDisplay(eq(1))
    }

    @Test
    fun start_shadeInWrongPosition_logsStartToLatencyTracker() {
        whenever(display.displayId).thenReturn(0)
        positionRepository.setDisplayId(1)

        underTest.start()

        verify(latencyTracker).onShadeDisplayChanging(eq(1))
    }
}
+78 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.common.ui.view

import android.view.Choreographer
import android.view.View
import com.android.app.tracing.coroutines.TrackTracer
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine

/** utilities related to [Choreographer]. */
interface ChoreographerUtils {
    /**
     * Waits until the next [view] doFrame is completed.
     *
     * Note that this is expected to work properly when called from any thread. If called during a
     * doFrame, it waits for the next one to be completed.
     *
     * This differs from [kotlinx.coroutines.android.awaitFrame] as it uses
     * [Handler.postAtFrontOfQueue] instead of [Handler.post] when called from a thread different
     * than the UI thread for that view. Using [Handler.post] might lead to posting the runnable
     * after a few frame, effectively missing the "next do frame".
     */
    suspend fun waitUntilNextDoFrameDone(view: View)
}

object ChoreographerUtilsImpl : ChoreographerUtils {
    private val t = TrackTracer("ChoreographerUtils")

    override suspend fun waitUntilNextDoFrameDone(view: View) {
        t.traceAsync("waitUntilNextDoFrameDone") { waitUntilNextDoFrameDoneTraced(view) }
    }

    suspend fun waitUntilNextDoFrameDoneTraced(view: View) {
        suspendCancellableCoroutine { cont ->
            val frameCallback =
                Choreographer.FrameCallback {
                    t.instant { "We're in doFrame, waiting for it to end." }
                    view.handler.postAtFrontOfQueue {
                        t.instant { "DoFrame ended." }
                        cont.resume(Unit)
                    }
                }
            view.runOnUiThreadUrgently {
                t.instant { "Waiting for next doFrame" }
                val choreographer = Choreographer.getInstance()
                cont.invokeOnCancellation { choreographer.removeFrameCallback(frameCallback) }
                choreographer.postFrameCallback(frameCallback)
            }
        }
    }

    /**
     * Execute [r] on the view UI thread, taking priority over everything else scheduled there. Runs
     * directly if we're already in the correct thread.
     */
    private fun View.runOnUiThreadUrgently(r: () -> Unit) {
        if (handler.looper.isCurrentThread) {
            r()
        } else {
            handler.postAtFrontOfQueue(r)
        }
    }
}
+51 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.common.ui.view

import android.view.View
import kotlinx.coroutines.CompletableDeferred

class FakeChoreographerUtils : ChoreographerUtils {

    private var pendingDeferred: CompletableDeferred<Unit>? = null

    override suspend fun waitUntilNextDoFrameDone(view: View) {
        getDeferred().await()
        clearDeferred()
    }

    /**
     * Called from tests when it's time to complete the doFrame. It works also if it's called before
     * [waitUntilNextDoFrameDone].
     */
    fun completeDoFrame() {
        getDeferred().complete(Unit)
    }

    @Synchronized
    private fun getDeferred(): CompletableDeferred<Unit> {
        if (pendingDeferred == null) {
            pendingDeferred = CompletableDeferred()
        }
        return pendingDeferred!!
    }

    @Synchronized
    private fun clearDeferred() {
        pendingDeferred = null
    }
}
Loading