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

Commit 6c5e2411 authored by Nicolò Mazzucato's avatar Nicolò Mazzucato Committed by Android (Google) Code Review
Browse files

Merge "Fix race in ScopedUnfoldTransitionProgressProvider" into main

parents 487929f9 d61e2679
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
package com.android.systemui.statusbar.phone

import android.graphics.Point
import android.testing.TestableLooper
import android.view.Display
import android.view.Surface
import android.view.View
@@ -19,6 +20,7 @@ import org.mockito.Mock
import org.mockito.MockitoAnnotations

@SmallTest
@TestableLooper.RunWithLooper
class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {

    @Mock
+6 −0
Original line number Diff line number Diff line
@@ -26,8 +26,11 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr

    private val recordings: MutableList<UnfoldTransitionRecording> = arrayListOf()
    private var currentRecording: UnfoldTransitionRecording? = null
    var lastCallbackThread: Thread? = null
        private set

    override fun onTransitionStarted() {
        lastCallbackThread = Thread.currentThread()
        assertWithMessage("Trying to start a transition when it is already in progress")
            .that(currentRecording)
            .isNull()
@@ -36,6 +39,7 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
    }

    override fun onTransitionProgress(progress: Float) {
        lastCallbackThread = Thread.currentThread()
        assertWithMessage("Received transition progress event when it's not started")
            .that(currentRecording)
            .isNotNull()
@@ -43,6 +47,7 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
    }

    override fun onTransitionFinishing() {
        lastCallbackThread = Thread.currentThread()
        assertWithMessage("Received transition finishing event when it's not started")
            .that(currentRecording)
            .isNotNull()
@@ -50,6 +55,7 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
    }

    override fun onTransitionFinished() {
        lastCallbackThread = Thread.currentThread()
        assertWithMessage("Received transition finish event when it's not started")
            .that(currentRecording)
            .isNotNull()
+5 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.systemui.unfold.util

import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@ import org.mockito.MockitoAnnotations

@RunWith(AndroidTestingRunner::class)
@SmallTest
@TestableLooper.RunWithLooper
class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {

    @Mock lateinit var rotationChangeProvider: RotationChangeProvider
@@ -48,10 +50,12 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
    @Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener>

    lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
    private lateinit var testableLooper : TestableLooper

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        testableLooper = TestableLooper.get(this)

        progressProvider =
            NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, sourceProvider)
@@ -123,5 +127,6 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {

    private fun onRotationChanged(rotation: Int) {
        rotationListenerCaptor.value.onRotationChanged(rotation)
        testableLooper.processAllMessages()
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import android.content.ContentResolver
import android.database.ContentObserver
import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
@@ -36,6 +37,7 @@ import org.mockito.MockitoAnnotations

@RunWith(AndroidTestingRunner::class)
@SmallTest
@TestableLooper.RunWithLooper
class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {

    @Mock lateinit var sinkProvider: TransitionProgressListener
+161 −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.unfold.util

import android.os.Handler
import android.os.HandlerThread
import android.os.Process
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
import com.android.systemui.unfold.progress.TestUnfoldProgressListener
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withTimeout
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper
class ScopedUnfoldTransitionProgressProviderTest : SysuiTestCase() {

    private val rootProvider = TestUnfoldTransitionProvider()
    private val listener = TestUnfoldProgressListener()
    private val testScope = TestScope(UnconfinedTestDispatcher())
    private val bgThread =
        HandlerThread("UnfoldBgTest", Process.THREAD_PRIORITY_FOREGROUND).apply { start() }
    private val bgHandler = Handler(bgThread.looper)
    private val scopedProvider =
        ScopedUnfoldTransitionProgressProvider(rootProvider).apply { addCallback(listener) }

    @Test
    fun setReadyToHandleTransition_whileTransitionRunning_propagatesCallbacks() =
        testScope.runTest {
            runBlockingInBg { rootProvider.onTransitionStarted() }

            scopedProvider.setReadyToHandleTransition(true)

            runBlockingInBg { /* sync barrier */}

            listener.assertStarted()

            runBlockingInBg { rootProvider.onTransitionProgress(1f) }

            listener.assertLastProgress(1f)

            runBlockingInBg { rootProvider.onTransitionFinished() }

            listener.assertNotStarted()
        }

    @Test
    fun setReadyToHandleTransition_whileTransitionNotRunning_callbacksInProgressThread() {
        testScope.runTest {
            scopedProvider.setReadyToHandleTransition(true)

            val bgThread = runBlockingInBg { Thread.currentThread() }

            runBlockingInBg { rootProvider.onTransitionStarted() }

            listener.assertStarted()

            assertThat(listener.lastCallbackThread).isEqualTo(bgThread)
        }
    }

    @Test
    fun setReadyToHandleTransition_beforeAnyCallback_doesNotCrash() {
        testScope.runTest { scopedProvider.setReadyToHandleTransition(true) }
    }

    @Test
    fun onTransitionStarted_whileNotReadyToHandleTransition_doesNotPropagate() {
        testScope.runTest {
            scopedProvider.setReadyToHandleTransition(false)

            rootProvider.onTransitionStarted()

            listener.assertNotStarted()
        }
    }

    @Test
    fun onTransitionStarted_defaultReadiness_doesNotPropagate() {
        testScope.runTest {
            rootProvider.onTransitionStarted()

            listener.assertNotStarted()
        }
    }

    @Test
    fun onTransitionStarted_fromDifferentThreads_throws() {
        testScope.runTest {
            runBlockingInBg {
                rootProvider.onTransitionStarted()
                rootProvider.onTransitionFinished()
            }
            assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionStarted() }
        }
    }

    @Test
    fun onTransitionProgress_fromDifferentThreads_throws() {
        testScope.runTest {
            runBlockingInBg { rootProvider.onTransitionStarted() }
            assertThrows(IllegalStateException::class.java) {
                rootProvider.onTransitionProgress(1f)
            }
        }
    }

    @Test
    fun onTransitionFinished_fromDifferentThreads_throws() {
        testScope.runTest {
            runBlockingInBg { rootProvider.onTransitionStarted() }
            assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionFinished() }
        }
    }

    @Test
    fun onTransitionFinishing_fromDifferentThreads_throws() {
        testScope.runTest {
            runBlockingInBg { rootProvider.onTransitionStarted() }
            assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionFinishing() }
        }
    }

    private fun <T> runBlockingInBg(f: () -> T): T {
        return runBlocking {
            withTimeout(5.seconds) {
                suspendCancellableCoroutine { c: CancellableContinuation<T> ->
                    bgHandler.post { c.resumeWith(Result.success(f())) }
                }
            }
        }
    }
}
Loading