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:
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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. -->
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user