Merge changes from topic "low_light_animations" into tm-qpr-dev am: b85665b316

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21456149

Change-Id: I148cf37ec79c3deda9c6d411bff4b8498e204cd3
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
William Xiao
2023-03-07 02:32:59 +00:00
committed by Automerger Merge Worker
9 changed files with 441 additions and 55 deletions

View File

@@ -72,6 +72,7 @@ public final class LowLightDreamManager {
public static final int AMBIENT_LIGHT_MODE_LOW_LIGHT = 2; public static final int AMBIENT_LIGHT_MODE_LOW_LIGHT = 2;
private final DreamManager mDreamManager; private final DreamManager mDreamManager;
private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
@Nullable @Nullable
private final ComponentName mLowLightDreamComponent; private final ComponentName mLowLightDreamComponent;
@@ -81,8 +82,10 @@ public final class LowLightDreamManager {
@Inject @Inject
public LowLightDreamManager( public LowLightDreamManager(
DreamManager dreamManager, DreamManager dreamManager,
LowLightTransitionCoordinator lowLightTransitionCoordinator,
@Named(LOW_LIGHT_DREAM_COMPONENT) @Nullable ComponentName lowLightDreamComponent) { @Named(LOW_LIGHT_DREAM_COMPONENT) @Nullable ComponentName lowLightDreamComponent) {
mDreamManager = dreamManager; mDreamManager = dreamManager;
mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mLowLightDreamComponent = lowLightDreamComponent; mLowLightDreamComponent = lowLightDreamComponent;
} }
@@ -111,7 +114,9 @@ public final class LowLightDreamManager {
mAmbientLightMode = ambientLightMode; mAmbientLightMode = ambientLightMode;
mDreamManager.setSystemDreamComponent(mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT boolean shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT;
? mLowLightDreamComponent : null); mLowLightTransitionCoordinator.notifyBeforeLowLightTransition(shouldEnterLowLight,
() -> mDreamManager.setSystemDreamComponent(
shouldEnterLowLight ? mLowLightDreamComponent : null));
} }
} }

View File

@@ -0,0 +1,111 @@
/*
* 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.dream.lowlight;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Helper class that allows listening and running animations before entering or exiting low light.
*/
@Singleton
public class LowLightTransitionCoordinator {
/**
* Listener that is notified before low light entry.
*/
public interface LowLightEnterListener {
/**
* Callback that is notified before the device enters low light.
*
* @return an optional animator that will be waited upon before entering low light.
*/
Animator onBeforeEnterLowLight();
}
/**
* Listener that is notified before low light exit.
*/
public interface LowLightExitListener {
/**
* Callback that is notified before the device exits low light.
*
* @return an optional animator that will be waited upon before exiting low light.
*/
Animator onBeforeExitLowLight();
}
private LowLightEnterListener mLowLightEnterListener;
private LowLightExitListener mLowLightExitListener;
@Inject
public LowLightTransitionCoordinator() {
}
/**
* Sets the listener for the low light enter event.
*
* Only one listener can be set at a time. This method will overwrite any previously set
* listener. Null can be used to unset the listener.
*/
public void setLowLightEnterListener(@Nullable LowLightEnterListener lowLightEnterListener) {
mLowLightEnterListener = lowLightEnterListener;
}
/**
* Sets the listener for the low light exit event.
*
* Only one listener can be set at a time. This method will overwrite any previously set
* listener. Null can be used to unset the listener.
*/
public void setLowLightExitListener(@Nullable LowLightExitListener lowLightExitListener) {
mLowLightExitListener = lowLightExitListener;
}
/**
* Notifies listeners that the device is about to enter or exit low light.
*
* @param entering true if listeners should be notified before entering low light, false if this
* is notifying before exiting.
* @param callback callback that will be run after listeners complete.
*/
void notifyBeforeLowLightTransition(boolean entering, Runnable callback) {
Animator animator = null;
if (entering && mLowLightEnterListener != null) {
animator = mLowLightEnterListener.onBeforeEnterLowLight();
} else if (!entering && mLowLightExitListener != null) {
animator = mLowLightExitListener.onBeforeExitLowLight();
}
// If the listener returned an animator to indicate it was running an animation, run the
// callback after the animation completes, otherwise call the callback directly.
if (animator != null) {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
callback.run();
}
});
} else {
callback.run();
}
}
}

View File

