[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:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user