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

Commit 8a0ac39a authored by Robin Lee's avatar Robin Lee
Browse files

Skip animation background if there is a wallpaper

This improves the animation for the case where the wallpaper target
is CHANGE-ing (remaining visible) as part of an OPEN/CLOSE transition.

In this case the wallpaper will be a better animation background than
a solid color, unless any of the participants specifically asked for
a solid color.

An example of a case this improves is BlurTests. Every case launches a
translucent dialog on top of an opaque activity and then finishes
the opaque activity first. This generates 1x CLOSE without a wallpaper
flag and 1x CHANGE with a wallpaper flag, which wrongly gets
interpreted as not showing wallpaper.

Flag: EXEMPT bugfix
Test: atest ShellTransitionTests
Test: atest DefaultTransitionHandlerTest
Test: atest BlurTests
Bug: 227736816
Change-Id: I7296118f59aa592bc48df9fc8c037ad5dd5e6ed2
parent 73e875a6
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -75,10 +75,11 @@ import java.util.List;
/** @hide */
public class TransitionAnimation {
    public static final int WALLPAPER_TRANSITION_NONE = 0;
    public static final int WALLPAPER_TRANSITION_OPEN = 1;
    public static final int WALLPAPER_TRANSITION_CLOSE = 2;
    public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 3;
    public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 4;
    public static final int WALLPAPER_TRANSITION_CHANGE = 1;
    public static final int WALLPAPER_TRANSITION_OPEN = 2;
    public static final int WALLPAPER_TRANSITION_CLOSE = 3;
    public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 4;
    public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 5;

    // These are the possible states for the enter/exit activities during a thumbnail transition
    private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
+7 −1
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;

import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
@@ -944,12 +945,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
    }

    private static int getWallpaperTransitType(TransitionInfo info) {
        boolean hasWallpaper = false;
        boolean hasOpenWallpaper = false;
        boolean hasCloseWallpaper = false;

        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            final TransitionInfo.Change change = info.getChanges().get(i);
            if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) {
            if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0
                    || (change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
                hasWallpaper = true;
                if (TransitionUtil.isOpeningType(change.getMode())) {
                    hasOpenWallpaper = true;
                } else if (TransitionUtil.isClosingType(change.getMode())) {
@@ -965,6 +969,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
            return WALLPAPER_TRANSITION_OPEN;
        } else if (hasCloseWallpaper) {
            return WALLPAPER_TRANSITION_CLOSE;
        } else if (hasWallpaper) {
            return WALLPAPER_TRANSITION_CHANGE;
        } else {
            return WALLPAPER_TRANSITION_NONE;
        }
+72 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.wm.shell.transition;

import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;

import static org.mockito.Mockito.mock;

import android.app.ActivityManager.RunningTaskInfo;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;

public class ChangeBuilder {
    final TransitionInfo.Change mChange;

    ChangeBuilder(@WindowManager.TransitionType int mode) {
        mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true));
        mChange.setMode(mode);
    }

    ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) {
        mChange.setFlags(flags);
        return this;
    }

    ChangeBuilder setTask(RunningTaskInfo taskInfo) {
        mChange.setTaskInfo(taskInfo);
        return this;
    }

    ChangeBuilder setRotate(int anim) {
        return setRotate(Surface.ROTATION_90, anim);
    }

    ChangeBuilder setRotate() {
        return setRotate(ROTATION_ANIMATION_UNSPECIFIED);
    }

    ChangeBuilder setRotate(@Surface.Rotation int target, int anim) {
        mChange.setRotation(Surface.ROTATION_0, target);
        mChange.setRotationAnimation(anim);
        return this;
    }

    TransitionInfo.Change build() {
        return mChange;
    }

    private static SurfaceControl createMockSurface(boolean valid) {
        SurfaceControl sc = mock(SurfaceControl.class);
        doReturn(valid).when(sc).isValid();
        return sc;
    }
}
+207 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.wm.shell.transition;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_SYNC;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;

