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

Commit 869bbe90 authored by Nick Chameyev's avatar Nick Chameyev Committed by Automerger Merge Worker
Browse files

Merge "Start unfold Shell transition from WM side only when unfolding" into udc-dev am: 25812122

parents e06d085b 25812122
Loading
Loading
Loading
Loading
+46 −12
Original line number Diff line number Diff line
@@ -20,41 +20,73 @@ import static android.view.WindowManager.TRANSIT_CHANGE;

import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;

import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.window.DisplayAreaInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.DeviceStateController.DeviceState;

public class PhysicalDisplaySwitchTransitionLauncher {

    private final DisplayContent mDisplayContent;
    private final WindowManagerService mService;
    private final ActivityTaskManagerService mAtmService;
    private final Context mContext;
    private final TransitionController mTransitionController;

    /**
     * If on a foldable device represents whether the device is folded or not
     * If on a foldable device represents whether we need to show unfold animation when receiving
     * a physical display switch event
     */
    private boolean mShouldRequestTransitionOnDisplaySwitch = false;
    /**
     * Current device state from {@link android.hardware.devicestate.DeviceStateManager}
     */
    private boolean mIsFolded;
    private DeviceState mDeviceState = DeviceState.UNKNOWN;
    private Transition mTransition;

    public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
            TransitionController transitionController) {
        this(displayContent, displayContent.mWmService.mAtmService,
                displayContent.mWmService.mContext, transitionController);
    }

    @VisibleForTesting
    public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
            ActivityTaskManagerService service, Context context,
            TransitionController transitionController) {
        mDisplayContent = displayContent;
        mService = displayContent.mWmService;
        mAtmService = service;
        mContext = context;
        mTransitionController = transitionController;
    }

    /**
     *   Called by the DeviceStateManager callback when the state changes.
     */
    void foldStateChanged(DeviceStateController.DeviceState newDeviceState) {
        // Ignore transitions to/from half-folded.
        if (newDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) return;
        mIsFolded = newDeviceState == DeviceStateController.DeviceState.FOLDED;
    void foldStateChanged(DeviceState newDeviceState) {
        boolean isUnfolding = mDeviceState == FOLDED
                && (newDeviceState == HALF_FOLDED || newDeviceState == OPEN);

        if (isUnfolding) {
            // Request transition only if we are unfolding the device
            mShouldRequestTransitionOnDisplaySwitch = true;
        } else if (newDeviceState != HALF_FOLDED && newDeviceState != OPEN) {
            // Cancel the transition request in case if we are folding or switching to back
            // to the rear display before the displays got switched
            mShouldRequestTransitionOnDisplaySwitch = false;
        }

        mDeviceState = newDeviceState;
    }

    /**
@@ -62,12 +94,12 @@ public class PhysicalDisplaySwitchTransitionLauncher {
     */
    public void requestDisplaySwitchTransitionIfNeeded(int displayId, int oldDisplayWidth,
            int oldDisplayHeight, int newDisplayWidth, int newDisplayHeight) {
        if (!mShouldRequestTransitionOnDisplaySwitch) return;
        if (!mTransitionController.isShellTransitionsEnabled()) return;
        if (!mDisplayContent.getLastHasContent()) return;

        boolean shouldRequestUnfoldTransition = !mIsFolded
                && mService.mContext.getResources().getBoolean(config_unfoldTransitionEnabled)
                && ValueAnimator.areAnimatorsEnabled();
        boolean shouldRequestUnfoldTransition = mContext.getResources()
                .getBoolean(config_unfoldTransitionEnabled) && ValueAnimator.areAnimatorsEnabled();

        if (!shouldRequestUnfoldTransition) {
            return;
@@ -91,6 +123,8 @@ public class PhysicalDisplaySwitchTransitionLauncher {
            mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
            mTransition = t;
        }

        mShouldRequestTransitionOnDisplaySwitch = false;
    }

    /**
@@ -118,7 +152,7 @@ public class PhysicalDisplaySwitchTransitionLauncher {
        if (mTransition == null) return;

        if (transaction != null) {
            mService.mAtmService.mWindowOrganizerController.applyTransaction(transaction);
            mAtmService.mWindowOrganizerController.applyTransaction(transaction);
        }

        markTransitionAsReady();
+273 −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.server.wm;

import static android.view.WindowManager.TRANSIT_CHANGE;

import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.window.TransitionRequestInfo.DisplayChange;

import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
import static com.android.server.wm.DeviceStateController.DeviceState.REAR;

import androidx.test.filters.SmallTest;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

/**
 * Tests for the {@link WindowToken} class.
 *
 * Build/Install/Run:
 * atest WmTests:PhysicalDisplaySwitchTransitionLauncherTest
 */
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase {

    @Mock
    DisplayContent mDisplayContent;
    @Mock
    Context mContext;
    @Mock
    Resources mResources;
    @Mock
    ActivityTaskManagerService mActivityTaskManagerService;
    @Mock
    TransitionController mTransitionController;

    private PhysicalDisplaySwitchTransitionLauncher mTarget;
    private float mOriginalAnimationScale;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        when(mContext.getResources()).thenReturn(mResources);
        mTarget = new PhysicalDisplaySwitchTransitionLauncher(mDisplayContent,
                mActivityTaskManagerService, mContext, mTransitionController);
        mOriginalAnimationScale = ValueAnimator.getDurationScale();
    }

    @After
    public void after() {
        ValueAnimator.setDurationScale(mOriginalAnimationScale);
    }

    @Test
    public void testDisplaySwitchAfterUnfoldToOpen_animationsEnabled_requestsTransition() {
        givenAllAnimationsEnabled();
        mTarget.foldStateChanged(FOLDED);

        mTarget.foldStateChanged(OPEN);
        mTarget.requestDisplaySwitchTransitionIfNeeded(
                /* displayId= */ 123,
                /* oldDisplayWidth= */ 100,
                /* oldDisplayHeight= */ 150,
                /* newDisplayWidth= */ 200,
                /* newDisplayHeight= */ 250
        );

        ArgumentCaptor<DisplayChange> displayChangeArgumentCaptor =
                ArgumentCaptor.forClass(DisplayChange.class);
        verify(mTransitionController).requestTransitionIfNeeded(eq(TRANSIT_CHANGE), /* flags= */
                eq(0), eq(mDisplayContent), eq(mDisplayContent), /* remoteTransition= */ isNull(),
                displayChangeArgumentCaptor.capture());
        assertThat(displayChangeArgumentCaptor.getValue().getDisplayId()).isEqualTo(123);
        assertThat(displayChangeArgumentCaptor.getValue().getStartAbsBounds()).isEqualTo(
                new Rect(0, 0, 100, 150));
        assertThat(displayChangeArgumentCaptor.getValue().getEndAbsBounds()).isEqualTo(
                new Rect(0, 0, 200, 250));
    }

    @Test
    public void testDisplaySwitchAfterFolding_animationEnabled_doesNotRequestTransition() {
        givenAllAnimationsEnabled();
        mTarget.foldStateChanged(OPEN);

        mTarget.foldStateChanged(FOLDED);
        requestDisplaySwitch();

        assertTransitionNotRequested();
    }

    @Test
    public void testDisplaySwitchAfterUnfoldingToHalf_animationEnabled_requestsTransition() {
        givenAllAnimationsEnabled();
        mTarget.foldStateChanged(FOLDED);

        mTarget.foldStateChanged(HALF_FOLDED);
        requestDisplaySwitch();

        assertTransitionRequested();
    }

    @Test
    public void testDisplaySwitchSecondTimeAfterUnfolding_animationEnabled_noTransition() {
        givenAllAnimationsEnabled();
        mTarget.foldStateChanged(FOLDED);
        mTarget.foldStateChanged(OPEN);
        requestDisplaySwitch();
        clearInvocations(mTransitionController);

        requestDisplaySwitch();

        assertTransitionNotRequested();
    }


    @Test
    public void testDisplaySwitchAfterGoingToRearAndBack_animationEnabled_noTransition() {
        givenAllAnimationsEnabled();
        mTarget.foldStateChanged(OPEN);

        mTarget.foldStateChanged(REAR);
        mTarget.foldStateChanged(OPEN);
        requestDisplaySwitch();

        assertTransitionNotRequested();
    }

    @Test
    public void testDisplaySwitchAfterUnfoldingAndFolding_animationEnabled_noTransition() {
        givenAllAnimationsEnabled();
        mTarget.foldStateChanged(FOLDED);
        mTarget.foldStateChanged(OPEN);
        // No request display switch event (simulate very fast fold after unfold, even before
        // the displays switched)
        mTarget.foldStateChanged(FOLDED);

        requestDisplaySwitch();

        assertTransitionNotRequested();
    }

    @Test
    public void testDisplaySwitch_whenShellTransitionsNotEnabled_noTransition() {
        givenAllAnimationsEnabled();
        givenShellTransitionsEnabled(false);
        mTarget.foldStateChanged(FOLDED);

        mTarget.foldStateChanged(OPEN);
        requestDisplaySwitch();

        assertTransitionNotRequested();
    }

    @Test
    public void testDisplaySwitch_whenAnimationsDisabled_noTransition() {
        givenAllAnimationsEnabled();
        givenAnimationsEnabled(false);
        mTarget.foldStateChanged(FOLDED);

        mTarget.foldStateChanged(OPEN);
        requestDisplaySwitch();

        assertTransitionNotRequested();
    }

    @Test
    public void testDisplaySwitch_whenUnfoldAnimationDisabled_noTransition() {
        givenAllAnimationsEnabled();
        givenUnfoldTransitionEnabled(false);
        mTarget.foldStateChanged(FOLDED);

        mTarget.foldStateChanged(OPEN);
        requestDisplaySwitch();

        assertTransitionNotRequested();
    }

    @Test
    public void testDisplaySwitch_whenNoContentInDisplayContent_noTransition() {
        givenAllAnimationsEnabled();
        givenDisplayContentHasContent(false);
        mTarget.foldStateChanged(FOLDED);

        mTarget.foldStateChanged(OPEN);
        requestDisplaySwitch();

        assertTransitionNotRequested();
    }

    private void assertTransitionRequested() {
        verify(mTransitionController).requestTransitionIfNeeded(anyInt(), anyInt(), any(), any(),
                any(), any());
    }

    private void assertTransitionNotRequested() {
        verify(mTransitionController, never()).requestTransitionIfNeeded(anyInt(), anyInt(), any(),
                any(), any(), any());
    }

    private void requestDisplaySwitch() {
        mTarget.requestDisplaySwitchTransitionIfNeeded(
                /* displayId= */ 123,
                /* oldDisplayWidth= */ 100,
                /* oldDisplayHeight= */ 150,
                /* newDisplayWidth= */ 200,
                /* newDisplayHeight= */ 250
        );
    }

    private void givenAllAnimationsEnabled() {
        givenAnimationsEnabled(true);
        givenUnfoldTransitionEnabled(true);
        givenShellTransitionsEnabled(true);
        givenDisplayContentHasContent(true);
    }

    private void givenUnfoldTransitionEnabled(boolean enabled) {
        when(mResources.getBoolean(config_unfoldTransitionEnabled)).thenReturn(enabled);
    }

    private void givenAnimationsEnabled(boolean enabled) {
        ValueAnimator.setDurationScale(enabled ? 1.0f : 0.0f);
    }

    private void givenShellTransitionsEnabled(boolean enabled) {
        when(mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
    }

    private void givenDisplayContentHasContent(boolean hasContent) {
        when(mDisplayContent.getLastHasContent()).thenReturn(hasContent);
    }
}