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
This commit is contained in:
Jerry Chang
2020-11-17 23:08:16 +08:00
parent 10963d5b06
commit f5592d148b
12 changed files with 147 additions and 128 deletions

View File

@@ -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,
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));
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(mTaskLeash1)
.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();

View File

@@ -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();
void init() {
if (mViewHost == null) {
mViewHost = new SurfaceControlViewHost(mContext, mDisplay, mAppPairWindowManager);
}
if (visible) {
mDividerView.show();
} else {
mDividerView.hide();
}
}
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;

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -0,0 +1,89 @@
/*
* 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;
}
}

View File

@@ -34,7 +34,6 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import org.junit.After;
import org.junit.Before;
@@ -52,7 +51,6 @@ public class AppPairTests extends ShellTestCase {
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController;
@Mock private TaskStackListenerImpl mTaskStackListener;
@Before
public void setUp() {
@@ -60,8 +58,7 @@ public class AppPairTests extends ShellTestCase {
mController = new TestAppPairsController(
mTaskOrganizer,
mSyncQueue,
mDisplayController,
mTaskStackListener);
mDisplayController);
when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
when(mDisplayController.getDisplay(anyInt())).thenReturn(
mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));

View File

@@ -34,7 +34,6 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import org.junit.After;
import org.junit.Before;
@@ -52,7 +51,6 @@ public class AppPairsControllerTests extends ShellTestCase {
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController;
@Mock private TaskStackListenerImpl mTaskStackListener;
@Before
public void setUp() {
@@ -60,8 +58,7 @@ public class AppPairsControllerTests extends ShellTestCase {
mController = new TestAppPairsController(
mTaskOrganizer,
mSyncQueue,
mDisplayController,
mTaskStackListener);
mDisplayController);
mPool = mController.getPool();
when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
when(mDisplayController.getDisplay(anyInt())).thenReturn(

View File

@@ -24,7 +24,6 @@ import androidx.test.filters.SmallTest;
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.TaskStackListenerImpl;
import org.junit.After;
import org.junit.Before;
@@ -42,7 +41,6 @@ public class AppPairsPoolTests {
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController;
@Mock private TaskStackListenerImpl mTaskStackListener;
@Before
public void setUp() {
@@ -50,8 +48,7 @@ public class AppPairsPoolTests {
mController = new TestAppPairsController(
mTaskOrganizer,
mSyncQueue,
mDisplayController,
mTaskStackListener);
mDisplayController);
mPool = mController.getPool();
}

View File

@@ -19,14 +19,13 @@ package com.android.wm.shell.apppairs;
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.TaskStackListenerImpl;
public class TestAppPairsController extends AppPairsController {
TestAppPairsPool mPool;
public TestAppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
DisplayController displayController, TaskStackListenerImpl taskStackListener) {
super(organizer, syncQueue, displayController, taskStackListener);
DisplayController displayController) {
super(organizer, syncQueue, displayController);
mPool = new TestAppPairsPool(this);
setPairsPool(mPool);
}

View File

@@ -53,7 +53,6 @@ import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
@@ -99,13 +98,11 @@ public final class WMShell extends SystemUI
private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
private final ProtoTracer mProtoTracer;
private final Optional<ShellCommandHandler> mShellCommandHandler;
private final Optional<AppPairs> mAppPairsOptional;
private boolean mIsSysUiStateValid;
private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
private KeyguardUpdateMonitorCallback mAppPairsKeyguardCallback;
@Inject
public WMShell(Context context, CommandQueue commandQueue,
@@ -119,8 +116,7 @@ public final class WMShell extends SystemUI
Optional<OneHanded> oneHandedOptional,
Optional<HideDisplayCutout> hideDisplayCutoutOptional,
ProtoTracer protoTracer,
Optional<ShellCommandHandler> shellCommandHandler,
Optional<AppPairs> appPairsOptional) {
Optional<ShellCommandHandler> shellCommandHandler) {
super(context);
mCommandQueue = commandQueue;
mConfigurationController = configurationController;
@@ -135,7 +131,6 @@ public final class WMShell extends SystemUI
mProtoTracer = protoTracer;
mProtoTracer.add(this);
mShellCommandHandler = shellCommandHandler;
mAppPairsOptional = appPairsOptional;
}
@Override
@@ -145,7 +140,6 @@ public final class WMShell extends SystemUI
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
mAppPairsOptional.ifPresent(this::initAppPairs);
}
@VisibleForTesting
@@ -294,16 +288,6 @@ public final class WMShell extends SystemUI
});
}
void initAppPairs(AppPairs appPairs) {
mAppPairsKeyguardCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onKeyguardVisibilityChanged(boolean showing) {
appPairs.onKeyguardVisibilityChanged(showing);
}
};
mKeyguardUpdateMonitor.registerCallback(mAppPairsKeyguardCallback);
}
@Override
public void writeToProto(SystemUiTraceProto proto) {
if (proto.wmShell == null) {

View File

@@ -84,10 +84,8 @@ public class WMShellModule {
@WMSingleton
@Provides
static AppPairs provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, DisplayController displayController,
TaskStackListenerImpl taskStackListener) {
return new AppPairsController(shellTaskOrganizer, syncQueue, displayController,
taskStackListener);
SyncTransactionQueue syncQueue, DisplayController displayController) {
return new AppPairsController(shellTaskOrganizer, syncQueue, displayController);
}
@WMSingleton

View File

@@ -34,7 +34,6 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedGestureHandler;
@@ -69,7 +68,6 @@ public class WMShellTest extends SysuiTestCase {
@Mock HideDisplayCutout mHideDisplayCutout;
@Mock ProtoTracer mProtoTracer;
@Mock ShellCommandHandler mShellCommandHandler;
@Mock AppPairs mAppPairs;
@Before
public void setUp() {
@@ -79,7 +77,7 @@ public class WMShellTest extends SysuiTestCase {
mKeyguardUpdateMonitor, mNavigationModeController,
mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen),
Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), mProtoTracer,
Optional.of(mShellCommandHandler), Optional.of(mAppPairs));
Optional.of(mShellCommandHandler));
when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
}