import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sysui.ShellInit;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Tests for the default animation handler that is used if no other special-purpose handler picks
 * up an animation request.
 *
 * Build/Install/Run:
 * atest WMShellUnitTests:DefaultTransitionHandlerTest
 */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DefaultTransitionHandlerTest extends ShellTestCase {

    private final Context mContext =
            InstrumentationRegistry.getInstrumentation().getTargetContext();

    private final DisplayController mDisplayController = mock(DisplayController.class);
    private final TransactionPool mTransactionPool = new MockTransactionPool();
    private final TestShellExecutor mMainExecutor = new TestShellExecutor();
    private final TestShellExecutor mAnimExecutor = new TestShellExecutor();
    private final Handler mMainHandler = new Handler(Looper.getMainLooper());

    private ShellInit mShellInit;
    private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
    private DefaultTransitionHandler mTransitionHandler;

    @Before
    public void setUp() {
        mShellInit = new ShellInit(mMainExecutor);
        mRootTaskDisplayAreaOrganizer = new RootTaskDisplayAreaOrganizer(
                mMainExecutor,
                mContext,
                mShellInit);
        mTransitionHandler = new DefaultTransitionHandler(
                mContext, mShellInit, mDisplayController,
                mTransactionPool, mMainExecutor, mMainHandler, mAnimExecutor,
                mRootTaskDisplayAreaOrganizer);
        mShellInit.init();
    }

    @After
    public void tearDown() {
        flushHandlers();
    }

    private void flushHandlers() {
        mMainHandler.runWithScissors(() -> {
            mAnimExecutor.flushAll();
            mMainExecutor.flushAll();
        }, 1000L);
    }

    @Test
    public void testAnimationBackgroundCreatedForTaskTransition() {
        final TransitionInfo.Change openTask = new ChangeBuilder(TRANSIT_OPEN)
                .setTask(createTaskInfo(1))
                .build();
        final TransitionInfo.Change closeTask = new ChangeBuilder(TRANSIT_TO_BACK)
                .setTask(createTaskInfo(2))
                .build();

        final IBinder token = new Binder();
        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                .addChange(openTask)
                .addChange(closeTask)
                .build();
        final SurfaceControl.Transaction startT = MockTransactionPool.create();
        final SurfaceControl.Transaction finishT = MockTransactionPool.create();

        mTransitionHandler.startAnimation(token, info, startT, finishT,
                mock(Transitions.TransitionFinishCallback.class));

        mergeSync(mTransitionHandler, token);
        flushHandlers();

        verify(startT).setColor(any(), any());
    }

    @Test
    public void testNoAnimationBackgroundForTranslucentTasks() {
        final TransitionInfo.Change openTask = new ChangeBuilder(TRANSIT_OPEN)
                .setTask(createTaskInfo(1))
                .setFlags(FLAG_TRANSLUCENT)
                .build();
        final TransitionInfo.Change closeTask = new ChangeBuilder(TRANSIT_TO_BACK)
                .setTask(createTaskInfo(2))
                .setFlags(FLAG_TRANSLUCENT)
                .build();

        final IBinder token = new Binder();
        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                .addChange(openTask)
                .addChange(closeTask)
                .build();
        final SurfaceControl.Transaction startT = MockTransactionPool.create();
        final SurfaceControl.Transaction finishT = MockTransactionPool.create();

        mTransitionHandler.startAnimation(token, info, startT, finishT,
                mock(Transitions.TransitionFinishCallback.class));

        mergeSync(mTransitionHandler, token);
        flushHandlers();

        verify(startT, never()).setColor(any(), any());
    }

    @Test
    public void testNoAnimationBackgroundForWallpapers() {
        final TransitionInfo.Change openWallpaper = new ChangeBuilder(TRANSIT_OPEN)
                .setFlags(TransitionInfo.FLAG_IS_WALLPAPER)
                .build();
        final TransitionInfo.Change closeWallpaper = new ChangeBuilder(TRANSIT_TO_BACK)
                .setFlags(TransitionInfo.FLAG_IS_WALLPAPER)
                .build();

        final IBinder token = new Binder();
        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                .addChange(openWallpaper)
                .addChange(closeWallpaper)
                .build();
        final SurfaceControl.Transaction startT = MockTransactionPool.create();
        final SurfaceControl.Transaction finishT = MockTransactionPool.create();

        mTransitionHandler.startAnimation(token, info, startT, finishT,
                mock(Transitions.TransitionFinishCallback.class));

        mergeSync(mTransitionHandler, token);
        flushHandlers();

        verify(startT, never()).setColor(any(), any());
    }

    private static void mergeSync(Transitions.TransitionHandler handler, IBinder token) {
        handler.mergeAnimation(
                new Binder(),
                new TransitionInfoBuilder(TRANSIT_SLEEP, FLAG_SYNC).build(),
                MockTransactionPool.create(),
                token,
                mock(Transitions.TransitionFinishCallback.class));
    }

    private static RunningTaskInfo createTaskInfo(int taskId) {
        RunningTaskInfo taskInfo = new RunningTaskInfo();
        taskInfo.taskId = taskId;
        taskInfo.topActivityType = ACTIVITY_TYPE_STANDARD;
        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
        taskInfo.configuration.windowConfiguration.setActivityType(taskInfo.topActivityType);
        taskInfo.token = mock(WindowContainerToken.class);
        return taskInfo;
    }
}
+41 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.wm.shell.transition;

import static org.mockito.Mockito.RETURNS_SELF;
import static org.mockito.Mockito.mock;

import android.view.SurfaceControl;

import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.util.StubTransaction;

public class MockTransactionPool extends TransactionPool {

    public static SurfaceControl.Transaction create() {
        return mock(StubTransaction.class, RETURNS_SELF);
    }

    @Override
    public SurfaceControl.Transaction acquire() {
        return create();
    }

    @Override
    public void release(SurfaceControl.Transaction t) {
    }
}
Loading