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

Commit 038ca7f4 authored by Bill Lin's avatar Bill Lin
Browse files

Reset WindowContainerTransaction when rotation changed callback

* Fix the problem that wct do not update/reset bounds after rotation
* Fix ConcurrentModificationException bug
* Refactor OneHandedDisplayAreaOrganizerTest
* Implement OHM dump in WMShell
* Consolidate the test cases
* Replace HashMap to ArrayMap for memory efficiency

Test: manual trigger OHM, rotate device, observe leash is reset
Test: atest SystemUITests
Test: atest WMShellUnitTests
Bug: 162470563
Bug: 170284095
Fixes: 160848002
Change-Id: I1c653174e28c72253cc7eac1d294a5c598bb7921
parent db2bcf83
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -77,7 +77,7 @@ public class OneHandedController implements OneHanded {
    private final DisplayChangeController.OnDisplayChangingListener mRotationController =
            (display, fromRotation, toRotation, wct) -> {
                if (mDisplayAreaOrganizer != null) {
                    mDisplayAreaOrganizer.onRotateDisplay(fromRotation, toRotation);
                    mDisplayAreaOrganizer.onRotateDisplay(fromRotation, toRotation, wct);
                }
            };

+40 −36
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
import android.view.SurfaceControl;
import android.window.DisplayAreaInfo;
@@ -42,7 +43,6 @@ import com.android.wm.shell.common.DisplayController;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

@@ -76,7 +76,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
    private int mEnterExitAnimationDurationMs;

    @VisibleForTesting
    HashMap<DisplayAreaInfo, SurfaceControl> mDisplayAreaMap = new HashMap();
    ArrayMap<DisplayAreaInfo, SurfaceControl> mDisplayAreaMap = new ArrayMap();
    private DisplayController mDisplayController;
    private OneHandedAnimationController mAnimationController;
    private OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
@@ -117,12 +117,13 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
    private Handler.Callback mUpdateCallback = (msg) -> {
        SomeArgs args = (SomeArgs) msg.obj;
        final Rect currentBounds = args.arg1 != null ? (Rect) args.arg1 : mDefaultDisplayBounds;
        final WindowContainerTransaction wctFromRotate = (WindowContainerTransaction) args.arg2;
        final int yOffset = args.argi2;
        final int direction = args.argi3;

        switch (msg.what) {
            case MSG_RESET_IMMEDIATE:
                resetWindowsOffset();
                resetWindowsOffset(wctFromRotate);
                mDefaultDisplayBounds.set(currentBounds);
                mLastVisualDisplayBounds.set(currentBounds);
                finishOffset(0, TRANSITION_DIRECTION_EXIT);
@@ -165,47 +166,44 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
            @NonNull SurfaceControl leash) {
        Objects.requireNonNull(displayAreaInfo, "displayAreaInfo must not be null");
        Objects.requireNonNull(leash, "leash must not be null");

        if (displayAreaInfo.featureId != FEATURE_ONE_HANDED) {
            Log.w(TAG, "Bypass onDisplayAreaAppeared()! displayAreaInfo=" + displayAreaInfo);
            return;
        }
        synchronized (this) {
            if (mDisplayAreaMap.get(displayAreaInfo) == null) {
                // mDefaultDisplayBounds may out of date after removeDisplayChangingController()
                mDefaultDisplayBounds.set(getDisplayBounds());
                mDisplayAreaMap.put(displayAreaInfo, leash);
            }
        }
    }

    @Override
    public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
        Objects.requireNonNull(displayAreaInfo,
                "Requires valid displayArea, and displayArea must not be null");

        synchronized (this) {
            if (!mDisplayAreaMap.containsKey(displayAreaInfo)) {
                Log.w(TAG, "Unrecognized token: " + displayAreaInfo.token);
                return;
            }
            mDisplayAreaMap.remove(displayAreaInfo);
        }
    }

    @Override
    public void unregisterOrganizer() {
        super.unregisterOrganizer();
        resetWindowsOffset();

        // Ensure all cached instance are cleared after resetWindowsOffset
        mUpdateHandler.post(() -> {
            if (mDisplayAreaMap != null && !mDisplayAreaMap.isEmpty()) {
                mDisplayAreaMap.clear();
            }
        });
        mUpdateHandler.post(() -> resetWindowsOffset(null));
    }

    /**
     * Handler for display rotation changes by below policy which
     * handles 90 degree display rotation changes {@link Surface.Rotation}
     * handles 90 degree display rotation changes {@link Surface.Rotation}.
     *
     * @param fromRotation starting rotation of the display.
     * @param toRotation target rotation of the display (after rotating).
     * @param wct A task transaction {@link WindowContainerTransaction} from
     *        {@link DisplayChangeController} to populate.
     */
    public void onRotateDisplay(int fromRotation, int toRotation) {
    public void onRotateDisplay(int fromRotation, int toRotation, WindowContainerTransaction wct) {
        // Stop one handed without animation and reset cropped size immediately
        final Rect newBounds = new Rect(mDefaultDisplayBounds);
        final boolean isOrientationDiff = Math.abs(fromRotation - toRotation) % 2 == 1;
@@ -214,6 +212,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
            newBounds.set(newBounds.left, newBounds.top, newBounds.bottom, newBounds.right);
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = newBounds;
            args.arg2 = wct;
            args.argi1 = 0 /* xOffset */;
            args.argi2 = 0 /* yOffset */;
            args.argi3 = TRANSITION_DIRECTION_EXIT;
@@ -239,18 +238,19 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
            throw new RuntimeException("Callers should call scheduleOffset() instead of this "
                    + "directly");
        }
        synchronized (this) {
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            mDisplayAreaMap.forEach(
                    (key, leash) -> {
                    animateWindows(leash, fromBounds, toBounds, direction,
                            durationMs);
                        animateWindows(leash, fromBounds, toBounds, direction, durationMs);
                        wct.setBounds(key.token, toBounds);
                    });
            applyTransaction(wct);
        }
    }

    private void resetWindowsOffset() {
        mUpdateHandler.post(() -> {
    private void resetWindowsOffset(WindowContainerTransaction wct) {
        synchronized (this) {
            final SurfaceControl.Transaction tx =
                    mSurfaceControlTransactionFactory.getTransaction();
            mDisplayAreaMap.forEach(
@@ -262,9 +262,13 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
                        }
                        tx.setPosition(leash, 0, 0)
                                .setWindowCrop(leash, -1/* reset */, -1/* reset */);
                        // DisplayRotationController will applyTransaction() after finish rotating
                        if (wct != null) {
                            wct.setBounds(key.token, null/* reset */);
                        }
                    });
            tx.apply();
        });
        }
    }

    private void animateWindows(SurfaceControl leash, Rect fromBounds, Rect toBounds,
+112 −61
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import android.view.SurfaceControl;
import android.window.DisplayAreaInfo;
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

import androidx.test.filters.SmallTest;

@@ -49,7 +50,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -60,11 +60,13 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {

    DisplayAreaInfo mDisplayAreaInfo;
    Display mDisplay;
    Handler mUpdateHandler;
    OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
    OneHandedTutorialHandler mTutorialHandler;
    OneHandedAnimationController.OneHandedTransitionAnimator mFakeAnimator;
    WindowContainerToken mToken;
    SurfaceControl mLeash;
    TestableLooper mTestableLooper;
    @Mock
    IWindowContainerToken mMockRealToken;
    @Mock
@@ -77,12 +79,13 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
    DisplayController mMockDisplayController;
    @Mock
    SurfaceControl mMockLeash;
    @Spy
    Handler mUpdateHandler;
    @Mock
    WindowContainerTransaction mMockWindowContainerTransaction;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mTestableLooper = TestableLooper.get(this);
        mToken = new WindowContainerToken(mMockRealToken);
        mLeash = new SurfaceControl();
        mDisplay = mContext.getDisplay();
@@ -110,14 +113,10 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
        mUpdateHandler = mDisplayAreaOrganizer.getUpdateHandler();
    }

    @Test
    public void testGetDisplayAreaUpdateHandler_isNotNull() {
        assertThat(mUpdateHandler).isNotNull();
    }

    @Test
    public void testOnDisplayAreaAppeared() {
        mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
        mTestableLooper.processAllMessages();

        verify(mMockAnimationController, never()).getAnimator(any(), any(), any());
    }
@@ -125,17 +124,10 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
    @Test
    public void testOnDisplayAreaVanished() {
        mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onDisplayAreaVanished(mDisplayAreaInfo);
    }

    @Test
    public void testOnDisplayAreaInfoChanged_updateDisplayAreaInfo() {
        final DisplayAreaInfo newDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY,
                FEATURE_ONE_HANDED);
        mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
        mDisplayAreaOrganizer.onDisplayAreaInfoChanged(newDisplayAreaInfo);

        assertThat(mDisplayAreaOrganizer.mDisplayAreaMap.containsKey(mDisplayAreaInfo)).isTrue();
        assertThat(mDisplayAreaOrganizer.mDisplayAreaMap).isEmpty();
    }

    @Ignore("b/160848002")
