From 705c665c06a86bee0c76fce4209e59da005e6c1c Mon Sep 17 00:00:00 2001 From: Nick Chameyev Date: Fri, 14 Apr 2023 17:05:12 +0100 Subject: [PATCH] [Unfold animation] Start Launcher animation preemptively to synchronize the first frame Starts unfold animation in Launcher right after receiving configuration change for the unfolded screen. This makes sure that before we unblock the screen we have the first frame of the unfold animation ready in Launcher (transformations are applied). Bug: 271099882 Test: atest com.android.systemui.unfold.util.PreemptiveUnfoldTransitionProgressProviderTest Test: manual testing fold/unfold, checking perfetto traces Test: test with flag enabled/disabled Change-Id: Icb8f91f9264248600d4bed14811445f50aac99c7 --- .../BaseUnfoldMoveFromCenterAnimator.java | 11 +- .../LauncherUnfoldAnimationController.java | 60 +++- .../UnfoldMoveFromCenterHotseatAnimator.java | 2 + ...UnfoldMoveFromCenterWorkspaceAnimator.java | 2 + ...emptiveUnfoldTransitionProgressProvider.kt | 161 +++++++++++ ...iveUnfoldTransitionProgressProviderTest.kt | 261 ++++++++++++++++++ .../launcher3/config/FeatureFlags.java | 6 + 7 files changed, 494 insertions(+), 9 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProvider.kt create mode 100644 quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt diff --git a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java index ad11b7e953..328a7270eb 100644 --- a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java +++ b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java @@ -43,6 +43,10 @@ public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProg new UnfoldMoveFromCenterRotationListener(); private boolean mAnimationInProgress = false; + // Save the last transition progress so we can re-apply it in case we re-register the view for + // the animation (by calling onPrepareViewsForAnimation) + private Float mLastTransitionProgress = null; + public BaseUnfoldMoveFromCenterAnimator(WindowManager windowManager, RotationChangeProvider rotationChangeProvider) { mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager, @@ -63,11 +67,13 @@ public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProg @Override public void onTransitionProgress(float progress) { mMoveFromCenterAnimation.onTransitionProgress(progress); + mLastTransitionProgress = progress; } @CallSuper @Override public void onTransitionFinished() { + mLastTransitionProgress = null; mAnimationInProgress = false; mRotationChangeProvider.removeCallback(mRotationListener); mMoveFromCenterAnimation.onTransitionFinished(); @@ -93,8 +99,11 @@ public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProg mOriginalClipToPadding.clear(); } + @CallSuper protected void onPrepareViewsForAnimation() { - + if (mLastTransitionProgress != null) { + mMoveFromCenterAnimation.onTransitionProgress(mLastTransitionProgress); + } } protected void registerViewForAnimation(View view) { diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java index 8fdafc6059..6d15e8be98 100644 --- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java +++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java @@ -27,10 +27,15 @@ import android.view.WindowManager; import androidx.core.view.OneShotPreDrawListener; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.Hotseat; import com.android.launcher3.Launcher; import com.android.launcher3.Workspace; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.HorizontalInsettableView; +import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.unfold.PreemptiveUnfoldTransitionProgressProvider; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener; import com.android.systemui.unfold.updates.RotationChangeProvider; @@ -40,7 +45,7 @@ import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider; /** * Controls animations that are happening during unfolding foldable devices */ -public class LauncherUnfoldAnimationController { +public class LauncherUnfoldAnimationController implements OnDeviceProfileChangeListener { // Percentage of the width of the quick search bar that will be reduced // from the both sides of the bar when progress is 0 @@ -55,9 +60,11 @@ public class LauncherUnfoldAnimationController { private final NaturalRotationUnfoldProgressProvider mNaturalOrientationProgressProvider; private final UnfoldMoveFromCenterHotseatAnimator mUnfoldMoveFromCenterHotseatAnimator; private final UnfoldMoveFromCenterWorkspaceAnimator mUnfoldMoveFromCenterWorkspaceAnimator; + private PreemptiveUnfoldTransitionProgressProvider mPreemptiveProgressProvider = null; + private Boolean mIsTablet = null; private static final String TRACE_WAIT_TO_HANDLE_UNFOLD_TRANSITION = - "waitingOneFrameBeforeHandlingUnfoldAnimation"; + "LauncherUnfoldAnimationController#waitingForTheNextFrame"; @Nullable private HorizontalInsettableView mQsbInsettable; @@ -68,8 +75,19 @@ public class LauncherUnfoldAnimationController { UnfoldTransitionProgressProvider unfoldTransitionProgressProvider, RotationChangeProvider rotationChangeProvider) { mLauncher = launcher; - mProgressProvider = new ScopedUnfoldTransitionProgressProvider( - unfoldTransitionProgressProvider); + + if (FeatureFlags.PREEMPTIVE_UNFOLD_ANIMATION_START.get()) { + mPreemptiveProgressProvider = new PreemptiveUnfoldTransitionProgressProvider( + unfoldTransitionProgressProvider, launcher.getMainThreadHandler()); + mPreemptiveProgressProvider.init(); + + mProgressProvider = new ScopedUnfoldTransitionProgressProvider( + mPreemptiveProgressProvider); + } else { + mProgressProvider = new ScopedUnfoldTransitionProgressProvider( + unfoldTransitionProgressProvider); + } + mUnfoldMoveFromCenterHotseatAnimator = new UnfoldMoveFromCenterHotseatAnimator(launcher, windowManager, rotationChangeProvider); mUnfoldMoveFromCenterWorkspaceAnimator = new UnfoldMoveFromCenterWorkspaceAnimator(launcher, @@ -85,6 +103,8 @@ public class LauncherUnfoldAnimationController { // Animated only in natural orientation mNaturalOrientationProgressProvider.addCallback(new QsbAnimationListener()); mNaturalOrientationProgressProvider.addCallback(mUnfoldMoveFromCenterHotseatAnimator); + + mLauncher.addOnDeviceProfileChangeListener(this); } /** @@ -96,17 +116,21 @@ public class LauncherUnfoldAnimationController { mQsbInsettable = (HorizontalInsettableView) hotseat.getQsb(); } - handleTransitionOnNextFrame(); + mProgressProvider.setReadyToHandleTransition(true); } - private void handleTransitionOnNextFrame() { + private void preemptivelyStartAnimationOnNextFrame() { Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, TRACE_WAIT_TO_HANDLE_UNFOLD_TRANSITION, /* cookie= */ 0); + + // Start the animation (and apply the transformations) in pre-draw listener to make sure + // that the views are laid out as some transformations depend on the view sizes and position OneShotPreDrawListener.add(mLauncher.getWorkspace(), () -> { Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, TRACE_WAIT_TO_HANDLE_UNFOLD_TRANSITION, /* cookie= */ 0); - mProgressProvider.setReadyToHandleTransition(true); + mPreemptiveProgressProvider.preemptivelyStartTransition( + /* initialProgress= */ 0f); }); } @@ -124,14 +148,34 @@ public class LauncherUnfoldAnimationController { public void onDestroy() { mProgressProvider.destroy(); mNaturalOrientationProgressProvider.destroy(); + mLauncher.removeOnDeviceProfileChangeListener(this); } - /** Called when launcher finished binding its items. */ + /** + * Called when launcher has finished binding its items + */ public void updateRegisteredViewsIfNeeded() { mUnfoldMoveFromCenterHotseatAnimator.updateRegisteredViewsIfNeeded(); mUnfoldMoveFromCenterWorkspaceAnimator.updateRegisteredViewsIfNeeded(); } + @Override + public void onDeviceProfileChanged(DeviceProfile dp) { + if (!FeatureFlags.PREEMPTIVE_UNFOLD_ANIMATION_START.get()) { + return; + } + + if (mIsTablet != null && dp.isTablet != mIsTablet) { + if (dp.isTablet && SystemUiProxy.INSTANCE.get(mLauncher).isActive()) { + // Preemptively start the unfold animation to make sure that we have drawn + // the first frame of the animation before the screen gets unblocked + preemptivelyStartAnimationOnNextFrame(); + } + } + + mIsTablet = dp.isTablet; + } + private class QsbAnimationListener implements TransitionProgressListener { @Override diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java index 70a12d6435..c8141b4642 100644 --- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java +++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java @@ -48,6 +48,8 @@ public class UnfoldMoveFromCenterHotseatAnimator extends BaseUnfoldMoveFromCente View child = hotseatIcons.getChildAt(i); registerViewForAnimation(child); } + + super.onPrepareViewsForAnimation(); } @Override diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java index 7da103ee58..c05b38f5f2 100644 --- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java +++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java @@ -58,6 +58,8 @@ public class UnfoldMoveFromCenterWorkspaceAnimator extends BaseUnfoldMoveFromCen setClipChildren(workspace, false); setClipToPadding(workspace, true); + + super.onPrepareViewsForAnimation(); } @Override diff --git a/quickstep/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProvider.kt b/quickstep/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProvider.kt new file mode 100644 index 0000000000..a9cd0484fd --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProvider.kt @@ -0,0 +1,161 @@ +/* + * 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.quickstep.util.unfold + +import android.os.Handler +import android.os.Trace +import android.util.Log +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener + +/** + * Transition progress provider wrapper that can preemptively start the transition on demand + * without relying on the source provider. When the source provider has started the animation + * it switches to it. + * + * This might be useful when we want to synchronously start the unfold animation and render + * the first frame during turning on the screen. For example, this is used in Launcher where + * we need to render the first frame of the animation immediately after receiving a configuration + * change event so Window Manager will wait for this frame to be rendered before unblocking + * the screen. We can't rely on the original transition progress as it starts the animation + * after the screen fully turned on (and unblocked), at this moment it is already too late to + * start the animation. + * + * Using this provider we could render the first frame preemptively by sending 'transition started' + * and '0' transition progress before the original progress provider sends these events. + */ +class PreemptiveUnfoldTransitionProgressProvider( + private val source: UnfoldTransitionProgressProvider, + private val handler: Handler +) : UnfoldTransitionProgressProvider, TransitionProgressListener { + + private val timeoutRunnable = Runnable { + if (isRunning) { + listeners.forEach { it.onTransitionFinished() } + onPreemptiveStartFinished() + Log.wtf(TAG, "Timeout occurred when waiting for the source transition to start") + } + } + + private val listeners = arrayListOf() + private var isPreemptivelyRunning = false + private var isSourceRunning = false + + private val isRunning: Boolean + get() = isPreemptivelyRunning || isSourceRunning + + private val sourceListener = + object : TransitionProgressListener { + override fun onTransitionStarted() { + handler.removeCallbacks(timeoutRunnable) + + if (!isRunning) { + listeners.forEach { it.onTransitionStarted() } + } + + onPreemptiveStartFinished() + isSourceRunning = true + } + + override fun onTransitionProgress(progress: Float) { + if (isRunning) { + listeners.forEach { it.onTransitionProgress(progress) } + isSourceRunning = true + } + } + + override fun onTransitionFinishing() { + if (isRunning) { + listeners.forEach { it.onTransitionFinishing() } + isSourceRunning = true + } + } + + override fun onTransitionFinished() { + if (isRunning) { + listeners.forEach { it.onTransitionFinished() } + } + + isSourceRunning = false + onPreemptiveStartFinished() + handler.removeCallbacks(timeoutRunnable) + } + } + + fun init() { + source.addCallback(sourceListener) + } + + /** + * Starts the animation preemptively. + * + * - If the source provider is already running, this method won't change any behavior + * - If the source provider has not started running yet, it will call onTransitionStarted + * for all listeners and optionally onTransitionProgress(initialProgress) if supplied. + * When the source provider starts the animation it will switch to send progress and finished + * events from it. + * If the source provider won't start the animation within a timeout, the animation will be + * cancelled and onTransitionFinished will be delivered to the current listeners. + */ + @JvmOverloads + fun preemptivelyStartTransition(initialProgress: Float? = null) { + if (!isRunning) { + Trace.beginAsyncSection("$TAG#startedPreemptively", 0) + + listeners.forEach { it.onTransitionStarted() } + initialProgress?.let { progress -> + listeners.forEach { it.onTransitionProgress(progress) } + } + + handler.removeCallbacks(timeoutRunnable) + handler.postDelayed(timeoutRunnable, PREEMPTIVE_UNFOLD_TIMEOUT_MS) + } + + isPreemptivelyRunning = true + } + + fun cancelPreemptiveStart() { + handler.removeCallbacks(timeoutRunnable) + if (isRunning) { + listeners.forEach { it.onTransitionFinished() } + } + onPreemptiveStartFinished() + } + + private fun onPreemptiveStartFinished() { + if (isPreemptivelyRunning) { + Trace.endAsyncSection("$TAG#startedPreemptively", 0) + isPreemptivelyRunning = false + } + } + + override fun destroy() { + handler.removeCallbacks(timeoutRunnable) + source.removeCallback(sourceListener) + source.destroy() + } + + override fun addCallback(listener: TransitionProgressListener) { + listeners += listener + } + + override fun removeCallback(listener: TransitionProgressListener) { + listeners -= listener + } +} + +const val TAG = "PreemptiveUnfoldTransitionProgressProvider" +const val PREEMPTIVE_UNFOLD_TIMEOUT_MS = 1700L diff --git a/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt b/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt new file mode 100644 index 0000000000..f73be7269d --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt @@ -0,0 +1,261 @@ +/* + * 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.quickstep.util.unfold + +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import android.util.Log +import androidx.test.filters.SmallTest +import com.android.launcher3.util.any +import com.android.launcher3.util.mock +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.anyFloat +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class PreemptiveUnfoldTransitionProgressProviderTest { + + private lateinit var testableLooper: TestableLooper + private lateinit var source: TransitionProgressListener + private lateinit var handler: Handler + private lateinit var oldWtfHandler: Log.TerribleFailureHandler + private val listener: TransitionProgressListener = mock() + private val testWtfHandler: Log.TerribleFailureHandler = mock() + + private lateinit var provider: PreemptiveUnfoldTransitionProgressProvider + + @Before + fun before() { + testableLooper = TestableLooper.get(this) + handler = Handler(testableLooper.looper) + + val testSource = createSource() + source = testSource as TransitionProgressListener + + oldWtfHandler = Log.setWtfHandler(testWtfHandler) + + provider = PreemptiveUnfoldTransitionProgressProvider(testSource, handler) + provider.init() + provider.addCallback(listener) + } + + @After + fun after() { + Log.setWtfHandler(oldWtfHandler) + } + + @Test + fun preemptiveStartInitialProgressNull_transitionStarts() { + provider.preemptivelyStartTransition(initialProgress = null) + + verify(listener).onTransitionStarted() + verify(listener, never()).onTransitionProgress(anyFloat()) + } + + @Test + fun preemptiveStartWithInitialProgress_startsAnimationAndSendsProgress() { + provider.preemptivelyStartTransition(initialProgress = 0.5f) + + verify(listener).onTransitionStarted() + verify(listener).onTransitionProgress(0.5f) + } + + @Test + fun preemptiveStartAndCancel_finishesAnimation() { + provider.preemptivelyStartTransition() + provider.cancelPreemptiveStart() + + with(inOrder(listener)) { + verify(listener).onTransitionStarted() + verify(listener).onTransitionFinished() + } + } + + @Test + fun preemptiveStartAndThenSourceStartsTransition_transitionStarts() { + provider.preemptivelyStartTransition() + source.onTransitionStarted() + + verify(listener).onTransitionStarted() + } + + @Test + fun preemptiveStartAndThenSourceStartsAndFinishesTransition_transitionFinishes() { + provider.preemptivelyStartTransition() + + source.onTransitionStarted() + source.onTransitionFinished() + + with(inOrder(listener)) { + verify(listener).onTransitionStarted() + verify(listener).onTransitionFinished() + } + } + + @Test + fun preemptiveStartAndThenSourceStartsAnimationAndSendsProgress_sendsProgress() { + provider.preemptivelyStartTransition() + + source.onTransitionStarted() + source.onTransitionProgress(0.4f) + + verify(listener).onTransitionProgress(0.4f) + } + + @Test + fun preemptiveStartAndThenSourceSendsProgress_sendsProgress() { + provider.preemptivelyStartTransition() + + source.onTransitionProgress(0.4f) + + verify(listener).onTransitionProgress(0.4f) + } + + @Test + fun preemptiveStartAfterTransitionRunning_transitionStarted() { + source.onTransitionStarted() + + provider.preemptivelyStartTransition() + + verify(listener).onTransitionStarted() + } + + @Test + fun preemptiveStartAfterTransitionRunningAndThenFinished_transitionFinishes() { + source.onTransitionStarted() + + provider.preemptivelyStartTransition() + source.onTransitionFinished() + + with(inOrder(listener)) { + verify(listener).onTransitionStarted() + verify(listener).onTransitionFinished() + } + } + + @Test + fun preemptiveStart_transitionDoesNotFinishAfterTimeout_finishesTransition() { + provider.preemptivelyStartTransition() + + testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1) + testableLooper.processAllMessages() + + with(inOrder(listener)) { + verify(listener).onTransitionStarted() + verify(listener).onTransitionFinished() + } + } + + @Test + fun preemptiveStart_transitionFinishAfterTimeout_logsWtf() { + provider.preemptivelyStartTransition() + + testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1) + testableLooper.processAllMessages() + + verify(testWtfHandler).onTerribleFailure(any(), any(), anyBoolean()) + } + + @Test + fun preemptiveStart_transitionDoesNotFinishBeforeTimeout_doesNotFinishTransition() { + provider.preemptivelyStartTransition() + + testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS - 1) + testableLooper.processAllMessages() + + verify(listener).onTransitionStarted() + } + + @Test + fun preemptiveStart_transitionStarted_timeoutHappened_doesNotFinishTransition() { + provider.preemptivelyStartTransition() + + source.onTransitionStarted() + testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1) + testableLooper.processAllMessages() + + verify(listener).onTransitionStarted() + } + + @Test + fun noPreemptiveStart_transitionStarted_startsTransition() { + source.onTransitionStarted() + + verify(listener).onTransitionStarted() + } + + @Test + fun noPreemptiveStart_transitionProgress_sendsProgress() { + source.onTransitionStarted() + + source.onTransitionProgress(0.5f) + + verify(listener).onTransitionProgress(0.5f) + } + + @Test + fun noPreemptiveStart_transitionFinishes_finishesTransition() { + source.onTransitionStarted() + source.onTransitionProgress(0.5f) + + source.onTransitionFinished() + + with(inOrder(listener)) { + verify(listener).onTransitionStarted() + verify(listener).onTransitionFinished() + } + } + + private fun createSource(): UnfoldTransitionProgressProvider = + object : TransitionProgressListener, UnfoldTransitionProgressProvider { + + private val listeners = arrayListOf() + + override fun addCallback(listener: TransitionProgressListener) { + listeners += listener + } + + override fun removeCallback(listener: TransitionProgressListener) { + listeners -= listener + } + + override fun destroy() {} + + override fun onTransitionStarted() = + listeners.forEach(TransitionProgressListener::onTransitionStarted) + + override fun onTransitionFinishing() = + listeners.forEach(TransitionProgressListener::onTransitionFinishing) + + override fun onTransitionFinished() = + listeners.forEach(TransitionProgressListener::onTransitionFinished) + + override fun onTransitionProgress(progress: Float) = + listeners.forEach { it.onTransitionProgress(progress) } + } +} diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index cdebe440a1..a78846aae1 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -307,6 +307,12 @@ public final class FeatureFlags { "Enables receiving unfold animation events from sysui instead of calculating " + "them in launcher process using hinge sensor values."); + public static final BooleanFlag PREEMPTIVE_UNFOLD_ANIMATION_START = getDebugFlag(270397209, + "PREEMPTIVE_UNFOLD_ANIMATION_START", ENABLED, + "Enables starting the unfold animation preemptively when unfolding, without" + + "waiting for SystemUI and then merging the SystemUI progress whenever we " + + "start receiving the events"); + // TODO(Block 23): Clean up flags public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(270397206, "ENABLE_GRID_ONLY_OVERVIEW", DISABLED,