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

Commit 85bc534d authored by Xiaowen Lei's avatar Xiaowen Lei
Browse files

Use ViewHierarchyAnimator to animate media container layout.

Instead of LayoutTransition.
  - The main advantage over LayoutTransition: no need for the second
    OnLayoutChangeListener. With LayoutTransition, the second
    OnLayoutChangeListener is necessary in order to disable the
    LayoutTransition.CHANGING.
  - The main disadvantage is testing. It's tricky to verify the usage of
    ViewHierarchyAnimator.Companion methods. But it's still achievable,
    via java reflect. (ViewHierarchyAnimator.Companion is essentially
    a singleton.)

The transition basially looks the same as with LayoutTransition. It does
use a different interpolator: Interpolators.STANDARD vs DECELERATE.

Other transitions tested include:
  - Same transition not in split shade
  - AOD <-> Lockscreen <-> Launcher
  - Fold / unfold, and rotate
  - Pick a different wallpaper/clock

Bug: 296907535
Test: On split shade with media, turn on/off flashlight via shortcut.
Test: With CHANGING duration = 10000, test other transitions with media.
Test: KeyguardStatusViewControllerTest
Change-Id: I9f41d75edd54196706e9f6009c9089148ffe7707
parent 1c716268
Loading
Loading
Loading
Loading
+0 −9
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.keyguard;

import static java.util.Collections.emptySet;

import android.animation.LayoutTransition;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
@@ -79,14 +78,6 @@ public class KeyguardStatusView extends GridLayout {
        mKeyguardSlice = findViewById(R.id.keyguard_slice_view);

        mMediaHostContainer = findViewById(R.id.status_view_media_container);
        if (mMediaHostContainer != null) {
            LayoutTransition mediaLayoutTransition = new LayoutTransition();
            ((ViewGroup) mMediaHostContainer).setLayoutTransition(mediaLayoutTransition);
            mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
            mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
            mediaLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
            mediaLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
        }

        updateDark();
    }
+6 −23
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CL
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;

import android.animation.Animator;
import android.animation.LayoutTransition;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.content.res.Configuration;
@@ -50,14 +49,15 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.res.R;
import com.android.systemui.animation.ViewHierarchyAnimator;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.power.shared.model.ScreenPowerState;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.ScreenPowerState;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -175,27 +175,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
                            return;
                        }

                        final LayoutTransition mediaLayoutTransition =
                                ((ViewGroup) mediaHostContainer).getLayoutTransition();
                        if (mediaLayoutTransition == null) return;

                        mediaLayoutTransition.enableTransitionType(LayoutTransition.CHANGING);
                        ViewHierarchyAnimator.Companion.animateNextUpdate(mediaHostContainer,
                                Interpolators.STANDARD, /* duration= */ 500L,
                                /* animateChildren= */ false);
                    });

            mediaHostContainer.addOnLayoutChangeListener(
                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                        final LayoutTransition mediaLayoutTransition =
                                ((ViewGroup) mediaHostContainer).getLayoutTransition();
                        if (mediaLayoutTransition == null) return;
                        if (!mediaLayoutTransition.isTransitionTypeEnabled(
                                LayoutTransition.CHANGING)) {
                            return;
                        }
                        // Note: when this is called, the LayoutTransition is already been set up.
                        // Disables the LayoutTransition until it's explicitly enabled again.
                        mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGING);
                    }
            );
        }

        mDumpManager.registerDumpable(getInstanceName(), this);
