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

Commit 88b0ffcd authored by Nicolo' Mazzucato's avatar Nicolo' Mazzucato
Browse files

Introduce waitUntilNextDoFrameDone

This will be used in child cls to report the time it takes for the shade
window to move between displays.

Bug: 362719719
Bug: 378688537
Test: None - see child cls
Flag: com.android.systemui.shade_window_goes_around
Change-Id: I65b640916e2b3cfb614d231fe58567ff89bca617
parent 3605e89e
Loading
Loading
Loading
Loading
+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
    }
}
+22 −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 com.android.systemui.kosmos.Kosmos

val Kosmos.fakeChoreographerUtils: FakeChoreographerUtils by
    Kosmos.Fixture { FakeChoreographerUtils() }