[Unfold animation] Disable task animations when folding by default

Disables animation of the tasks (cropping and scaling) when
folding e.g. in an app or in split screen. We will still play
the animation when unfolding but we disable it when folding.

Bug: 283218963
Test: atest UnfoldOnlyProgressProviderTest
Test: manual open an app, fold, unfold => there is task unfold animation
Test: manual open an app, start folding => there is no animation
Test: manual fold/unfold with split screen, on launcher
Change-Id: Ide43b31fce8bb0d0aa4881c951590ccc5f8e345f
This commit is contained in:
Nick Chameyev
2023-05-26 17:53:13 +01:00
parent c42b01e3e2
commit 014ef53efd
7 changed files with 274 additions and 26 deletions

View File

@@ -0,0 +1,63 @@
/*
* 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 com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
import java.util.concurrent.Executor
/**
* [UnfoldTransitionProgressProvider] that emits transition progress only when unfolding but not
* when folding, so we can play the animation only one way but not the other way.
*/
class UnfoldOnlyProgressProvider(
foldProvider: FoldProvider,
@Main private val executor: Executor,
private val sourceProvider: UnfoldTransitionProgressProvider,
private val scopedProvider: ScopedUnfoldTransitionProgressProvider =
ScopedUnfoldTransitionProgressProvider(sourceProvider)
) : UnfoldTransitionProgressProvider by scopedProvider {
private var isFolded = false
init {
foldProvider.registerCallback(FoldListener(), executor)
sourceProvider.addCallback(SourceTransitionListener())
}
private inner class SourceTransitionListener : TransitionProgressListener {
override fun onTransitionFinished() {
// Disable scoped progress provider after the first unfold animation, so fold animation
// will not be propagated. It will be re-enabled after folding so we can play
// the unfold animation again.
if (!isFolded) {
scopedProvider.setReadyToHandleTransition(false)
}
}
}
private inner class FoldListener : FoldCallback {
override fun onFoldUpdated(isFolded: Boolean) {
if (isFolded) {
scopedProvider.setReadyToHandleTransition(true)
}
this@UnfoldOnlyProgressProvider.isFolded = isFolded
}
}
}

View File

@@ -18,16 +18,19 @@ package com.android.systemui.unfold
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.os.SystemProperties
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.LifecycleScreenStatusProvider
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.system.SystemUnfoldSharedModule
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.unfold.util.UnfoldOnlyProgressProvider
import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
import com.android.systemui.util.time.SystemClockImpl
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
@@ -37,6 +40,7 @@ import dagger.Provides
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Named
import javax.inject.Provider
import javax.inject.Singleton
@Module(includes = [UnfoldSharedModule::class, SystemUnfoldSharedModule::class])
@@ -90,6 +94,18 @@ class UnfoldTransitionModule {
NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, provider)
}
@Provides
@Singleton
@Named(UNFOLD_ONLY_PROVIDER)
fun provideUnfoldOnlyProvider(
foldProvider: FoldProvider,
@Main executor: Executor,
sourceProvider: Optional<UnfoldTransitionProgressProvider>
): Optional<UnfoldTransitionProgressProvider> =
sourceProvider.map { provider ->
UnfoldOnlyProgressProvider(foldProvider, executor, provider)
}
@Provides
@Named(UNFOLD_STATUS_BAR)
@Singleton
@@ -102,16 +118,35 @@ class UnfoldTransitionModule {
@Singleton
fun provideShellProgressProvider(
config: UnfoldTransitionConfig,
provider: Optional<UnfoldTransitionProgressProvider>
): ShellUnfoldProgressProvider =
if (config.isEnabled && provider.isPresent) {
UnfoldProgressProvider(provider.get())
} else {
ShellUnfoldProgressProvider.NO_PROVIDER
}
provider: Provider<Optional<UnfoldTransitionProgressProvider>>,
@Named(UNFOLD_ONLY_PROVIDER)
unfoldOnlyProvider: Provider<Optional<UnfoldTransitionProgressProvider>>
): ShellUnfoldProgressProvider {
val resultingProvider =
if (config.isEnabled) {
// Return unfold only provider to the shell if we don't want to animate tasks during
// folding. Shell provider listeners are responsible for animating task bounds.
if (ENABLE_FOLD_TASK_ANIMATIONS) {
provider
} else {
unfoldOnlyProvider
}
} else {
null
}
return resultingProvider?.get()?.orElse(null)?.let(::UnfoldProgressProvider)
?: ShellUnfoldProgressProvider.NO_PROVIDER
}
@Provides
fun screenStatusProvider(impl: LifecycleScreenStatusProvider): ScreenStatusProvider = impl
}
const val UNFOLD_STATUS_BAR = "unfold_status_bar"
const val UNFOLD_ONLY_PROVIDER = "unfold_only_provider"
// TODO: b/265764985 - tracking bug to clean-up the flag
// FeatureFlags are not accessible here because it's a global submodule (see GlobalModule.java)
private val ENABLE_FOLD_TASK_ANIMATIONS =
SystemProperties.getBoolean("persist.unfold.enable_fold_tasks_animation", false)

View File

