diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt index b6be6edc7a108..e46b6f12e4a38 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt @@ -26,6 +26,7 @@ import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider +import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider import com.android.systemui.unfold.updates.DeviceFoldStateProvider import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider @@ -62,7 +63,7 @@ fun createUnfoldTransitionProgressProvider( mainExecutor ) - return if (config.isHingeAngleEnabled) { + val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) { PhysicsBasedUnfoldTransitionProgressProvider( mainHandler, foldStateProvider @@ -70,6 +71,10 @@ fun createUnfoldTransitionProgressProvider( } else { FixedTimingTransitionProgressProvider(foldStateProvider) } + return ScaleAwareTransitionProgressProvider( + unfoldTransitionProgressProvider, + context.contentResolver + ) } fun createConfig(context: Context): UnfoldTransitionConfig = diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt index e072d41e4eeec..58d7dfb133a59 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt @@ -2,7 +2,6 @@ package com.android.systemui.unfold.util import android.content.Context import android.os.RemoteException -import android.util.Log import android.view.IRotationWatcher import android.view.IWindowManager import android.view.Surface diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt new file mode 100644 index 0000000000000..df9078a15520e --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt @@ -0,0 +1,50 @@ +package com.android.systemui.unfold.util + +import android.animation.ValueAnimator +import android.content.ContentResolver +import android.database.ContentObserver +import android.provider.Settings +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener + +/** Wraps [UnfoldTransitionProgressProvider] to disable transitions when animations are disabled. */ +class ScaleAwareTransitionProgressProvider( + unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, + private val contentResolver: ContentResolver +) : UnfoldTransitionProgressProvider { + + private val scopedUnfoldTransitionProgressProvider = + ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider) + + private val animatorDurationScaleObserver = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + onAnimatorScaleChanged() + } + } + + init { + contentResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE), + /* notifyForDescendants= */ false, + animatorDurationScaleObserver) + onAnimatorScaleChanged() + } + + private fun onAnimatorScaleChanged() { + val animationsEnabled = ValueAnimator.areAnimatorsEnabled() + scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(animationsEnabled) + } + + override fun addCallback(listener: TransitionProgressListener) { + scopedUnfoldTransitionProgressProvider.addCallback(listener) + } + + override fun removeCallback(listener: TransitionProgressListener) { + scopedUnfoldTransitionProgressProvider.removeCallback(listener) + } + + override fun destroy() { + contentResolver.unregisterContentObserver(animatorDurationScaleObserver) + scopedUnfoldTransitionProgressProvider.destroy() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index 51de132108bef..e6fc49fae3156 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.unfold +import android.animation.ValueAnimator import android.content.Context import android.graphics.PixelFormat import android.hardware.devicestate.DeviceStateManager @@ -111,7 +112,7 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn") try { // Add the view only if we are unfolding and this is the first screen on - if (!isFolded && !isUnfoldHandled) { + if (!isFolded && !isUnfoldHandled && ValueAnimator.areAnimatorsEnabled()) { addView(onOverlayReady) isUnfoldHandled = true } else { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt new file mode 100644 index 0000000000000..db7a85166807c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt @@ -0,0 +1,139 @@ +/* + * 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.util + +import android.animation.ValueAnimator +import android.content.ContentResolver +import android.database.ContentObserver +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.util.mockito.any +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() { + + @Mock + lateinit var contentResolver: ContentResolver + + @Mock + lateinit var sourceProvider: UnfoldTransitionProgressProvider + + @Mock + lateinit var sinkProvider: TransitionProgressListener + + lateinit var progressProvider: ScaleAwareTransitionProgressProvider + + private val sourceProviderListenerCaptor = + ArgumentCaptor.forClass(TransitionProgressListener::class.java) + + private val animatorDurationScaleListenerCaptor = + ArgumentCaptor.forClass(ContentObserver::class.java) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + progressProvider = ScaleAwareTransitionProgressProvider( + sourceProvider, + contentResolver + ) + + verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture()) + verify(contentResolver).registerContentObserver(any(), any(), + animatorDurationScaleListenerCaptor.capture()) + + progressProvider.addCallback(sinkProvider) + } + + @Test + fun onTransitionStarted_animationsEnabled_eventReceived() { + setAnimationsEnabled(true) + + source.onTransitionStarted() + + verify(sinkProvider).onTransitionStarted() + } + + @Test + fun onTransitionStarted_animationsNotEnabled_eventNotReceived() { + setAnimationsEnabled(false) + + source.onTransitionStarted() + + verifyNoMoreInteractions(sinkProvider) + } + + @Test + fun onTransitionEnd_animationsEnabled_eventReceived() { + setAnimationsEnabled(true) + + source.onTransitionFinished() + + verify(sinkProvider).onTransitionFinished() + } + + @Test + fun onTransitionEnd_animationsNotEnabled_eventNotReceived() { + setAnimationsEnabled(false) + + source.onTransitionFinished() + + verifyNoMoreInteractions(sinkProvider) + } + + @Test + fun onTransitionProgress_animationsEnabled_eventReceived() { + setAnimationsEnabled(true) + + source.onTransitionProgress(42f) + + verify(sinkProvider).onTransitionProgress(42f) + } + + @Test + fun onTransitionProgress_animationsNotEnabled_eventNotReceived() { + setAnimationsEnabled(false) + + source.onTransitionProgress(42f) + + verifyNoMoreInteractions(sinkProvider) + } + + private fun setAnimationsEnabled(enabled: Boolean) { + val durationScale = if (enabled) { + 1f + } else { + 0f + } + ValueAnimator.setDurationScale(durationScale) + animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false) + } + + private val source: TransitionProgressListener + get() = sourceProviderListenerCaptor.value +}