Merge "[Unfold animation] Start Launcher animation preemptively to synchronize the first frame" into udc-dev
diff --git a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
index ad11b7e..328a727 100644
--- a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
@@ -43,6 +43,10 @@
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 @@
@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 @@
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 8fdafc6..6d15e8b 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -27,10 +27,15 @@
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 @@
/**
* 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 @@
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 @@
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 @@
// Animated only in natural orientation
mNaturalOrientationProgressProvider.addCallback(new QsbAnimationListener());
mNaturalOrientationProgressProvider.addCallback(mUnfoldMoveFromCenterHotseatAnimator);
+
+ mLauncher.addOnDeviceProfileChangeListener(this);
}
/**
@@ -96,17 +116,21 @@
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 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 70a12d6..c8141b4 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
@@ -48,6 +48,8 @@
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 7da103e..c05b38f 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
@@ -58,6 +58,8 @@
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 0000000..a9cd048
--- /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<TransitionProgressListener>()
+ 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 0000000..f73be72
--- /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<TransitionProgressListener>()
+
+ 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 331ae5d..fef6639 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -314,6 +314,12 @@
"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,