@@ -4,7 +4,7 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr
class TestUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener {
private val listeners = arrayListOf<TransitionProgressListener>()
private val listeners = mutableListOf<TransitionProgressListener>()
override fun destroy() {
listeners.clear()

View File

@@ -20,10 +20,9 @@ import android.os.Vibrator
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.util.TestFoldProvider
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -115,20 +114,4 @@ class UnfoldHapticsPlayerTest : SysuiTestCase() {
verify(vibrator).vibrate(any<VibrationEffect>())
}
private class TestFoldProvider : FoldProvider {
private val listeners = arrayListOf<FoldProvider.FoldCallback>()
override fun registerCallback(callback: FoldProvider.FoldCallback, executor: Executor) {
listeners += callback
}
override fun unregisterCallback(callback: FoldProvider.FoldCallback) {
listeners -= callback
}
fun onFoldUpdate(isFolded: Boolean) {
listeners.forEach { it.onFoldUpdated(isFolded) }
}
}
}

View File

@@ -74,6 +74,11 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
currentRecording?.assertLastProgress(progress) ?: error("unfold not in progress.")
}
fun clear() {
currentRecording = null
recordings.clear()
}
class UnfoldTransitionRecording {
private val progressHistory: MutableList<Float> = arrayListOf()
private var finishingInvocations: Int = 0

View File

@@ -0,0 +1,34 @@
/*
* 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 com.android.systemui.unfold.updates.FoldProvider
import java.util.concurrent.Executor
class TestFoldProvider : FoldProvider {
private val listeners = arrayListOf<FoldProvider.FoldCallback>()
override fun registerCallback(callback: FoldProvider.FoldCallback, executor: Executor) {
listeners += callback
}
override fun unregisterCallback(callback: FoldProvider.FoldCallback) {
listeners -= callback
}
fun onFoldUpdate(isFolded: Boolean) {
listeners.forEach { it.onFoldUpdated(isFolded) }
}
}

View File

@@ -0,0 +1,128 @@
/*
* 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.testing.AndroidTestingRunner
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.util.concurrent.MoreExecutors
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
class UnfoldOnlyProgressProviderTest : SysuiTestCase() {
private val listener = TestUnfoldProgressListener()
private val sourceProvider = TestUnfoldTransitionProvider()
private val foldProvider = TestFoldProvider()
private lateinit var progressProvider: UnfoldOnlyProgressProvider
@Before
fun setUp() {
progressProvider =
UnfoldOnlyProgressProvider(foldProvider, MoreExecutors.directExecutor(), sourceProvider)
progressProvider.addCallback(listener)
}
@Test
fun unfolded_unfoldAnimationFinished_propagatesEvents() {
foldProvider.onFoldUpdate(isFolded = true)
foldProvider.onFoldUpdate(isFolded = false)
// Unfold animation
sourceProvider.onTransitionStarted()
sourceProvider.onTransitionProgress(0.5f)
sourceProvider.onTransitionFinished()
with(listener.ensureTransitionFinished()) {
assertLastProgress(0.5f)
}
}
@Test
fun unfoldedWithAnimation_foldAnimation_doesNotPropagateEvents() {
foldProvider.onFoldUpdate(isFolded = true)
foldProvider.onFoldUpdate(isFolded = false)
// Unfold animation
sourceProvider.onTransitionStarted()
sourceProvider.onTransitionProgress(0.5f)
sourceProvider.onTransitionFinished()
listener.clear()
// Fold animation
sourceProvider.onTransitionStarted()
sourceProvider.onTransitionProgress(0.2f)
sourceProvider.onTransitionFinished()
listener.assertNotStarted()
}
@Test
fun unfoldedWithAnimation_foldAnimationSeveralTimes_doesNotPropagateEvents() {
foldProvider.onFoldUpdate(isFolded = true)
foldProvider.onFoldUpdate(isFolded = false)
// Unfold animation
sourceProvider.onTransitionStarted()
sourceProvider.onTransitionProgress(0.5f)
sourceProvider.onTransitionFinished()
listener.clear()
// Start and stop fold animation several times
repeat(3) {
sourceProvider.onTransitionStarted()
sourceProvider.onTransitionProgress(0.2f)
sourceProvider.onTransitionFinished()
}
listener.assertNotStarted()
}
@Test
fun unfoldedAgainAfterFolding_propagatesEvents() {
foldProvider.onFoldUpdate(isFolded = true)
foldProvider.onFoldUpdate(isFolded = false)
// Unfold animation
sourceProvider.onTransitionStarted()
sourceProvider.onTransitionProgress(0.5f)
sourceProvider.onTransitionFinished()
// Fold animation
sourceProvider.onTransitionStarted()
sourceProvider.onTransitionProgress(0.2f)
sourceProvider.onTransitionFinished()
foldProvider.onFoldUpdate(isFolded = true)
listener.clear()
// Second unfold animation after folding
foldProvider.onFoldUpdate(isFolded = false)
sourceProvider.onTransitionStarted()
sourceProvider.onTransitionProgress(0.1f)
sourceProvider.onTransitionFinished()
with(listener.ensureTransitionFinished()) {
assertLastProgress(0.1f)
}
}
}