@@ -21,7 +21,10 @@ import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE
import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN; import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -43,45 +46,53 @@ public class LowLightDreamManagerTest {
@Mock @Mock
private DreamManager mDreamManager; private DreamManager mDreamManager;
@Mock
private LowLightTransitionCoordinator mTransitionCoordinator;
@Mock @Mock
private ComponentName mDreamComponent; private ComponentName mDreamComponent;
LowLightDreamManager mLowLightDreamManager;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
// Automatically run any provided Runnable to mTransitionCoordinator to simplify testing.
doAnswer(invocation -> {
((Runnable) invocation.getArgument(1)).run();
return null;
}).when(mTransitionCoordinator).notifyBeforeLowLightTransition(anyBoolean(),
any(Runnable.class));
mLowLightDreamManager = new LowLightDreamManager(mDreamManager, mTransitionCoordinator,
mDreamComponent);
} }
@Test @Test
public void setAmbientLightMode_lowLight_setSystemDream() { public void setAmbientLightMode_lowLight_setSystemDream() {
final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager, mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
mDreamComponent);
lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(true), any());
verify(mDreamManager).setSystemDreamComponent(mDreamComponent); verify(mDreamManager).setSystemDreamComponent(mDreamComponent);
} }
@Test @Test
public void setAmbientLightMode_regularLight_clearSystemDream() { public void setAmbientLightMode_regularLight_clearSystemDream() {
final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager, mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
mDreamComponent);
lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(false), any());
verify(mDreamManager).setSystemDreamComponent(null); verify(mDreamManager).setSystemDreamComponent(null);
} }
@Test @Test
public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() { public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() {
final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
mDreamComponent);
// Set to low light first. // Set to low light first.
lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
clearInvocations(mDreamManager); clearInvocations(mDreamManager);
// Return to default unknown mode. // Return to default unknown mode.
lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN); mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
verify(mDreamManager).setSystemDreamComponent(null); verify(mDreamManager).setSystemDreamComponent(null);
} }
@@ -89,7 +100,7 @@ public class LowLightDreamManagerTest {
@Test @Test
public void setAmbientLightMode_dreamComponentNotSet_doNothing() { public void setAmbientLightMode_dreamComponentNotSet_doNothing() {
final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager, final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
null /*dream component*/); mTransitionCoordinator, null /*dream component*/);
lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);

View File

