Merge "[Unfold animation] Disable task animations when folding by default" into udc-dev

This commit is contained in:
Nick Chameyev
2023-05-31 13:06:28 +00:00
committed by Android (Google) Code Review
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)
}
}
}