+0 −2
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.animation.LayoutTransition;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
@@ -69,7 +68,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {

    @Mock protected KeyguardClockSwitch mKeyguardClockSwitch;
    @Mock protected FrameLayout mMediaHostContainer;
    @Mock protected LayoutTransition mMediaLayoutTransition;

    @Before
    public void setup() {
+35 −41
Original line number Diff line number Diff line
@@ -16,19 +16,24 @@

package com.android.keyguard;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.animation.LayoutTransition;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;

import com.android.app.animation.Interpolators;
import com.android.systemui.animation.ViewHierarchyAnimator;
import com.android.systemui.plugins.ClockConfig;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.res.R;
@@ -39,6 +44,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;

import java.lang.reflect.Field;

@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner.class)
@@ -142,19 +149,7 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll
    }

    @Test
    public void onInit_addsOnLayoutChangeListenerToMediaHostContainer() {
        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
                mMediaHostContainer);

        mController.onInit();

        ArgumentCaptor<View.OnLayoutChangeListener> captor =
                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
        verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture());
    }

    @Test
    public void clockSwitchHeightChanged_mediaChangingLayoutTransitionEnabled() {
    public void clockSwitchHeightChanged_animatesMediaHostContainer() {
        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
                mMediaHostContainer);

@@ -167,6 +162,10 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll
        // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
        // Below here is the actual test.

        ViewHierarchyAnimator.Companion animator = ViewHierarchyAnimator.Companion;
        ViewHierarchyAnimator.Companion spiedAnimator = spy(animator);
        setCompanion(spiedAnimator);

        View.OnLayoutChangeListener listener = captor.getValue();

        mController.setSplitShadeEnabled(true);
@@ -174,17 +173,20 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll
        when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
        when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
        when(mMediaHostContainer.getHeight()).thenReturn(200);
        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);

        when(mKeyguardClockSwitch.getHeight()).thenReturn(0);
        listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
                0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
                0, /* oldBottom = */ 200);
        verify(mMediaLayoutTransition).enableTransitionType(LayoutTransition.CHANGING);
        verify(spiedAnimator).animateNextUpdate(mMediaHostContainer,
                Interpolators.STANDARD, /* duration= */ 500L, /* animateChildren= */ false);

        // Resets ViewHierarchyAnimator.Companion to its original value
        setCompanion(animator);
    }

    @Test
    public void clockSwitchHeightNotChanged_mediaChangingLayoutTransitionNotEnabled() {
    public void clockSwitchHeightNotChanged_doesNotAnimateMediaOutputContainer() {
        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
                mMediaHostContainer);

@@ -197,6 +199,10 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll
        // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
        // Below here is the actual test.

        ViewHierarchyAnimator.Companion animator = ViewHierarchyAnimator.Companion;
        ViewHierarchyAnimator.Companion spiedAnimator = spy(animator);
        setCompanion(spiedAnimator);

        View.OnLayoutChangeListener listener = captor.getValue();

        mController.setSplitShadeEnabled(true);
@@ -204,36 +210,24 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll
        when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
        when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
        when(mMediaHostContainer.getHeight()).thenReturn(200);
        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);

        when(mKeyguardClockSwitch.getHeight()).thenReturn(200);
        listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
                0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
                0, /* oldBottom = */ 200);
        verify(mMediaLayoutTransition, never()).enableTransitionType(LayoutTransition.CHANGING);
    }
        verify(spiedAnimator, never()).animateNextUpdate(any(), any(), anyLong(), anyBoolean());

    @Test
    public void onMediaHostContainerLayout_disablesChangingLayoutTransition() {
        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
                mMediaHostContainer);

        mController.onInit();

        ArgumentCaptor<View.OnLayoutChangeListener> captor =
                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
        verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture());

        // Above here is the same as `onInit_addsOnLayoutChangeListenerToMediaHostContainer`.
        // Below here is the actual test.

        View.OnLayoutChangeListener listener = captor.getValue();

        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
        // Resets ViewHierarchyAnimator.Companion to its original value
        setCompanion(animator);
    }

        when(mMediaLayoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).thenReturn(
                true);
        listener.onLayoutChange(mMediaHostContainer, 1, 2, 3, 4, 1, 2, 3, 4);
        verify(mMediaLayoutTransition).disableTransitionType(LayoutTransition.CHANGING);
    private void setCompanion(ViewHierarchyAnimator.Companion companion) {
        try {
            Field field = ViewHierarchyAnimator.class.getDeclaredField("Companion");
            field.setAccessible(true);
            field.set(null, companion);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
+0 −15
Original line number Diff line number Diff line
package com.android.keyguard

import android.animation.LayoutTransition
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -35,20 +34,6 @@ class KeyguardStatusViewTest : SysuiTestCase() {
                as KeyguardStatusView
    }

    @Test
    fun mediaViewHasLayoutTransitionInDisabledState() {
        val layoutTransition = (mediaView as ViewGroup).layoutTransition
        assertThat(layoutTransition).isNotNull()
        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_APPEARING))
            .isFalse()
        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_DISAPPEARING))
            .isFalse()
        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.APPEARING)).isFalse()
        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.DISAPPEARING))
            .isFalse()
        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).isFalse()
    }

    @Test
    fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
        val translationY = 1234f