@@ -0,0 +1,113 @@
/*
* 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.dream.lowlight;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.animation.Animator;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class LowLightTransitionCoordinatorTest {
@Mock
private LowLightTransitionCoordinator.LowLightEnterListener mEnterListener;
@Mock
private LowLightTransitionCoordinator.LowLightExitListener mExitListener;
@Mock
private Animator mAnimator;
@Captor
private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor;
@Mock
private Runnable mRunnable;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void onEnterCalledOnListeners() {
LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
coordinator.setLowLightEnterListener(mEnterListener);
coordinator.notifyBeforeLowLightTransition(true, mRunnable);
verify(mEnterListener).onBeforeEnterLowLight();
verify(mRunnable).run();
}
@Test
public void onExitCalledOnListeners() {
LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
coordinator.setLowLightExitListener(mExitListener);
coordinator.notifyBeforeLowLightTransition(false, mRunnable);
verify(mExitListener).onBeforeExitLowLight();
verify(mRunnable).run();
}
@Test
public void listenerNotCalledAfterRemoval() {
LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
coordinator.setLowLightEnterListener(mEnterListener);
coordinator.setLowLightEnterListener(null);
coordinator.notifyBeforeLowLightTransition(true, mRunnable);
verifyZeroInteractions(mEnterListener);
verify(mRunnable).run();
}
@Test
public void runnableCalledAfterAnimationEnds() {
when(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator);
LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
coordinator.setLowLightEnterListener(mEnterListener);
coordinator.notifyBeforeLowLightTransition(true, mRunnable);
// Animator listener is added and the runnable is not run yet.
verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
verifyZeroInteractions(mRunnable);
// Runnable is run once the animation ends.
mAnimatorListenerCaptor.getValue().onAnimationEnd(null);
verify(mRunnable).run();
}
}

View File

@@ -774,7 +774,7 @@
<!-- Duration in milliseconds of the dream in complications fade-in animation. --> <!-- Duration in milliseconds of the dream in complications fade-in animation. -->
<integer name="config_dreamOverlayInComplicationsDurationMs">250</integer> <integer name="config_dreamOverlayInComplicationsDurationMs">250</integer>
<!-- Duration in milliseconds of the y-translation animation when entering a dream --> <!-- Duration in milliseconds of the y-translation animation when entering a dream -->
<integer name="config_dreamOverlayInTranslationYDurationMs">917</integer> <integer name="config_dreamOverlayInTranslationYDurationMs">1167</integer>
<!-- Delay in milliseconds before switching to the dock user and dreaming if a secondary user is <!-- Delay in milliseconds before switching to the dock user and dreaming if a secondary user is
active when the device is locked and docked. 0 indicates disabled. Default is 1 minute. --> active when the device is locked and docked. 0 indicates disabled. Default is 1 minute. -->

View File

@@ -43,7 +43,6 @@ import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -131,9 +130,17 @@ constructor(
} }
} }
/** Starts the dream content and dream overlay entry animations. */ /**
* Starts the dream content and dream overlay entry animations.
*
* @param downwards if true, the entry animation translations downwards into position rather
* than upwards.
*/
@JvmOverloads @JvmOverloads
fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) { fun startEntryAnimations(
downwards: Boolean,
animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
) {
cancelAnimations() cancelAnimations()
mAnimator = mAnimator =
@@ -153,7 +160,7 @@ constructor(
interpolator = Interpolators.LINEAR interpolator = Interpolators.LINEAR
), ),
translationYAnimator( translationYAnimator(
from = mDreamInTranslationYDistance.toFloat(), from = mDreamInTranslationYDistance.toFloat() * (if (downwards) -1 else 1),
to = 0f, to = 0f,
durationMs = mDreamInTranslationYDurationMs, durationMs = mDreamInTranslationYDurationMs,
interpolator = Interpolators.EMPHASIZED_DECELERATE interpolator = Interpolators.EMPHASIZED_DECELERATE
@@ -167,6 +174,71 @@ constructor(
} }
} }
/**
* Starts the dream content and dream overlay exit animations.
*
* This should only be used when the low light dream is entering, animations to/from other SysUI
* views is controlled by `transitionViewModel`.
*/
// TODO(b/256916668): integrate with the keyguard transition model once dream surfaces work is
// done.
@JvmOverloads
fun startExitAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }): Animator {
cancelAnimations()
mAnimator =
animatorBuilder().apply {
playTogether(
translationYAnimator(
from = 0f,
to = -mDreamInTranslationYDistance.toFloat(),
durationMs = mDreamInTranslationYDurationMs,
delayMs = 0,
interpolator = Interpolators.EMPHASIZED
),
alphaAnimator(
from =
mCurrentAlphaAtPosition.getOrDefault(
key = POSITION_BOTTOM,
defaultValue = 1f
),
to = 0f,
durationMs = mDreamInComplicationsAnimDurationMs,
delayMs = 0,
positions = POSITION_BOTTOM
)
.apply {
doOnEnd {
// The logical end of the animation is once the alpha and blur
// animations finish, end the animation so that any listeners are
// notified. The Y translation animation is much longer than all of
// the other animations due to how the spec is defined, but is not
// expected to run to completion.
mAnimator?.end()
}
},
alphaAnimator(
from =
mCurrentAlphaAtPosition.getOrDefault(
key = POSITION_TOP,
defaultValue = 1f
),
to = 0f,
durationMs = mDreamInComplicationsAnimDurationMs,
delayMs = 0,
positions = POSITION_TOP
)
)
doOnEnd {
mAnimator = null
mOverlayStateController.setExitAnimationsRunning(false)
}
start()
}
mOverlayStateController.setExitAnimationsRunning(true)
return mAnimator as AnimatorSet
}
/** Starts the dream content and dream overlay exit animations. */ /** Starts the dream content and dream overlay exit animations. */
fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) { fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) {
cancelAnimations() cancelAnimations()
@@ -182,19 +254,6 @@ constructor(
} }
} }
/**
* Ends the dream content and dream overlay animations, if they're currently running.
*
* @see [AnimatorSet.end]
*/
fun endAnimations() {
mAnimator =
mAnimator?.let {
it.end()
null
}
}
private fun blurAnimator( private fun blurAnimator(
view: View, view: View,
fromBlurRadius: Float, fromBlurRadius: Float,

View File

@@ -23,6 +23,7 @@ import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM; import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM;
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP; import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP;
import android.animation.Animator;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Handler; import android.os.Handler;
import android.util.MathUtils; import android.util.MathUtils;
@@ -31,6 +32,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.Main;
@@ -54,11 +56,14 @@ import javax.inject.Named;
* View controller for {@link DreamOverlayContainerView}. * View controller for {@link DreamOverlayContainerView}.
*/ */
@DreamOverlayComponent.DreamOverlayScope @DreamOverlayComponent.DreamOverlayScope
public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> { public class DreamOverlayContainerViewController extends
ViewController<DreamOverlayContainerView> implements
LowLightTransitionCoordinator.LowLightEnterListener {
private final DreamOverlayStatusBarViewController mStatusBarViewController; private final DreamOverlayStatusBarViewController mStatusBarViewController;
private final BlurUtils mBlurUtils; private final BlurUtils mBlurUtils;
private final DreamOverlayAnimationsController mDreamOverlayAnimationsController; private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
private final DreamOverlayStateController mStateController; private final DreamOverlayStateController mStateController;
private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
private final ComplicationHostViewController mComplicationHostViewController; private final ComplicationHostViewController mComplicationHostViewController;
@@ -143,19 +148,18 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
}; };
/** /**
* If true, overlay entry animations should be skipped once. * If {@code true}, the dream has just transitioned from the low light dream back to the user
* * dream and we should play an entry animation where the overlay slides in downwards from the
* This is turned on when exiting low light and should be turned off once the entry animations * top instead of the typicla slide in upwards from the bottom.
* are skipped once.
*/ */
private boolean mSkipEntryAnimations; private boolean mExitingLowLight;
private final DreamOverlayStateController.Callback private final DreamOverlayStateController.Callback
mDreamOverlayStateCallback = mDreamOverlayStateCallback =
new DreamOverlayStateController.Callback() { new DreamOverlayStateController.Callback() {
@Override @Override
public void onExitLowLight() { public void onExitLowLight() {
mSkipEntryAnimations = true; mExitingLowLight = true;
} }
}; };
@@ -165,6 +169,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
ComplicationHostViewController complicationHostViewController, ComplicationHostViewController complicationHostViewController,
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView, @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
DreamOverlayStatusBarViewController statusBarViewController, DreamOverlayStatusBarViewController statusBarViewController,
LowLightTransitionCoordinator lowLightTransitionCoordinator,
BlurUtils blurUtils, BlurUtils blurUtils,
@Main Handler handler, @Main Handler handler,
@Main Resources resources, @Main Resources resources,
@@ -182,6 +187,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
mBlurUtils = blurUtils; mBlurUtils = blurUtils;
mDreamOverlayAnimationsController = animationsController; mDreamOverlayAnimationsController = animationsController;
mStateController = stateController; mStateController = stateController;
mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mBouncerlessScrimController = bouncerlessScrimController; mBouncerlessScrimController = bouncerlessScrimController;
mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback); mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
@@ -208,6 +214,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
mStatusBarViewController.init(); mStatusBarViewController.init();
mComplicationHostViewController.init(); mComplicationHostViewController.init();
mDreamOverlayAnimationsController.init(mView); mDreamOverlayAnimationsController.init(mView);
mLowLightTransitionCoordinator.setLowLightEnterListener(this);
} }
@Override @Override
@@ -219,14 +226,10 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
// Start dream entry animations. Skip animations for low light clock. // Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) { if (!mStateController.isLowLightActive()) {
mDreamOverlayAnimationsController.startEntryAnimations(); // If this is transitioning from the low light dream to the user dream, the overlay
// should translate in downwards instead of upwards.
if (mSkipEntryAnimations) { mDreamOverlayAnimationsController.startEntryAnimations(mExitingLowLight);
// If we're transitioning from the low light dream back to the user dream, skip the mExitingLowLight = false;
// overlay animations and show immediately.
mDreamOverlayAnimationsController.endAnimations();
mSkipEntryAnimations = false;
}
} }
} }
@@ -310,4 +313,12 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor); mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
} }
@Override
public Animator onBeforeEnterLowLight() {
// Return the animator so that the transition coordinator waits for the overlay exit
// animations to finish before entering low light, as otherwise the default DreamActivity
// animation plays immediately and there's no time for this animation to play.
return mDreamOverlayAnimationsController.startExitAnimations();
}
} }

