Merge "[Unfold animation] Start Launcher animation preemptively to synchronize the first frame" into udc-dev am: 1aa2383d26
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/22647883 Change-Id: If2b5feb329926a9841cef56b137251a839b82120 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,6 +48,8 @@ public class UnfoldMoveFromCenterHotseatAnimator extends BaseUnfoldMoveFromCente
|
||||
View child = hotseatIcons.getChildAt(i);
|
||||
registerViewForAnimation(child);
|
||||
}
|
||||
|
||||
super.onPrepareViewsForAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -58,6 +58,8 @@ public class UnfoldMoveFromCenterWorkspaceAnimator extends BaseUnfoldMoveFromCen
|
||||
|
||||
setClipChildren(workspace, false);
|
||||
setClipToPadding(workspace, true);
|
||||
|
||||
super.onPrepareViewsForAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+161
@@ -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
|
||||
+261
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -314,6 +314,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,
|
||||
|
||||
Reference in New Issue
Block a user