@@ -143,142 +135,201 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
    public void testScheduleOffset() {
        final int xOffSet = 0;
        final int yOffSet = 100;

        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
        mDisplayAreaOrganizer.scheduleOffset(xOffSet, yOffSet);
        mTestableLooper.processAllMessages();

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_OFFSET_ANIMATE)).isEqualTo(true);
    }

    @Ignore("b/160848002")
    @Test
    public void testRotation_portraitToLandscape() {
    public void testRotation_portrait_0_to_landscape_90() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 0 -> 90
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_90);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_90,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
    }

    @Test
    public void testRotation_portrait_0_to_seascape_270() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 0 -> 270
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_270);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_270,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);

    }

    @Test
    public void testRotation_portrait_180_to_landscape_90() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 180 -> 90
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_90);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_90,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
    }

    @Test
    public void testRotation_portrait_180_to_seascape_270() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 180 -> 270
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_270);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_270,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
    }

    @Ignore("b/160848002")
    @Test
    public void testRotation_landscapeToPortrait() {
    public void testRotation_landscape_90_to_portrait_0() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 90 -> 0
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_0);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_0,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
    }

    @Test
    public void testRotation_landscape_90_to_portrait_180() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 90 -> 180
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_180);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_180,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
    }

    @Test
    public void testRotation_Seascape_270_to_portrait_0() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 270 -> 0
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_0);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_0,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
    }

    @Test
    public void testRotation_seascape_90_to_portrait_180() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 270 -> 180
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_180);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_180,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
    }

    @Ignore("b/160848002")
    @Test
    public void testRotation_portraitToPortrait() {
    public void testRotation_portrait_0_to_portrait_0() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 0 -> 0
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_0);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_0,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
    }

    @Test
    public void testRotation_portrait_0_to_portrait_180() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 0 -> 180
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_180);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_180,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
    }

    @Test
    public void testRotation_portrait_180_to_portrait_180() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 180 -> 180
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_180);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_180,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
    }

        // Rotate 180 -> 180
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_0);
    @Test
    public void testRotation_portrait_180_to_portrait_0() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 180 -> 0
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_0,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
    }

    @Ignore("b/160848002")
    @Test
    public void testRotation_landscapeToLandscape() {
    public void testRotation_landscape_90_to_landscape_90() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 90 -> 90
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_90);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_90,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
    }

    @Test
    public void testRotation_landscape_90_to_seascape_270() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 90 -> 270
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_270);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_270,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
    }

    @Test
    public void testRotation_seascape_270_to_seascape_270() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 270 -> 270
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_270);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_270,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
    }

    @Test
    public void testRotation_seascape_90_to_landscape_90() {
        when(mMockLeash.isValid()).thenReturn(false);
        // Rotate 270 -> 90
        TestableLooper.get(this).processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_90);
        mTestableLooper.processAllMessages();
        mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_90,
                mMockWindowContainerTransaction);

        assertThat(mUpdateHandler.hasMessages(
                OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
+1 −1
Original line number Diff line number Diff line
@@ -423,8 +423,8 @@ public final class WMShell extends SystemUI
        if (handleLoggingCommand(args, pw)) {
            return;
        }

        // Dump WMShell stuff here if no commands were handled
        mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
    }

    @Override