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

Commit f5592d14 authored by Jerry Chang's avatar Jerry Chang
Browse files

Handle app pair divider bar rotation

Add logic to rotate app pair divider bar and add more tests.

Bug: 172704238
Test: AppPairTests, AppPairsPoolTests, AppPairsControllerTests
Test: AppPairLayoutTests
Test: manual check the behavior of divider bar.
Change-Id: Icb51cfaea04b1a9fd04cbb2bc868897d8428d483
parent 10963d5b
Loading
Loading
Loading
Loading
+22 −20
Original line number Diff line number Diff line
@@ -136,13 +136,6 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
        mAppPairLayout = null;
    }

    void setVisible(boolean visible) {
        if (mAppPairLayout == null) {
            return;
        }
        mAppPairLayout.setDividerVisibility(visible);
    }

    @Override
    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
        if (mRootTaskInfo == null || taskInfo.taskId == mRootTaskInfo.taskId) {
@@ -160,32 +153,41 @@ class AppPair implements ShellTaskOrganizer.TaskListener {

        if (mTaskLeash1 == null || mTaskLeash2 == null) return;

        setVisible(true);
        mAppPairLayout.init();
        final SurfaceControl dividerLeash = mAppPairLayout.getDividerLeash();
        final Rect dividerBounds = mAppPairLayout.getDividerBounds();

        // TODO: Is there more we need to do here?
        mSyncQueue.runInSync(t -> t
                .setPosition(mTaskLeash1, mTaskInfo1.positionInParent.x,
        mSyncQueue.runInSync(t -> {
            t.setPosition(mTaskLeash1, mTaskInfo1.positionInParent.x,
                    mTaskInfo1.positionInParent.y)
                    .setPosition(mTaskLeash2, mTaskInfo2.positionInParent.x,
                            mTaskInfo2.positionInParent.y)
                    .setLayer(dividerLeash, Integer.MAX_VALUE)
                    .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
                    .show(mRootTaskLeash)
                .show(dividerLeash)
                    .show(mTaskLeash1)
                .show(mTaskLeash2));
                    .show(mTaskLeash2);
        });
    }

    @Override
    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
        if (taskInfo.taskId == getRootTaskId()) {
            if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
                mSyncQueue.runInSync(t -> {
                    if (taskInfo.isVisible) {
                        t.show(mRootTaskLeash);
                    } else {
                        t.hide(mRootTaskLeash);
                    }
                });
            }
            mRootTaskInfo = taskInfo;

            if (mAppPairLayout != null
                    && mAppPairLayout.updateConfiguration(mRootTaskInfo.configuration)) {
                // Update bounds when there is root bounds or orientation changed.
                // Update bounds when root bounds or its orientation changed.
                final WindowContainerTransaction wct = new WindowContainerTransaction();
                final SurfaceControl dividerLeash = mAppPairLayout.getDividerLeash();
                final Rect dividerBounds = mAppPairLayout.getDividerBounds();
+25 −38
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.wm.shell.apppairs;

import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
@@ -45,21 +44,18 @@ import com.android.wm.shell.R;
/**
 * Records and handles layout of a pair of apps.
 */
// TODO(172704238): add tests
final class AppPairLayout {
    private static final String DIVIDER_WINDOW_TITLE = "AppPairDivider";
    private final Context mContext;
    private final AppPairWindowManager mAppPairWindowManager;
    private final SurfaceControlViewHost mViewHost;

    private final Display mDisplay;
    private final int mDividerWindowWidth;
    private final int mDividerWindowInsets;
    private final AppPairWindowManager mAppPairWindowManager;

    private boolean mIsLandscape;
    private Context mContext;
    private Rect mRootBounds;
    private DIVIDE_POLICY mDividePolicy;

    private DividerView mDividerView;
    private SurfaceControlViewHost mViewHost;
    private SurfaceControl mDividerLeash;

    AppPairLayout(
@@ -68,7 +64,7 @@ final class AppPairLayout {
            Configuration configuration,
            SurfaceControl rootLeash) {
        mContext = context.createConfigurationContext(configuration);
        mIsLandscape = isLandscape(configuration);
        mDisplay = display;
        mRootBounds = configuration.windowConfiguration.getBounds();
        mDividerWindowWidth = mContext.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.docked_stack_divider_thickness);
@@ -76,26 +72,22 @@ final class AppPairLayout {
                com.android.internal.R.dimen.docked_stack_divider_insets);

        mAppPairWindowManager = new AppPairWindowManager(configuration, rootLeash);
        mViewHost = new SurfaceControlViewHost(mContext, display, mAppPairWindowManager);
        mDividePolicy = DIVIDE_POLICY.MIDDLE;
        mDividePolicy.update(mIsLandscape, mRootBounds, mDividerWindowWidth, mDividerWindowInsets);
        mDividePolicy.update(mRootBounds, mDividerWindowWidth, mDividerWindowInsets);
    }

    boolean updateConfiguration(Configuration configuration) {
        mAppPairWindowManager.setConfiguration(configuration);
        final Rect rootBounds = configuration.windowConfiguration.getBounds();
        final boolean isLandscape = isLandscape(configuration);
        if (mIsLandscape == isLandscape && isIdenticalBounds(mRootBounds, rootBounds)) {
        if (isIdenticalBounds(mRootBounds, rootBounds)) {
            return false;
        }

        mIsLandscape = isLandscape;
        mContext = mContext.createConfigurationContext(configuration);
        mRootBounds = rootBounds;
        mDividePolicy.update(mIsLandscape, mRootBounds, mDividerWindowWidth, mDividerWindowInsets);
        mViewHost.relayout(
                mDividePolicy.mDividerBounds.width(),
                mDividePolicy.mDividerBounds.height());
        // TODO(172704238): handle divider bar rotation.
        mDividePolicy.update(mRootBounds, mDividerWindowWidth, mDividerWindowInsets);
        release();
        init();
        return true;
    }

@@ -116,22 +108,19 @@ final class AppPairLayout {
    }

    void release() {
        if (mViewHost == null) return;
        if (mViewHost == null) {
            return;
        }
        mViewHost.release();
        mDividerLeash = null;
        mViewHost = null;
    }

    void setDividerVisibility(boolean visible) {
        if (mDividerView == null) {
            initDivider();
        }
        if (visible) {
            mDividerView.show();
        } else {
            mDividerView.hide();
        }
    void init() {
        if (mViewHost == null) {
            mViewHost = new SurfaceControlViewHost(mContext, mDisplay, mAppPairWindowManager);
        }

    private void initDivider() {
        final DividerView dividerView = (DividerView) LayoutInflater.from(mContext)
                .inflate(R.layout.split_divider, null);

@@ -147,14 +136,9 @@ final class AppPairLayout {
        lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;

        mViewHost.setView(dividerView, lp);
        mDividerView = dividerView;
        mDividerLeash = mAppPairWindowManager.getSurfaceControl(mViewHost.getWindowToken());
    }

    private static boolean isLandscape(Configuration configuration) {
        return configuration.orientation == ORIENTATION_LANDSCAPE;
    }

    private static boolean isIdenticalBounds(Rect bounds1, Rect bounds2) {
        return bounds1.left == bounds2.left && bounds1.top == bounds2.top
                && bounds1.right == bounds2.right && bounds1.bottom == bounds2.bottom;
@@ -167,8 +151,7 @@ final class AppPairLayout {
    enum DIVIDE_POLICY {
        MIDDLE;

        void update(boolean isLandscape, Rect rootBounds, int dividerWindowWidth,
                int dividerWindowInsets) {
        void update(Rect rootBounds, int dividerWindowWidth, int dividerWindowInsets) {
            final int dividerOffset = dividerWindowWidth / 2;
            final int boundsOffset = dividerOffset - dividerWindowInsets;

@@ -179,7 +162,7 @@ final class AppPairLayout {
            switch (this) {
                case MIDDLE:
                default:
                    if (isLandscape) {
                    if (isLandscape(rootBounds)) {
                        mDividerBounds.left = rootBounds.width() / 2 - dividerOffset;
                        mDividerBounds.right = rootBounds.width() / 2 + dividerOffset;
                        mBounds1.left = rootBounds.width() / 2 + boundsOffset;
@@ -193,6 +176,10 @@ final class AppPairLayout {
            }
        }

        private boolean isLandscape(Rect bounds) {
            return bounds.width() > bounds.height();
        }

        Rect mDividerBounds;
        Rect mBounds1;
        Rect mBounds2;
+0 −2
Original line number Diff line number Diff line
@@ -36,6 +36,4 @@ public interface AppPairs {
    void dump(@NonNull PrintWriter pw, String prefix);
    /** Called when the shell organizer has been registered. */
    void onOrganizerRegistered();
    /** Called when the visibility of the keyguard changes. */
    void onKeyguardVisibilityChanged(boolean showing);
}
+2 −29
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.wm.shell.apppairs;

import static android.app.ActivityTaskManager.INVALID_TASK_ID;

import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;

import android.app.ActivityManager;
@@ -30,15 +28,13 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;

import java.io.PrintWriter;

/**
 * Class manages app-pairs multitasking mode and implements the main interface {@link AppPairs}.
 */
public class AppPairsController implements AppPairs, TaskStackListenerCallback {
public class AppPairsController implements AppPairs {
    private static final String TAG = AppPairsController.class.getSimpleName();

    private final ShellTaskOrganizer mTaskOrganizer;
@@ -48,14 +44,12 @@ public class AppPairsController implements AppPairs, TaskStackListenerCallback {
    // Active app-pairs mapped by root task id key.
    private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
    private final DisplayController mDisplayController;
    private int mForegroundTaskId = INVALID_TASK_ID;

    public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
                DisplayController displayController, TaskStackListenerImpl taskStackListener) {
                DisplayController displayController) {
        mTaskOrganizer = organizer;
        mSyncQueue = syncQueue;
        mDisplayController = displayController;
        taskStackListener.addListener(this);
    }

    @Override
@@ -70,27 +64,6 @@ public class AppPairsController implements AppPairs, TaskStackListenerCallback {
        mPairsPool = pool;
    }

    @Override
    public void onTaskMovedToFront(int taskId) {
        mForegroundTaskId = INVALID_TASK_ID;
        for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
            final AppPair candidate = mActiveAppPairs.valueAt(i);
            final boolean containForegroundTask = candidate.contains(taskId);
            candidate.setVisible(containForegroundTask);
            if (containForegroundTask) {
                mForegroundTaskId = candidate.getRootTaskId();
            }
        }
    }

    @Override
    public void onKeyguardVisibilityChanged(boolean showing) {
        if (mForegroundTaskId == INVALID_TASK_ID) {
            return;
        }
        mActiveAppPairs.get(mForegroundTaskId).setVisible(!showing);
    }

    @Override
    public boolean pair(int taskId1, int taskId2) {
        final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
+89 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.apppairs;

import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;

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

import android.content.res.Configuration;
import android.graphics.Rect;
import android.view.Display;
import android.view.SurfaceControl;

import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.wm.shell.ShellTestCase;

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

/** Tests for {@link AppPairLayout} */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppPairLayoutTests extends ShellTestCase {
    @Mock SurfaceControl mSurfaceControl;
    private Display mDisplay;
    private Configuration mConfiguration;
    private AppPairLayout mAppPairLayout;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mConfiguration = getConfiguration(false);
        mDisplay = mContext.getDisplay();
        mAppPairLayout = new AppPairLayout(mContext, mDisplay, mConfiguration, mSurfaceControl);
    }

    @After
    @UiThreadTest
    public void tearDown() {
        mAppPairLayout.release();
    }

    @Test
    @UiThreadTest
    public void testUpdateConfiguration() {
        assertThat(mAppPairLayout.updateConfiguration(getConfiguration(false))).isFalse();
        assertThat(mAppPairLayout.updateConfiguration(getConfiguration(true))).isTrue();
    }

    @Test
    @UiThreadTest
    public void testInitRelease() {
        mAppPairLayout.init();
        assertThat(mAppPairLayout.getDividerLeash()).isNotNull();
        mAppPairLayout.release();
        assertThat(mAppPairLayout.getDividerLeash()).isNull();
    }

    private static Configuration getConfiguration(boolean isLandscape) {
        final Configuration configuration = new Configuration();
        configuration.unset();
        configuration.orientation = isLandscape ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
        configuration.windowConfiguration.setBounds(
                new Rect(0, 0, isLandscape ? 2160 : 1080, isLandscape ? 1080 : 2160));
        return configuration;
    }
}
Loading