Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit eba59517 authored by William Xiao's avatar William Xiao
Browse files

Implement updated low light animations

The transition between low light and the user dream is currently the
only dream to dream transition. The prior dream does not get any time
to play animations as the default dream transition is played as soon as
the new dream starts.

This change allows an animation to play before setting the system
dream to low light for a smooth transition.

The overlay exit animation is reintroduced from ag/20377749.
The animation was removed since transitions between dreams and core
SysUI components is now centrally controlled by keyguard. Since this
transition is between two dreams, keyguard is not able to coordinate.

Screen capture from device: http://screencast/cast/NDU0NzAyNjIyMzEwNDAwMHwyNmU0ZWExNy0xNg

Bug: 260638159
Test: atest LowLightTransitionCoordinatorTest LowLightDreamManagerTest DreamOverlayAnimationsControllerTest DreamOverlayContainerViewControllerTest
Change-Id: Ib38a8848ea6361d3d6ef267f2f3a84d3daf6711d
parent 6c7e4554
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ public final class LowLightDreamManager {
    public static final int AMBIENT_LIGHT_MODE_LOW_LIGHT = 2;

    private final DreamManager mDreamManager;
    private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;

    @Nullable
    private final ComponentName mLowLightDreamComponent;
@@ -81,8 +82,10 @@ public final class LowLightDreamManager {
    @Inject
    public LowLightDreamManager(
            DreamManager dreamManager,
            LowLightTransitionCoordinator lowLightTransitionCoordinator,
            @Named(LOW_LIGHT_DREAM_COMPONENT) @Nullable ComponentName lowLightDreamComponent) {
        mDreamManager = dreamManager;
        mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
        mLowLightDreamComponent = lowLightDreamComponent;
    }

@@ -111,7 +114,9 @@ public final class LowLightDreamManager {

        mAmbientLightMode = ambientLightMode;

        mDreamManager.setSystemDreamComponent(mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT
                ? mLowLightDreamComponent : null);
        boolean shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT;
        mLowLightTransitionCoordinator.notifyBeforeLowLightTransition(shouldEnterLowLight,
                () -> mDreamManager.setSystemDreamComponent(
                        shouldEnterLowLight ? mLowLightDreamComponent : null));
    }
}
+111 −0
Original line number Diff line number Diff line
/*
 * 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();
        }
    }
}
+25 −14
Original line number Diff line number Diff line
@@ -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 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.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

@@ -43,45 +46,53 @@ public class LowLightDreamManagerTest {
    @Mock
    private DreamManager mDreamManager;

    @Mock
    private LowLightTransitionCoordinator mTransitionCoordinator;

    @Mock
    private ComponentName mDreamComponent;

    LowLightDreamManager mLowLightDreamManager;

    @Before
    public void setUp() {
        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
    public void setAmbientLightMode_lowLight_setSystemDream() {
        final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
                mDreamComponent);

        lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
        mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);

        verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(true), any());
        verify(mDreamManager).setSystemDreamComponent(mDreamComponent);
    }

    @Test
    public void setAmbientLightMode_regularLight_clearSystemDream() {
        final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
                mDreamComponent);

        lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
        mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);

        verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(false), any());
        verify(mDreamManager).setSystemDreamComponent(null);
    }

    @Test
    public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() {
        final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
                mDreamComponent);

        // Set to low light first.
        lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
        mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
        clearInvocations(mDreamManager);

        // Return to default unknown mode.
        lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
        mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);

        verify(mDreamManager).setSystemDreamComponent(null);
    }
@@ -89,7 +100,7 @@ public class LowLightDreamManagerTest {
    @Test
    public void setAmbientLightMode_dreamComponentNotSet_doNothing() {
        final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
                null /*dream component*/);
                mTransitionCoordinator, null /*dream component*/);

        lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);

+113 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -774,7 +774,7 @@
    <!-- Duration in milliseconds of the dream in complications fade-in animation. -->
    <integer name="config_dreamOverlayInComplicationsDurationMs">250</integer>
    <!-- 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
    active when the device is locked and docked. 0 indicates disabled. Default is 1 minute. -->
Loading