Loading core/java/android/view/WindowlessWindowManager.java +8 −1 Original line number Diff line number Diff line Loading @@ -207,12 +207,19 @@ public class WindowlessWindowManager implements IWindowSession { } /** @hide */ @Nullable protected SurfaceControl getSurfaceControl(View rootView) { final ViewRootImpl root = rootView.getViewRootImpl(); if (root == null) { return null; } final State s = mStateForWindow.get(root.mWindow.asBinder()); return getSurfaceControl(root.mWindow); } /** @hide */ @Nullable protected SurfaceControl getSurfaceControl(IWindow window) { final State s = mStateForWindow.get(window.asBinder()); if (s == null) { return null; } Loading libs/WindowManager/Shell/res/layout/split_divider.xml 0 → 100644 +27 −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. --> <com.android.wm.shell.apppairs.DividerView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" android:layout_width="match_parent"> <View style="@style/DockedDividerBackground" android:id="@+id/docked_divider_background" android:background="@color/docked_divider_background"/> </com.android.wm.shell.apppairs.DividerView> libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +45 −10 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import androidx.annotation.NonNull; 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 java.io.PrintWriter; Loading @@ -41,8 +42,6 @@ import java.io.PrintWriter; * {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair. * Also includes all UI for managing the pair like the divider. */ // TODO: Add divider // TODO: Handle display rotation class AppPair implements ShellTaskOrganizer.TaskListener { private static final String TAG = AppPair.class.getSimpleName(); Loading @@ -55,10 +54,13 @@ class AppPair implements ShellTaskOrganizer.TaskListener { private final AppPairsController mController; private final SyncTransactionQueue mSyncQueue; private final DisplayController mDisplayController; private AppPairLayout mAppPairLayout; AppPair(AppPairsController controller) { mController = controller; mSyncQueue = controller.getSyncTransactionQueue(); mDisplayController = controller.getDisplayController(); } int getRootTaskId() { Loading Loading @@ -90,13 +92,12 @@ class AppPair implements ShellTaskOrganizer.TaskListener { mTaskInfo1 = task1; mTaskInfo2 = task2; mAppPairLayout = new AppPairLayout( mDisplayController.getDisplayContext(mRootTaskInfo.displayId), mDisplayController.getDisplay(mRootTaskInfo.displayId), mRootTaskInfo.configuration, mRootTaskLeash); // TODO: properly calculate bounds for pairs. final Rect rootBounds = mRootTaskInfo.configuration.windowConfiguration.getBounds(); final Rect bounds1 = new Rect( rootBounds.left, rootBounds.top, rootBounds.right / 2, rootBounds.bottom / 2); final Rect bounds2 = new Rect( bounds1.right, bounds1.bottom, rootBounds.right, rootBounds.bottom); final WindowContainerToken token1 = task1.token; final WindowContainerToken token2 = task2.token; final WindowContainerTransaction wct = new WindowContainerTransaction(); Loading @@ -106,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, bounds1) .setBounds(token2, bounds2) .setBounds(token1, mAppPairLayout.getBounds1()) .setBounds(token2, mAppPairLayout.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); Loading @@ -131,6 +132,15 @@ class AppPair implements ShellTaskOrganizer.TaskListener { mTaskInfo1 = null; mTaskInfo2 = null; mAppPairLayout.release(); mAppPairLayout = null; } void setVisible(boolean visible) { if (mAppPairLayout == null) { return; } mAppPairLayout.setDividerVisibility(visible); } @Override Loading @@ -150,13 +160,20 @@ class AppPair implements ShellTaskOrganizer.TaskListener { if (mTaskLeash1 == null || mTaskLeash2 == null) return; setVisible(true); 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)); } Loading @@ -165,6 +182,24 @@ class AppPair implements ShellTaskOrganizer.TaskListener { public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { if (taskInfo.taskId == getRootTaskId()) { mRootTaskInfo = taskInfo; if (mAppPairLayout != null && mAppPairLayout.updateConfiguration(mRootTaskInfo.configuration)) { // Update bounds when there is root bounds or 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(); wct.setBounds(mTaskInfo1.token, bounds1) .setBounds(mTaskInfo2.token, bounds2); mController.getTaskOrganizer().applyTransaction(wct); mSyncQueue.runInSync(t -> t .setPosition(mTaskLeash1, bounds1.left, bounds1.top) .setPosition(mTaskLeash2, bounds2.left, bounds2.top) .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)); } } else if (taskInfo.taskId == getTaskId1()) { mTaskInfo1 = taskInfo; } else if (taskInfo.taskId == getTaskId2()) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java 0 → 100644 +224 −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.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. */ // 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 int mDividerWindowWidth; private final int mDividerWindowInsets; private boolean mIsLandscape; private Rect mRootBounds; private DIVIDE_POLICY mDividePolicy; private DividerView mDividerView; private SurfaceControl mDividerLeash; AppPairLayout( Context context, Display display, Configuration configuration, SurfaceControl rootLeash) { mContext = context.createConfigurationContext(configuration); mIsLandscape = isLandscape(configuration); 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); mViewHost = new SurfaceControlViewHost(mContext, display, mAppPairWindowManager); mDividePolicy = DIVIDE_POLICY.MIDDLE; mDividePolicy.update(mIsLandscape, 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)) { return false; } mIsLandscape = isLandscape; mRootBounds = rootBounds; mDividePolicy.update(mIsLandscape, mRootBounds, mDividerWindowWidth, mDividerWindowInsets); mViewHost.relayout( mDividePolicy.mDividerBounds.width(), mDividePolicy.mDividerBounds.height()); // TODO(172704238): handle divider bar rotation. 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(); } void setDividerVisibility(boolean visible) { if (mDividerView == null) { initDivider(); } if (visible) { mDividerView.show(); } else { mDividerView.hide(); } } private void initDivider() { 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); 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; } /** * 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(boolean isLandscape, 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) { 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; } } } 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); } } } libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java +2 −0 Original line number Diff line number Diff line Loading @@ -36,4 +36,6 @@ 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); } Loading
core/java/android/view/WindowlessWindowManager.java +8 −1 Original line number Diff line number Diff line Loading @@ -207,12 +207,19 @@ public class WindowlessWindowManager implements IWindowSession { } /** @hide */ @Nullable protected SurfaceControl getSurfaceControl(View rootView) { final ViewRootImpl root = rootView.getViewRootImpl(); if (root == null) { return null; } final State s = mStateForWindow.get(root.mWindow.asBinder()); return getSurfaceControl(root.mWindow); } /** @hide */ @Nullable protected SurfaceControl getSurfaceControl(IWindow window) { final State s = mStateForWindow.get(window.asBinder()); if (s == null) { return null; } Loading
libs/WindowManager/Shell/res/layout/split_divider.xml 0 → 100644 +27 −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. --> <com.android.wm.shell.apppairs.DividerView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" android:layout_width="match_parent"> <View style="@style/DockedDividerBackground" android:id="@+id/docked_divider_background" android:background="@color/docked_divider_background"/> </com.android.wm.shell.apppairs.DividerView>
libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +45 −10 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import androidx.annotation.NonNull; 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 java.io.PrintWriter; Loading @@ -41,8 +42,6 @@ import java.io.PrintWriter; * {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair. * Also includes all UI for managing the pair like the divider. */ // TODO: Add divider // TODO: Handle display rotation class AppPair implements ShellTaskOrganizer.TaskListener { private static final String TAG = AppPair.class.getSimpleName(); Loading @@ -55,10 +54,13 @@ class AppPair implements ShellTaskOrganizer.TaskListener { private final AppPairsController mController; private final SyncTransactionQueue mSyncQueue; private final DisplayController mDisplayController; private AppPairLayout mAppPairLayout; AppPair(AppPairsController controller) { mController = controller; mSyncQueue = controller.getSyncTransactionQueue(); mDisplayController = controller.getDisplayController(); } int getRootTaskId() { Loading Loading @@ -90,13 +92,12 @@ class AppPair implements ShellTaskOrganizer.TaskListener { mTaskInfo1 = task1; mTaskInfo2 = task2; mAppPairLayout = new AppPairLayout( mDisplayController.getDisplayContext(mRootTaskInfo.displayId), mDisplayController.getDisplay(mRootTaskInfo.displayId), mRootTaskInfo.configuration, mRootTaskLeash); // TODO: properly calculate bounds for pairs. final Rect rootBounds = mRootTaskInfo.configuration.windowConfiguration.getBounds(); final Rect bounds1 = new Rect( rootBounds.left, rootBounds.top, rootBounds.right / 2, rootBounds.bottom / 2); final Rect bounds2 = new Rect( bounds1.right, bounds1.bottom, rootBounds.right, rootBounds.bottom); final WindowContainerToken token1 = task1.token; final WindowContainerToken token2 = task2.token; final WindowContainerTransaction wct = new WindowContainerTransaction(); Loading @@ -106,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, bounds1) .setBounds(token2, bounds2) .setBounds(token1, mAppPairLayout.getBounds1()) .setBounds(token2, mAppPairLayout.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); Loading @@ -131,6 +132,15 @@ class AppPair implements ShellTaskOrganizer.TaskListener { mTaskInfo1 = null; mTaskInfo2 = null; mAppPairLayout.release(); mAppPairLayout = null; } void setVisible(boolean visible) { if (mAppPairLayout == null) { return; } mAppPairLayout.setDividerVisibility(visible); } @Override Loading @@ -150,13 +160,20 @@ class AppPair implements ShellTaskOrganizer.TaskListener { if (mTaskLeash1 == null || mTaskLeash2 == null) return; setVisible(true); 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)); } Loading @@ -165,6 +182,24 @@ class AppPair implements ShellTaskOrganizer.TaskListener { public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { if (taskInfo.taskId == getRootTaskId()) { mRootTaskInfo = taskInfo; if (mAppPairLayout != null && mAppPairLayout.updateConfiguration(mRootTaskInfo.configuration)) { // Update bounds when there is root bounds or 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(); wct.setBounds(mTaskInfo1.token, bounds1) .setBounds(mTaskInfo2.token, bounds2); mController.getTaskOrganizer().applyTransaction(wct); mSyncQueue.runInSync(t -> t .setPosition(mTaskLeash1, bounds1.left, bounds1.top) .setPosition(mTaskLeash2, bounds2.left, bounds2.top) .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)); } } else if (taskInfo.taskId == getTaskId1()) { mTaskInfo1 = taskInfo; } else if (taskInfo.taskId == getTaskId2()) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java 0 → 100644 +224 −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.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. */ // 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 int mDividerWindowWidth; private final int mDividerWindowInsets; private boolean mIsLandscape; private Rect mRootBounds; private DIVIDE_POLICY mDividePolicy; private DividerView mDividerView; private SurfaceControl mDividerLeash; AppPairLayout( Context context, Display display, Configuration configuration, SurfaceControl rootLeash) { mContext = context.createConfigurationContext(configuration); mIsLandscape = isLandscape(configuration); 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); mViewHost = new SurfaceControlViewHost(mContext, display, mAppPairWindowManager); mDividePolicy = DIVIDE_POLICY.MIDDLE; mDividePolicy.update(mIsLandscape, 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)) { return false; } mIsLandscape = isLandscape; mRootBounds = rootBounds; mDividePolicy.update(mIsLandscape, mRootBounds, mDividerWindowWidth, mDividerWindowInsets); mViewHost.relayout( mDividePolicy.mDividerBounds.width(), mDividePolicy.mDividerBounds.height()); // TODO(172704238): handle divider bar rotation. 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(); } void setDividerVisibility(boolean visible) { if (mDividerView == null) { initDivider(); } if (visible) { mDividerView.show(); } else { mDividerView.hide(); } } private void initDivider() { 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); 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; } /** * 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(boolean isLandscape, 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) { 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; } } } 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); } } }
libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java +2 −0 Original line number Diff line number Diff line Loading @@ -36,4 +36,6 @@ 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); }