View File

@@ -1,6 +1,8 @@
package com.android.systemui.dreams package com.android.systemui.dreams
import android.animation.Animator
import android.animation.AnimatorSet import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.view.View import android.view.View
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
@@ -10,13 +12,16 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransition
import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.anyLong import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq import org.mockito.Mockito.eq
@@ -70,6 +75,19 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
runBlocking(Dispatchers.Main.immediate) { controller.init(mockView) } runBlocking(Dispatchers.Main.immediate) { controller.init(mockView) }
} }
@Test
fun testExitAnimationUpdatesState() {
controller.startExitAnimations(animatorBuilder = { mockAnimator })
verify(stateController).setExitAnimationsRunning(true)
val captor = argumentCaptor<Animator.AnimatorListener>()
verify(mockAnimator).addListener(captor.capture())
captor.value.onAnimationEnd(mockAnimator)
verify(stateController).setExitAnimationsRunning(false)
}
@Test @Test
fun testWakeUpCallsExecutor() { fun testWakeUpCallsExecutor() {
val mockExecutor: DelayableExecutor = mock() val mockExecutor: DelayableExecutor = mock()
@@ -87,7 +105,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
fun testWakeUpAfterStartWillCancel() { fun testWakeUpAfterStartWillCancel() {
val mockStartAnimator: AnimatorSet = mock() val mockStartAnimator: AnimatorSet = mock()
controller.startEntryAnimations(animatorBuilder = { mockStartAnimator }) controller.startEntryAnimations(false, animatorBuilder = { mockStartAnimator })
verify(mockStartAnimator, never()).cancel() verify(mockStartAnimator, never()).cancel()
@@ -100,4 +118,50 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
// animator. // animator.
verify(mockStartAnimator, times(1)).cancel() verify(mockStartAnimator, times(1)).cancel()
} }
@Test
fun testEntryAnimations_translatesUpwards() {
val mockStartAnimator: AnimatorSet = mock()
controller.startEntryAnimations(
/* downwards= */ false,
animatorBuilder = { mockStartAnimator }
)
val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java)
verify(mockStartAnimator).playTogether(animatorCaptor.capture())
// Check if there's a ValueAnimator starting at the expected Y distance.
val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator>
assertTrue(
animators.any {
// Call setCurrentFraction so the animated value jumps to the initial value.
it.setCurrentFraction(0f)
it.animatedValue == DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat()
}
)
}
@Test
fun testEntryAnimations_translatesDownwards() {
val mockStartAnimator: AnimatorSet = mock()
controller.startEntryAnimations(
/* downwards= */ true,
animatorBuilder = { mockStartAnimator }
)
val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java)
verify(mockStartAnimator).playTogether(animatorCaptor.capture())
// Check if there's a ValueAnimator starting at the expected Y distance.
val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator>
assertTrue(
animators.any {
// Call setCurrentFraction so the animated value jumps to the initial value.
it.setCurrentFraction(0f)
it.animatedValue == -DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat()
}
)
}
} }

