diff options
| author | 2020-12-04 03:03:34 +0000 | |
|---|---|---|
| committer | 2020-12-04 03:03:34 +0000 | |
| commit | 1fa042b64f4d45e8c502d424d78c925d23b134d8 (patch) | |
| tree | 04043a14763847571f0afd22ecdadf05378494d0 | |
| parent | 089d0f680b48f527397c5a94fc69ffa7356c9e4c (diff) | |
| parent | 0c9c2cfac1f63f7447b4a2da3843568d572c3dd1 (diff) | |
Merge "Support dragging divider bar of app-pair to resize the splits"
13 files changed, 743 insertions, 390 deletions
diff --git a/libs/WindowManager/Shell/res/layout/split_divider.xml b/libs/WindowManager/Shell/res/layout/split_divider.xml index b86f36a14d17..341fe617b2d0 100644 --- a/libs/WindowManager/Shell/res/layout/split_divider.xml +++ b/libs/WindowManager/Shell/res/layout/split_divider.xml @@ -14,7 +14,7 @@ ~ limitations under the License. --> -<com.android.wm.shell.apppairs.DividerView +<com.android.wm.shell.common.split.DividerView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" android:layout_width="match_parent"> @@ -24,4 +24,4 @@ android:id="@+id/docked_divider_background" android:background="@color/docked_divider_background"/> -</com.android.wm.shell.apppairs.DividerView> +</com.android.wm.shell.common.split.DividerView> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java index d3032f83fc1c..cfbf8452ddae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java @@ -29,11 +29,13 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; 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.split.SplitLayout; import java.io.PrintWriter; @@ -42,7 +44,7 @@ import java.io.PrintWriter; * {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair. * Also includes all UI for managing the pair like the divider. */ -class AppPair implements ShellTaskOrganizer.TaskListener { +class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChangeListener { private static final String TAG = AppPair.class.getSimpleName(); private ActivityManager.RunningTaskInfo mRootTaskInfo; @@ -55,7 +57,7 @@ class AppPair implements ShellTaskOrganizer.TaskListener { private final AppPairsController mController; private final SyncTransactionQueue mSyncQueue; private final DisplayController mDisplayController; - private AppPairLayout mAppPairLayout; + private SplitLayout mSplitLayout; AppPair(AppPairsController controller) { mController = controller; @@ -92,11 +94,9 @@ class AppPair implements ShellTaskOrganizer.TaskListener { mTaskInfo1 = task1; mTaskInfo2 = task2; - mAppPairLayout = new AppPairLayout( + mSplitLayout = new SplitLayout( mDisplayController.getDisplayContext(mRootTaskInfo.displayId), - mDisplayController.getDisplay(mRootTaskInfo.displayId), - mRootTaskInfo.configuration, - mRootTaskLeash); + mRootTaskInfo.configuration, this, mRootTaskLeash); final WindowContainerToken token1 = task1.token; final WindowContainerToken token2 = task2.token; @@ -107,8 +107,8 @@ class AppPair implements ShellTaskOrganizer.TaskListener { .reparent(token2, mRootTaskInfo.token, true /* onTop */) .setWindowingMode(token1, WINDOWING_MODE_MULTI_WINDOW) .setWindowingMode(token2, WINDOWING_MODE_MULTI_WINDOW) - .setBounds(token1, mAppPairLayout.getBounds1()) - .setBounds(token2, mAppPairLayout.getBounds2()) + .setBounds(token1, mSplitLayout.getBounds1()) + .setBounds(token2, mSplitLayout.getBounds2()) // Moving the root task to top after the child tasks were repareted , or the root // task cannot be visible and focused. .reorder(mRootTaskInfo.token, true); @@ -117,6 +117,10 @@ class AppPair implements ShellTaskOrganizer.TaskListener { } void unpair() { + unpair(null /* toTopToken */); + } + + private void unpair(@Nullable WindowContainerToken toTopToken) { final WindowContainerToken token1 = mTaskInfo1.token; final WindowContainerToken token2 = mTaskInfo2.token; final WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -124,16 +128,16 @@ class AppPair implements ShellTaskOrganizer.TaskListener { // Reparent out of this container and reset windowing mode. wct.setHidden(mRootTaskInfo.token, true) .reorder(mRootTaskInfo.token, false) - .reparent(token1, null, false /* onTop */) - .reparent(token2, null, false /* onTop */) + .reparent(token1, null, token1 == toTopToken /* onTop */) + .reparent(token2, null, token2 == toTopToken /* onTop */) .setWindowingMode(token1, WINDOWING_MODE_UNDEFINED) .setWindowingMode(token2, WINDOWING_MODE_UNDEFINED); mController.getTaskOrganizer().applyTransaction(wct); mTaskInfo1 = null; mTaskInfo2 = null; - mAppPairLayout.release(); - mAppPairLayout = null; + mSplitLayout.release(); + mSplitLayout = null; } @Override @@ -153,17 +157,17 @@ class AppPair implements ShellTaskOrganizer.TaskListener { if (mTaskLeash1 == null || mTaskLeash2 == null) return; - mAppPairLayout.init(); - final SurfaceControl dividerLeash = mAppPairLayout.getDividerLeash(); - final Rect dividerBounds = mAppPairLayout.getDividerBounds(); + mSplitLayout.init(); + final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); + final Rect dividerBounds = mSplitLayout.getDividerBounds(); // TODO: Is there more we need to do here? mSyncQueue.runInSync(t -> { - t.setPosition(mTaskLeash1, mTaskInfo1.positionInParent.x, - mTaskInfo1.positionInParent.y) + t.setLayer(dividerLeash, Integer.MAX_VALUE) + .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) @@ -185,14 +189,14 @@ class AppPair implements ShellTaskOrganizer.TaskListener { } mRootTaskInfo = taskInfo; - if (mAppPairLayout != null - && mAppPairLayout.updateConfiguration(mRootTaskInfo.configuration)) { + if (mSplitLayout != null + && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) { // Update bounds when root bounds or its orientation changed. final WindowContainerTransaction wct = new WindowContainerTransaction(); - final SurfaceControl dividerLeash = mAppPairLayout.getDividerLeash(); - final Rect dividerBounds = mAppPairLayout.getDividerBounds(); - final Rect bounds1 = mAppPairLayout.getBounds1(); - final Rect bounds2 = mAppPairLayout.getBounds2(); + final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); + final Rect dividerBounds = mSplitLayout.getDividerBounds(); + final Rect bounds1 = mSplitLayout.getBounds1(); + final Rect bounds2 = mSplitLayout.getBounds2(); wct.setBounds(mTaskInfo1.token, bounds1) .setBounds(mTaskInfo2.token, bounds2); @@ -200,7 +204,9 @@ class AppPair implements ShellTaskOrganizer.TaskListener { mSyncQueue.runInSync(t -> t .setPosition(mTaskLeash1, bounds1.left, bounds1.top) .setPosition(mTaskLeash2, bounds2.left, bounds2.top) - .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)); + .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top) + // Resets layer to divider bar to make sure it is always on top. + .setLayer(dividerLeash, Integer.MAX_VALUE)); } } else if (taskInfo.taskId == getTaskId1()) { mTaskInfo1 = taskInfo; @@ -242,4 +248,39 @@ class AppPair implements ShellTaskOrganizer.TaskListener { public String toString() { return TAG + "#" + getRootTaskId(); } + + @Override + public void onSnappedToDismiss(boolean snappedToEnd) { + unpair(snappedToEnd ? mTaskInfo1.token : mTaskInfo2.token /* toTopToken */); + } + + @Override + public void onBoundsChanging(SplitLayout layout) { + final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); + if (dividerLeash == null) return; + final Rect dividerBounds = layout.getDividerBounds(); + final Rect bounds1 = layout.getBounds1(); + final Rect bounds2 = layout.getBounds2(); + mSyncQueue.runInSync(t -> t + .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top) + .setPosition(mTaskLeash1, bounds1.left, bounds1.top) + .setPosition(mTaskLeash2, bounds2.left, bounds2.top)); + } + + @Override + public void onBoundsChanged(SplitLayout layout) { + final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); + if (dividerLeash == null) return; + final Rect dividerBounds = layout.getDividerBounds(); + final Rect bounds1 = layout.getBounds1(); + final Rect bounds2 = layout.getBounds2(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setBounds(mTaskInfo1.token, bounds1) + .setBounds(mTaskInfo2.token, bounds2); + mController.getTaskOrganizer().applyTransaction(wct); + mSyncQueue.runInSync(t -> t + .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top) + .setPosition(mTaskLeash1, bounds1.left, bounds1.top) + .setPosition(mTaskLeash2, bounds2.left, bounds2.top)); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java deleted file mode 100644 index 8c8655e1ff1f..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * 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.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; -import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; -import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; -import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; -import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; - -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.Region; -import android.os.Binder; -import android.os.IBinder; -import android.view.Display; -import android.view.IWindow; -import android.view.LayoutInflater; -import android.view.SurfaceControl; -import android.view.SurfaceControlViewHost; -import android.view.WindowManager; -import android.view.WindowlessWindowManager; - -import com.android.wm.shell.R; - -/** - * Records and handles layout of a pair of apps. - */ -final class AppPairLayout { - private static final String DIVIDER_WINDOW_TITLE = "AppPairDivider"; - private final Display mDisplay; - private final int mDividerWindowWidth; - private final int mDividerWindowInsets; - private final AppPairWindowManager mAppPairWindowManager; - - private Context mContext; - private Rect mRootBounds; - private DIVIDE_POLICY mDividePolicy; - - private SurfaceControlViewHost mViewHost; - private SurfaceControl mDividerLeash; - - AppPairLayout( - Context context, - Display display, - Configuration configuration, - SurfaceControl rootLeash) { - mContext = context.createConfigurationContext(configuration); - mDisplay = display; - mRootBounds = configuration.windowConfiguration.getBounds(); - mDividerWindowWidth = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.docked_stack_divider_thickness); - mDividerWindowInsets = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.docked_stack_divider_insets); - - mAppPairWindowManager = new AppPairWindowManager(configuration, rootLeash); - mDividePolicy = DIVIDE_POLICY.MIDDLE; - mDividePolicy.update(mRootBounds, mDividerWindowWidth, mDividerWindowInsets); - } - - boolean updateConfiguration(Configuration configuration) { - mAppPairWindowManager.setConfiguration(configuration); - final Rect rootBounds = configuration.windowConfiguration.getBounds(); - if (isIdenticalBounds(mRootBounds, rootBounds)) { - return false; - } - - mContext = mContext.createConfigurationContext(configuration); - mRootBounds = rootBounds; - mDividePolicy.update(mRootBounds, mDividerWindowWidth, mDividerWindowInsets); - release(); - init(); - return true; - } - - Rect getBounds1() { - return mDividePolicy.mBounds1; - } - - Rect getBounds2() { - return mDividePolicy.mBounds2; - } - - Rect getDividerBounds() { - return mDividePolicy.mDividerBounds; - } - - SurfaceControl getDividerLeash() { - return mDividerLeash; - } - - void release() { - if (mViewHost == null) { - return; - } - mViewHost.release(); - mDividerLeash = null; - mViewHost = null; - } - - void init() { - if (mViewHost == null) { - mViewHost = new SurfaceControlViewHost(mContext, mDisplay, mAppPairWindowManager); - } - - final DividerView dividerView = (DividerView) LayoutInflater.from(mContext) - .inflate(R.layout.split_divider, null); - - WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - mDividePolicy.mDividerBounds.width(), - mDividePolicy.mDividerBounds.height(), - TYPE_DOCK_DIVIDER, - FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH - | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY, - PixelFormat.TRANSLUCENT); - lp.token = new Binder(); - lp.setTitle(DIVIDER_WINDOW_TITLE); - lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; - - mViewHost.setView(dividerView, lp); - mDividerLeash = mAppPairWindowManager.getSurfaceControl(mViewHost.getWindowToken()); - } - - 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; - } - - /** - * Indicates the policy of placing divider bar and corresponding split-screens. - */ - // TODO(172704238): add more divide policy and provide snap to resize feature for divider bar. - enum DIVIDE_POLICY { - MIDDLE; - - void update(Rect rootBounds, int dividerWindowWidth, int dividerWindowInsets) { - final int dividerOffset = dividerWindowWidth / 2; - final int boundsOffset = dividerOffset - dividerWindowInsets; - - mDividerBounds = new Rect(rootBounds); - mBounds1 = new Rect(rootBounds); - mBounds2 = new Rect(rootBounds); - - switch (this) { - case MIDDLE: - default: - if (isLandscape(rootBounds)) { - mDividerBounds.left = rootBounds.width() / 2 - dividerOffset; - mDividerBounds.right = rootBounds.width() / 2 + dividerOffset; - mBounds1.left = rootBounds.width() / 2 + boundsOffset; - mBounds2.right = rootBounds.width() / 2 - boundsOffset; - } else { - mDividerBounds.top = rootBounds.height() / 2 - dividerOffset; - mDividerBounds.bottom = rootBounds.height() / 2 + dividerOffset; - mBounds1.bottom = rootBounds.height() / 2 - boundsOffset; - mBounds2.top = rootBounds.height() / 2 + boundsOffset; - } - } - } - - private boolean isLandscape(Rect bounds) { - return bounds.width() > bounds.height(); - } - - Rect mDividerBounds; - Rect mBounds1; - Rect mBounds2; - } - - /** - * WindowManger for app pair. Holds view hierarchy for the root task. - */ - private static final class AppPairWindowManager extends WindowlessWindowManager { - AppPairWindowManager(Configuration config, SurfaceControl rootSurface) { - super(config, rootSurface, null /* hostInputToken */); - } - - @Override - public void setTouchRegion(IBinder window, Region region) { - super.setTouchRegion(window, region); - } - - @Override - public SurfaceControl getSurfaceControl(IWindow window) { - return super.getSurfaceControl(window); - } - - @Override - public void setConfiguration(Configuration configuration) { - super.setConfiguration(configuration); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/DividerView.java deleted file mode 100644 index 41b5e47e7fa9..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/DividerView.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * Stack divider for app pair. - */ -public class DividerView extends FrameLayout { - public DividerView(@NonNull Context context) { - super(context); - } - - public DividerView(@NonNull Context context, - @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - void show() { - post(() -> setVisibility(View.VISIBLE)); - } - - void hide() { - post(() -> setVisibility(View.INVISIBLE)); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java new file mode 100644 index 000000000000..50d9fe8629ac --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -0,0 +1,171 @@ +/* + * 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.common.split; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.SurfaceControlViewHost; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.policy.DividerSnapAlgorithm; + +/** + * Stack divider for app pair. + */ +// TODO(b/172704238): add handle view to indicate touching status. +public class DividerView extends FrameLayout implements View.OnTouchListener { + private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + + private SplitLayout mSplitLayout; + private SurfaceControlViewHost mViewHost; + private DragListener mDragListener; + + private VelocityTracker mVelocityTracker; + private boolean mMoving; + private int mStartPos; + + public DividerView(@NonNull Context context) { + super(context); + } + + public DividerView(@NonNull Context context, + @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + /** Sets up essential dependencies of the divider bar. */ + public void setup( + SplitLayout layout, + SurfaceControlViewHost viewHost, + @Nullable DragListener dragListener) { + mSplitLayout = layout; + mViewHost = viewHost; + mDragListener = dragListener; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + setOnTouchListener(this); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mSplitLayout == null) { + return false; + } + + final int action = event.getAction() & MotionEvent.ACTION_MASK; + final boolean isLandscape = isLandscape(); + // Using raw xy to prevent lost track of motion events while moving divider bar. + final int touchPos = isLandscape ? (int) event.getRawX() : (int) event.getRawY(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mVelocityTracker = VelocityTracker.obtain(); + mVelocityTracker.addMovement(event); + setSlippery(false); + mStartPos = touchPos; + mMoving = false; + break; + case MotionEvent.ACTION_MOVE: + mVelocityTracker.addMovement(event); + if (!mMoving && Math.abs(touchPos - mStartPos) > mTouchSlop) { + mStartPos = touchPos; + mMoving = true; + if (mDragListener != null) { + mDragListener.onDragStart(); + } + } + if (mMoving) { + final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos; + mSplitLayout.updateDividePosition(position); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mVelocityTracker.addMovement(event); + mVelocityTracker.computeCurrentVelocity(1000 /* units */); + final float velocity = isLandscape + ? mVelocityTracker.getXVelocity() + : mVelocityTracker.getYVelocity(); + setSlippery(true); + mMoving = false; + if (mDragListener != null) { + mDragListener.onDragEnd(); + } + + final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos; + final DividerSnapAlgorithm.SnapTarget snapTarget = + mSplitLayout.findSnapTarget(position, velocity); + mSplitLayout.setSnapTarget(snapTarget); + break; + } + return true; + } + + private void setSlippery(boolean slippery) { + if (mViewHost == null) { + return; + } + + final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams(); + final boolean isSlippery = (lp.flags & FLAG_SLIPPERY) != 0; + if (isSlippery == slippery) { + return; + } + + if (slippery) { + lp.flags |= FLAG_SLIPPERY; + } else { + lp.flags &= ~FLAG_SLIPPERY; + } + mViewHost.relayout(lp); + } + + private boolean isLandscape() { + return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE; + } + + /** Monitors dragging action of the divider bar. */ + // TODO(b/172704238): add listeners to deal with resizing state of the app windows. + public interface DragListener { + /** Called when start dragging. */ + void onDragStart(); + /** Called when stop dragging. */ + void onDragEnd(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java new file mode 100644 index 000000000000..e11037f55cfa --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -0,0 +1,205 @@ +/* + * 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.common.split; + +import static android.view.WindowManager.DOCKED_LEFT; +import static android.view.WindowManager.DOCKED_TOP; + +import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END; +import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Rect; +import android.view.SurfaceControl; + +import androidx.annotation.Nullable; + +import com.android.internal.policy.DividerSnapAlgorithm; + +/** + * Records and handles layout of splits. Helps to calculate proper bounds when configuration or + * divide position changes. + */ +public class SplitLayout { + private final int mDividerWindowWidth; + private final int mDividerInsets; + private final int mDividerSize; + + private final Rect mRootBounds = new Rect(); + private final Rect mDividerBounds = new Rect(); + private final Rect mBounds1 = new Rect(); + private final Rect mBounds2 = new Rect(); + private final LayoutChangeListener mLayoutChangeListener; + private final SplitWindowManager mSplitWindowManager; + + private Context mContext; + private DividerSnapAlgorithm mDividerSnapAlgorithm; + private int mDividePosition; + + public SplitLayout(Context context, Configuration configuration, + LayoutChangeListener layoutChangeListener, SurfaceControl rootLeash) { + mContext = context.createConfigurationContext(configuration); + mLayoutChangeListener = layoutChangeListener; + mSplitWindowManager = new SplitWindowManager(mContext, configuration, rootLeash); + + mDividerWindowWidth = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_thickness); + mDividerInsets = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_insets); + mDividerSize = mDividerWindowWidth - mDividerInsets * 2; + + mRootBounds.set(configuration.windowConfiguration.getBounds()); + mDividerSnapAlgorithm = getSnapAlgorithm(context.getResources(), mRootBounds); + mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position; + updateBounds(mDividePosition); + } + + /** Gets bounds of the primary split. */ + public Rect getBounds1() { + return mBounds1; + } + + /** Gets bounds of the secondary split. */ + public Rect getBounds2() { + return mBounds2; + } + + /** Gets bounds of divider window. */ + public Rect getDividerBounds() { + return mDividerBounds; + } + + /** Returns leash of the current divider bar. */ + @Nullable + public SurfaceControl getDividerLeash() { + return mSplitWindowManager == null ? null : mSplitWindowManager.getSurfaceControl(); + } + + int getDividePosition() { + return mDividePosition; + } + + /** Applies new configuration, returns {@code false} if there's no effect to the layout. */ + public boolean updateConfiguration(Configuration configuration) { + final Rect rootBounds = configuration.windowConfiguration.getBounds(); + if (mRootBounds.equals(rootBounds)) { + return false; + } + + mContext = mContext.createConfigurationContext(configuration); + mSplitWindowManager.setConfiguration(configuration); + mRootBounds.set(rootBounds); + mDividerSnapAlgorithm = getSnapAlgorithm(mContext.getResources(), mRootBounds); + mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position; + updateBounds(mDividePosition); + release(); + init(); + return true; + } + + /** Updates recording bounds of divider window and both of the splits. */ + private void updateBounds(int position) { + mDividerBounds.set(mRootBounds); + mBounds1.set(mRootBounds); + mBounds2.set(mRootBounds); + if (isLandscape(mRootBounds)) { + mDividerBounds.left = position - mDividerInsets; + mDividerBounds.right = mDividerBounds.left + mDividerWindowWidth; + mBounds1.right = mBounds1.left + position; + mBounds2.left = mBounds1.right + mDividerSize; + } else { + mDividerBounds.top = position - mDividerInsets; + mDividerBounds.bottom = mDividerBounds.top + mDividerWindowWidth; + mBounds1.bottom = mBounds1.top + position; + mBounds2.top = mBounds1.bottom + mDividerSize; + } + } + + /** Inflates {@link DividerView} on the root surface. */ + public void init() { + mSplitWindowManager.init(this); + } + + /** Releases the surface holding the current {@link DividerView}. */ + public void release() { + mSplitWindowManager.release(); + } + + /** + * Updates bounds with the passing position. Usually used to update recording bounds while + * performing animation or dragging divider bar to resize the splits. + */ + public void updateDividePosition(int position) { + updateBounds(position); + mLayoutChangeListener.onBoundsChanging(this); + } + + /** + * Sets new divide position and updates bounds correspondingly. Notifies listener if the new + * target indicates dismissing split. + */ + public void setSnapTarget(DividerSnapAlgorithm.SnapTarget snapTarget) { + switch(snapTarget.flag) { + case FLAG_DISMISS_START: + mLayoutChangeListener.onSnappedToDismiss(false /* snappedToEnd */); + break; + case FLAG_DISMISS_END: + mLayoutChangeListener.onSnappedToDismiss(true /* snappedToEnd */); + break; + default: + mDividePosition = snapTarget.position; + updateBounds(mDividePosition); + mLayoutChangeListener.onBoundsChanged(this); + break; + } + } + + /** + * Returns {@link DividerSnapAlgorithm.SnapTarget} which matches passing position and velocity. + */ + public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity) { + return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity); + } + + private DividerSnapAlgorithm getSnapAlgorithm(Resources resources, Rect rootBounds) { + final boolean isLandscape = isLandscape(rootBounds); + return new DividerSnapAlgorithm( + resources, + rootBounds.width(), + rootBounds.height(), + mDividerSize, + !isLandscape, + new Rect() /* insets */, + isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */); + } + + private static boolean isLandscape(Rect bounds) { + return bounds.width() > bounds.height(); + } + + /** Listens layout change event. */ + public interface LayoutChangeListener { + /** Calls when dismissing split. */ + void onSnappedToDismiss(boolean snappedToEnd); + /** Calls when the bounds is changing due to animation or dragging divider bar. */ + void onBoundsChanging(SplitLayout layout); + /** Calls when the target bounds changed. */ + void onBoundsChanged(SplitLayout layout); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java new file mode 100644 index 000000000000..542867d83e29 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java @@ -0,0 +1,115 @@ +/* + * 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.common.split; + +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; +import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; +import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Binder; +import android.os.IBinder; +import android.view.IWindow; +import android.view.LayoutInflater; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; + +import androidx.annotation.Nullable; + +import com.android.wm.shell.R; + +/** + * Holds view hierarchy of a root surface and helps to inflate {@link DividerView} for a split. + */ +public final class SplitWindowManager extends WindowlessWindowManager { + private static final String DIVIDER_WINDOW_TITLE = "SplitDivider"; + + private Context mContext; + private SurfaceControlViewHost mViewHost; + + public SplitWindowManager(Context context, Configuration config, SurfaceControl rootSurface) { + super(config, rootSurface, null /* hostInputToken */); + mContext = context.createConfigurationContext(config); + } + + @Override + public void setTouchRegion(IBinder window, Region region) { + super.setTouchRegion(window, region); + } + + @Override + public SurfaceControl getSurfaceControl(IWindow window) { + return super.getSurfaceControl(window); + } + + @Override + public void setConfiguration(Configuration configuration) { + super.setConfiguration(configuration); + mContext = mContext.createConfigurationContext(configuration); + } + + /** Inflates {@link DividerView} on to the root surface. */ + void init(SplitLayout splitLayout) { + if (mViewHost == null) { + mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + } + + final Rect dividerBounds = splitLayout.getDividerBounds(); + final DividerView dividerView = (DividerView) LayoutInflater.from(mContext) + .inflate(R.layout.split_divider, null /* root */); + + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH + | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY, + PixelFormat.TRANSLUCENT); + lp.token = new Binder(); + lp.setTitle(DIVIDER_WINDOW_TITLE); + lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; + mViewHost.setView(dividerView, lp); + dividerView.setup(splitLayout, mViewHost, null /* dragListener */); + } + + /** + * Releases the surface control of the current {@link DividerView} and tear down the view + * hierarchy. + */ + void release() { + if (mViewHost == null) return; + mViewHost.release(); + mViewHost = null; + } + + /** + * Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not + * feasible. + */ + @Nullable + SurfaceControl getSurfaceControl() { + return mViewHost == null ? null : getSurfaceControl(mViewHost.getWindowToken()); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairLayoutTests.java deleted file mode 100644 index c9d32c4b1f76..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairLayoutTests.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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; - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java index f12648a7f709..8dbc1d56bcc2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java @@ -55,13 +55,13 @@ public class AppPairTests extends ShellTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); + when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext); + when(mDisplayController.getDisplay(anyInt())).thenReturn( + mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY)); mController = new TestAppPairsController( mTaskOrganizer, mSyncQueue, mDisplayController); - when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext); - when(mDisplayController.getDisplay(anyInt())).thenReturn( - mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY)); } @After diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java index f8c68d2018da..fada694a4c07 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java @@ -55,14 +55,14 @@ public class AppPairsControllerTests extends ShellTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); + when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext); + when(mDisplayController.getDisplay(anyInt())).thenReturn( + mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY)); mController = new TestAppPairsController( mTaskOrganizer, mSyncQueue, mDisplayController); mPool = mController.getPool(); - when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext); - when(mDisplayController.getDisplay(anyInt())).thenReturn( - mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY)); } @After diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java index 8ece913de53f..a3f134ee97ed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java @@ -18,10 +18,14 @@ package com.android.wm.shell.apppairs; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; 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; @@ -35,7 +39,7 @@ import org.mockito.MockitoAnnotations; /** Tests for {@link AppPairsPool} */ @SmallTest @RunWith(AndroidJUnit4.class) -public class AppPairsPoolTests { +public class AppPairsPoolTests extends ShellTestCase { private TestAppPairsController mController; private TestAppPairsPool mPool; @Mock private SyncTransactionQueue mSyncQueue; @@ -45,6 +49,7 @@ public class AppPairsPoolTests { @Before public void setUp() { MockitoAnnotations.initMocks(this); + when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext); mController = new TestAppPairsController( mTaskOrganizer, mSyncQueue, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java new file mode 100644 index 000000000000..d87f4c60fad4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -0,0 +1,106 @@ +/* + * 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.common.split; + +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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.view.SurfaceControl; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.policy.DividerSnapAlgorithm; +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Tests for {@link SplitLayout} */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SplitLayoutTests extends ShellTestCase { + @Mock SplitLayout.LayoutChangeListener mLayoutChangeListener; + @Mock SurfaceControl mRootLeash; + private SplitLayout mSplitLayout; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mSplitLayout = new SplitLayout( + mContext, + getConfiguration(false), + mLayoutChangeListener, + mRootLeash); + } + + @Test + @UiThreadTest + public void testUpdateConfiguration() { + assertThat(mSplitLayout.updateConfiguration(getConfiguration(false))).isFalse(); + assertThat(mSplitLayout.updateConfiguration(getConfiguration(true))).isTrue(); + } + + @Test + public void testUpdateDividePosition() { + mSplitLayout.updateDividePosition(anyInt()); + verify(mLayoutChangeListener).onBoundsChanging(any(SplitLayout.class)); + } + + @Test + public void testSetSnapTarget() { + DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0, + DividerSnapAlgorithm.SnapTarget.FLAG_NONE); + mSplitLayout.setSnapTarget(snapTarget); + verify(mLayoutChangeListener).onBoundsChanged(any(SplitLayout.class)); + + // verify it callbacks properly when the snap target indicates dismissing split. + snapTarget = getSnapTarget(0, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START); + mSplitLayout.setSnapTarget(snapTarget); + verify(mLayoutChangeListener).onSnappedToDismiss(eq(false)); + snapTarget = getSnapTarget(0, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END); + mSplitLayout.setSnapTarget(snapTarget); + verify(mLayoutChangeListener).onSnappedToDismiss(eq(true)); + } + + 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; + } + + private static DividerSnapAlgorithm.SnapTarget getSnapTarget(int position, int flag) { + return new DividerSnapAlgorithm.SnapTarget( + position /* position */, position /* taskPosition */, flag); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java new file mode 100644 index 000000000000..aa0eb2f95ed8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java @@ -0,0 +1,66 @@ +/* + * 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.common.split; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.res.Configuration; +import android.graphics.Rect; +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.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Tests for {@link SplitWindowManager} */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SplitWindowManagerTests extends ShellTestCase { + @Mock SurfaceControl mSurfaceControl; + @Mock SplitLayout mSplitLayout; + private SplitWindowManager mSplitWindowManager; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + final Configuration configuration = new Configuration(); + configuration.setToDefaults(); + mSplitWindowManager = new SplitWindowManager(mContext, configuration, mSurfaceControl); + when(mSplitLayout.getDividerBounds()).thenReturn( + new Rect(0, 0, configuration.windowConfiguration.getBounds().width(), + configuration.windowConfiguration.getBounds().height())); + } + + @Test + @UiThreadTest + public void testInitRelease() { + mSplitWindowManager.init(mSplitLayout); + assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull(); + mSplitWindowManager.release(); + assertThat(mSplitWindowManager.getSurfaceControl()).isNull(); + } +} |