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

Commit dd26f470 authored by Nick Chameyev's avatar Nick Chameyev Committed by Android (Google) Code Review
Browse files

Merge "Revert "Do not remove unfold overlay too early on folding"" into main

parents bce7c42c f7dbbe08
Loading
Loading
Loading
Loading
+0 −131
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fake
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.testKosmos
import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Factory
import com.android.systemui.unfold.util.fakeDeviceStateManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.concurrency.ThreadFactory
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.utils.os.FakeHandler
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidJUnit4::class)
class UnfoldLightRevealOverlayAnimationTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val featureFlags = kosmos.featureFlagsClassic
    private val progressProvider = kosmos.fakeUnfoldTransitionProgressProvider
    private val deviceStateManager = kosmos.fakeDeviceStateManager

    private val contentResolver: ContentResolver = mock()
    private val mockFullScreenController = kosmos.fullscreenLightRevealAnimationController
    private val mockScrimView = mock<LightRevealScrim>()
    private val threadFactory: ThreadFactory = mock()
    private val fullscreenLightRevealAnimationControllerFactory: Factory = mock()

    private val fakeSystemClock = FakeSystemClock()
    private val executor = FakeExecutor(fakeSystemClock)

    private lateinit var fakeHandler: FakeHandler
    private lateinit var animation: UnfoldLightRevealOverlayAnimation

    @Before
    fun setup() {
        whenever(mockFullScreenController.scrimView).thenReturn(mockScrimView)
        whenever(fullscreenLightRevealAnimationControllerFactory.create(any(), any(), any()))
            .thenReturn(mockFullScreenController)
        whenever(threadFactory.buildDelayableExecutorOnHandler(any())).thenReturn(executor)

        featureFlags.fake.set(Flags.ENABLE_DARK_VIGNETTE_WHEN_FOLDING, true)
        fakeHandler = FakeHandler(TestableLooper.get(this).looper)

        animation =
            UnfoldLightRevealOverlayAnimation(
                mContext,
                featureFlags,
                contentResolver,
                fakeHandler,
                { progressProvider },
                { progressProvider },
                deviceStateManager.deviceStateManager,
                threadFactory,
                fullscreenLightRevealAnimationControllerFactory,
            )
        animation.init()
    }

    @Test
    fun onScreenTurnedOff_overlayIsRemoved() {
        animation.onScreenTurnedOff()

        verifyOverlayRemoved()
    }

    @Test
    fun transitionFinishedAt0Progress_overlayIsNotRemoved() {
        progressProvider.onTransitionStarted()
        progressProvider.onTransitionProgress(1.0f)

        progressProvider.onTransitionProgress(0.0f)
        progressProvider.onTransitionFinished()

        verifyOverlayIsNotRemoved()
    }

    @Test
    fun deviceFolds_overlayIsNotRemoved() {
        deviceStateManager.fold()

        verifyOverlayIsNotRemoved()
    }

    @Test
    fun transitionFinishedAtNonZeroProgress_overlayIsRemoved() {
        progressProvider.onTransitionStarted()
        progressProvider.onTransitionProgress(0.0f)

        progressProvider.onTransitionProgress(0.123f)
        progressProvider.onTransitionFinished()

        verifyOverlayRemoved()
    }

    private fun verifyOverlayIsNotRemoved() =
        verify(mockFullScreenController, never()).ensureOverlayRemoved()

    private fun verifyOverlayRemoved() = verify(mockFullScreenController).ensureOverlayRemoved()
}
+0 −55
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.hardware.devicestate.DeviceState
import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback
import com.android.systemui.kosmos.Kosmos
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

val Kosmos.fakeDeviceStateManager by Kosmos.Fixture { FakeDeviceStateManager() }

class FakeDeviceStateManager {
    val deviceStateManager: DeviceStateManager = mock()

    private val listeners = arrayListOf<DeviceStateCallback>()

    init {
        whenever(deviceStateManager.registerCallback(any(), any())).thenAnswer {
            val deviceStateCallback =
                it.arguments[1] as? DeviceStateCallback ?: return@thenAnswer Unit
            listeners.add(deviceStateCallback)
        }
    }

    fun fold() {
        val foldDeviceStateConfiguration =
            DeviceState.Configuration.Builder(/* identifier= */ 0, "FOLDED")
                .setPhysicalProperties(setOf(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY))
                .build()

        sendDeviceState(DeviceState(foldDeviceStateConfiguration))
    }

    fun sendDeviceState(deviceState: DeviceState) {
        listeners.forEach { it.onDeviceStateChanged(deviceState) }
    }
}
+0 −1
Original line number Diff line number Diff line
@@ -93,7 +93,6 @@ constructor(

    @BinderThread
    fun onScreenTurnedOff() {
        fullScreenLightRevealAnimations?.forEach { it.onScreenTurnedOff() }
        pendingTasks.reset()
    }
}
+1 −3
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import android.view.SurfaceSession
import android.view.WindowManager
import android.view.WindowlessWindowManager
import androidx.annotation.WorkerThread
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -52,13 +51,12 @@ import java.util.concurrent.Executor
import java.util.function.Consumer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import com.android.app.tracing.coroutines.launchTraced as launch

interface FullscreenLightRevealAnimation {
    fun init()

    fun onScreenTurningOn(onOverlayReady: Runnable)

    fun onScreenTurnedOff() {}
}

class FullscreenLightRevealAnimationController
+3 −22
Original line number Diff line number Diff line
@@ -63,8 +63,6 @@ constructor(
    private lateinit var controller: FullscreenLightRevealAnimationController
    private lateinit var bgExecutor: Executor

    private var lastTransitionProgress = ANIMATION_PROGRESS_UNFOLDED

    override fun init() {
        // This method will be called only on devices where this animation is enabled,
        // so normally this thread won't be created
@@ -113,10 +111,6 @@ constructor(
        }
    }

    override fun onScreenTurnedOff() {
        controller.ensureOverlayRemoved()
    }

    private fun calculateRevealAmount(animationProgress: Float? = null): Float {
        val overlayAddReason = overlayAddReason

@@ -152,23 +146,11 @@ constructor(
            // affect much the usage of the device
            controller.isTouchBlocked =
                overlayAddReason == FOLD || progress < UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS

            lastTransitionProgress = progress
        }

        override fun onTransitionFinished() = executeInBackground {
            // It is guaranteed to receive a 0.0f event when the transition is finished
            // because of folding
            val finishedBecauseFolded = lastTransitionProgress == ANIMATION_PROGRESS_FOLDED

            // Remove the overlay here only if the transition is finished in 'unfolded' state.
            // The folded case removal will be handled by onScreenTurnedOff callback.
            // This is needed to avoid too early removal of the overlay, as we might receive
            // onTransitionFinished before the screen is turned off
            if (!finishedBecauseFolded) {
            controller.ensureOverlayRemoved()
        }
        }

        override fun onTransitionStarted() {
            // Add view for folding case (when unfolding the view is added earlier)
@@ -198,6 +180,7 @@ constructor(
            context,
            Consumer { isFolded ->
                if (isFolded) {
                    controller.ensureOverlayRemoved()
                    isUnfoldHandled = false
                }
                this.isFolded = isFolded
@@ -213,7 +196,5 @@ constructor(
        const val TAG = "UnfoldLightRevealOverlayAnimation"
        const val OVERLAY_TITLE = "unfold-animation-overlay"
        const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f
        const val ANIMATION_PROGRESS_UNFOLDED = 1.0f
        const val ANIMATION_PROGRESS_FOLDED = 0.0f
    }
}