View File

@@ -17,6 +17,7 @@
package com.android.systemui.dreams; package com.android.systemui.dreams;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@@ -33,6 +34,7 @@ import android.view.ViewTreeObserver;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.ComplicationHostViewController; import com.android.systemui.dreams.complication.ComplicationHostViewController;
@@ -64,6 +66,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
@Mock @Mock
DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController; DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController;
@Mock
LowLightTransitionCoordinator mLowLightTransitionCoordinator;
@Mock @Mock
DreamOverlayContainerView mDreamOverlayContainerView; DreamOverlayContainerView mDreamOverlayContainerView;
@@ -109,6 +114,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mComplicationHostViewController, mComplicationHostViewController,
mDreamOverlayContentView, mDreamOverlayContentView,
mDreamOverlayStatusBarViewController, mDreamOverlayStatusBarViewController,
mLowLightTransitionCoordinator,
mBlurUtils, mBlurUtils,
mHandler, mHandler,
mResources, mResources,
@@ -200,7 +206,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mController.onViewAttached(); mController.onViewAttached();
verify(mAnimationsController).startEntryAnimations(); verify(mAnimationsController).startEntryAnimations(false);
verify(mAnimationsController, never()).cancelAnimations(); verify(mAnimationsController, never()).cancelAnimations();
} }
@@ -210,11 +216,11 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mController.onViewAttached(); mController.onViewAttached();
verify(mAnimationsController, never()).startEntryAnimations(); verify(mAnimationsController, never()).startEntryAnimations(anyBoolean());
} }
@Test @Test
public void testSkipEntryAnimationsWhenExitingLowLight() { public void testDownwardEntryAnimationsWhenExitingLowLight() {
ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor = ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
when(mStateController.isLowLightActive()).thenReturn(false); when(mStateController.isLowLightActive()).thenReturn(false);
@@ -230,8 +236,14 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mController.onViewAttached(); mController.onViewAttached();
// Entry animations should be started then immediately ended to skip to the end. // Entry animations should be started then immediately ended to skip to the end.
verify(mAnimationsController).startEntryAnimations(); verify(mAnimationsController).startEntryAnimations(true);
verify(mAnimationsController).endAnimations(); }
@Test
public void testStartsExitAnimationsBeforeEnteringLowLight() {
mController.onBeforeEnterLowLight();
verify(mAnimationsController).startExitAnimations();
} }
@Test @Test