Loading libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl 0 → 100644 +103 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.stagesplit; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; import android.os.UserHandle; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.window.RemoteTransition; import com.android.wm.shell.stagesplit.ISplitScreenListener; /** * Interface that is exposed to remote callers to manipulate the splitscreen feature. */ interface ISplitScreen { /** * Registers a split screen listener. */ oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1; /** * Unregisters a split screen listener. */ oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2; /** * Hides the side-stage if it is currently visible. */ oneway void setSideStageVisibility(boolean visible) = 3; /** * Removes a task from the side stage. */ oneway void removeFromSideStage(int taskId) = 4; /** * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID * to indicate leaving no top task after leaving split-screen. */ oneway void exitSplitScreen(int toTopTaskId) = 5; /** * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */ oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6; /** * Starts a task in a stage. */ oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7; /** * Starts a shortcut in a stage. */ oneway void startShortcut(String packageName, String shortcutId, int stage, int position, in Bundle options, in UserHandle user) = 8; /** * Starts an activity in a stage. */ oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage, int position, in Bundle options) = 9; /** * Starts tasks simultaneously in one transition. */ oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId, in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10; /** * Version of startTasks using legacy transition system. */ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions, int sideTaskId, in Bundle sideOptions, int sidePosition, in RemoteAnimationAdapter adapter) = 11; /** * Blocking call that notifies and gets additional split-screen targets when entering * recents (for example: the dividerBar). * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled). * @param appTargets apps that will be re-parented to display area */ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, in RemoteAnimationTarget[] appTargets) = 12; } libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl 0 → 100644 +33 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.stagesplit; /** * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. */ oneway interface ISplitScreenListener { /** * Called when the stage position changes. */ void onStagePositionChanged(int stage, int position); /** * Called when a task changes stages. */ void onTaskStageChanged(int taskId, int stage, boolean visible); } No newline at end of file libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java 0 → 100644 +104 −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.stagesplit; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import android.annotation.Nullable; import android.graphics.Rect; import android.view.SurfaceSession; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; /** * Main stage for split-screen mode. When split-screen is active all standard activity types launch * on the main stage, except for task that are explicitly pinned to the {@link SideStage}. * @see StageCoordinator */ class MainStage extends StageTaskListener { private static final String TAG = MainStage.class.getSimpleName(); private boolean mIsActive = false; MainStage(ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, SurfaceSession surfaceSession, @Nullable StageTaskUnfoldController stageTaskUnfoldController) { super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, stageTaskUnfoldController); } boolean isActive() { return mIsActive; } void activate(Rect rootBounds, WindowContainerTransaction wct) { if (mIsActive) return; final WindowContainerToken rootToken = mRootTaskInfo.token; wct.setBounds(rootToken, rootBounds) .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW) .setLaunchRoot( rootToken, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES) .reparentTasks( null /* currentParent */, rootToken, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES, true /* onTop */) // Moving the root task to top after the child tasks were re-parented , or the root // task cannot be visible and focused. .reorder(rootToken, true /* onTop */); mIsActive = true; } void deactivate(WindowContainerTransaction wct) { deactivate(wct, false /* toTop */); } void deactivate(WindowContainerTransaction wct, boolean toTop) { if (!mIsActive) return; mIsActive = false; if (mRootTaskInfo == null) return; final WindowContainerToken rootToken = mRootTaskInfo.token; wct.setLaunchRoot( rootToken, null, null) .reparentTasks( rootToken, null /* newParent */, CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, CONTROLLED_ACTIVITY_TYPES, toTop) // We want this re-order to the bottom regardless since we are re-parenting // all its tasks. .reorder(rootToken, false /* onTop */); } void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) { wct.setBounds(mRootTaskInfo.token, bounds) .setWindowingMode(mRootTaskInfo.token, windowingMode); } } libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java 0 → 100644 +181 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.stagesplit; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Binder; import android.view.IWindow; import android.view.InsetsSource; import android.view.InsetsState; import android.view.LayoutInflater; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowlessWindowManager; import android.widget.FrameLayout; import com.android.wm.shell.R; /** * Handles drawing outline of the bounds of provided root surface. The outline will be drown with * the consideration of display insets like status bar, navigation bar and display cutout. */ class OutlineManager extends WindowlessWindowManager { private static final String WINDOW_NAME = "SplitOutlineLayer"; private final Context mContext; private final Rect mRootBounds = new Rect(); private final Rect mTempRect = new Rect(); private final Rect mLastOutlineBounds = new Rect(); private final InsetsState mInsetsState = new InsetsState(); private final int mExpandedTaskBarHeight; private OutlineView mOutlineView; private SurfaceControlViewHost mViewHost; private SurfaceControl mHostLeash; private SurfaceControl mLeash; OutlineManager(Context context, Configuration configuration) { super(configuration, null /* rootSurface */, null /* hostInputToken */); mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY, null /* options */); mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.taskbar_frame_height); } @Override protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { b.setParent(mHostLeash); } void inflate(SurfaceControl rootLeash, Rect rootBounds) { if (mLeash != null || mViewHost != null) return; mHostLeash = rootLeash; mRootBounds.set(rootBounds); mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext) .inflate(R.layout.split_outline, null); mOutlineView = rootLayout.findViewById(R.id.split_outline); final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); lp.width = mRootBounds.width(); lp.height = mRootBounds.height(); lp.token = new Binder(); lp.setTitle(WINDOW_NAME); lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports // TRUSTED_OVERLAY for windowless window without input channel. mViewHost.setView(rootLayout, lp); mLeash = getSurfaceControl(mViewHost.getWindowToken()); drawOutline(); } void release() { if (mViewHost != null) { mViewHost.release(); mViewHost = null; } mRootBounds.setEmpty(); mLastOutlineBounds.setEmpty(); mOutlineView = null; mHostLeash = null; mLeash = null; } @Nullable SurfaceControl getOutlineLeash() { return mLeash; } void setVisibility(boolean visible) { if (mOutlineView != null) { mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } } void setRootBounds(Rect rootBounds) { if (mViewHost == null || mViewHost.getView() == null) { return; } if (!mRootBounds.equals(rootBounds)) { WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams(); lp.width = rootBounds.width(); lp.height = rootBounds.height(); mViewHost.relayout(lp); mRootBounds.set(rootBounds); drawOutline(); } } void onInsetsChanged(InsetsState insetsState) { if (!mInsetsState.equals(insetsState)) { mInsetsState.set(insetsState); drawOutline(); } } private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) { outBounds.set(rootBounds); final InsetsSource taskBarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); // Only insets the divider bar with task bar when it's expanded so that the rounded corners // will be drawn against task bar. if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) { outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds)); } // Offset the coordinate from screen based to surface based. outBounds.offset(-rootBounds.left, -rootBounds.top); } void drawOutline() { if (mOutlineView == null) { return; } computeOutlineBounds(mRootBounds, mInsetsState, mTempRect); if (mTempRect.equals(mLastOutlineBounds)) { return; } ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams(); lp.leftMargin = mTempRect.left; lp.topMargin = mTempRect.top; lp.width = mTempRect.width(); lp.height = mTempRect.height(); mOutlineView.setLayoutParams(lp); mLastOutlineBounds.set(mTempRect); } } libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.stagesplit; import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT; import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT; import static android.view.RoundedCorner.POSITION_TOP_LEFT; import static android.view.RoundedCorner.POSITION_TOP_RIGHT; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.view.RoundedCorner; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.R; /** View for drawing split outline. */ public class OutlineView extends View { private final Paint mPaint = new Paint(); private final Path mPath = new Path(); private final float[] mRadii = new float[8]; public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth( getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width)); mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null)); } @Override protected void onAttachedToWindow() { // TODO(b/200850654): match the screen corners with the actual display decor. mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT); mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT); mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT); mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT); } private int getCornerRadius(@RoundedCorner.Position int position) { final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position); return roundedCorner == null ? 0 : roundedCorner.getRadius(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (changed) { mPath.reset(); mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW); } } @Override protected void onDraw(Canvas canvas) { canvas.drawPath(mPath, mPaint); } @Override public boolean hasOverlappingRendering() { return false; } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl 0 → 100644 +103 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.stagesplit; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; import android.os.UserHandle; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.window.RemoteTransition; import com.android.wm.shell.stagesplit.ISplitScreenListener; /** * Interface that is exposed to remote callers to manipulate the splitscreen feature. */ interface ISplitScreen { /** * Registers a split screen listener. */ oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1; /** * Unregisters a split screen listener. */ oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2; /** * Hides the side-stage if it is currently visible. */ oneway void setSideStageVisibility(boolean visible) = 3; /** * Removes a task from the side stage. */ oneway void removeFromSideStage(int taskId) = 4; /** * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID * to indicate leaving no top task after leaving split-screen. */ oneway void exitSplitScreen(int toTopTaskId) = 5; /** * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */ oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6; /** * Starts a task in a stage. */ oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7; /** * Starts a shortcut in a stage. */ oneway void startShortcut(String packageName, String shortcutId, int stage, int position, in Bundle options, in UserHandle user) = 8; /** * Starts an activity in a stage. */ oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage, int position, in Bundle options) = 9; /** * Starts tasks simultaneously in one transition. */ oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId, in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10; /** * Version of startTasks using legacy transition system. */ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions, int sideTaskId, in Bundle sideOptions, int sidePosition, in RemoteAnimationAdapter adapter) = 11; /** * Blocking call that notifies and gets additional split-screen targets when entering * recents (for example: the dividerBar). * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled). * @param appTargets apps that will be re-parented to display area */ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, in RemoteAnimationTarget[] appTargets) = 12; }
libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl 0 → 100644 +33 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.stagesplit; /** * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. */ oneway interface ISplitScreenListener { /** * Called when the stage position changes. */ void onStagePositionChanged(int stage, int position); /** * Called when a task changes stages. */ void onTaskStageChanged(int taskId, int stage, boolean visible); } No newline at end of file
libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java 0 → 100644 +104 −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.stagesplit; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import android.annotation.Nullable; import android.graphics.Rect; import android.view.SurfaceSession; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; /** * Main stage for split-screen mode. When split-screen is active all standard activity types launch * on the main stage, except for task that are explicitly pinned to the {@link SideStage}. * @see StageCoordinator */ class MainStage extends StageTaskListener { private static final String TAG = MainStage.class.getSimpleName(); private boolean mIsActive = false; MainStage(ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, SurfaceSession surfaceSession, @Nullable StageTaskUnfoldController stageTaskUnfoldController) { super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, stageTaskUnfoldController); } boolean isActive() { return mIsActive; } void activate(Rect rootBounds, WindowContainerTransaction wct) { if (mIsActive) return; final WindowContainerToken rootToken = mRootTaskInfo.token; wct.setBounds(rootToken, rootBounds) .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW) .setLaunchRoot( rootToken, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES) .reparentTasks( null /* currentParent */, rootToken, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES, true /* onTop */) // Moving the root task to top after the child tasks were re-parented , or the root // task cannot be visible and focused. .reorder(rootToken, true /* onTop */); mIsActive = true; } void deactivate(WindowContainerTransaction wct) { deactivate(wct, false /* toTop */); } void deactivate(WindowContainerTransaction wct, boolean toTop) { if (!mIsActive) return; mIsActive = false; if (mRootTaskInfo == null) return; final WindowContainerToken rootToken = mRootTaskInfo.token; wct.setLaunchRoot( rootToken, null, null) .reparentTasks( rootToken, null /* newParent */, CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, CONTROLLED_ACTIVITY_TYPES, toTop) // We want this re-order to the bottom regardless since we are re-parenting // all its tasks. .reorder(rootToken, false /* onTop */); } void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) { wct.setBounds(mRootTaskInfo.token, bounds) .setWindowingMode(mRootTaskInfo.token, windowingMode); } }
libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java 0 → 100644 +181 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.stagesplit; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Binder; import android.view.IWindow; import android.view.InsetsSource; import android.view.InsetsState; import android.view.LayoutInflater; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowlessWindowManager; import android.widget.FrameLayout; import com.android.wm.shell.R; /** * Handles drawing outline of the bounds of provided root surface. The outline will be drown with * the consideration of display insets like status bar, navigation bar and display cutout. */ class OutlineManager extends WindowlessWindowManager { private static final String WINDOW_NAME = "SplitOutlineLayer"; private final Context mContext; private final Rect mRootBounds = new Rect(); private final Rect mTempRect = new Rect(); private final Rect mLastOutlineBounds = new Rect(); private final InsetsState mInsetsState = new InsetsState(); private final int mExpandedTaskBarHeight; private OutlineView mOutlineView; private SurfaceControlViewHost mViewHost; private SurfaceControl mHostLeash; private SurfaceControl mLeash; OutlineManager(Context context, Configuration configuration) { super(configuration, null /* rootSurface */, null /* hostInputToken */); mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY, null /* options */); mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.taskbar_frame_height); } @Override protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { b.setParent(mHostLeash); } void inflate(SurfaceControl rootLeash, Rect rootBounds) { if (mLeash != null || mViewHost != null) return; mHostLeash = rootLeash; mRootBounds.set(rootBounds); mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext) .inflate(R.layout.split_outline, null); mOutlineView = rootLayout.findViewById(R.id.split_outline); final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); lp.width = mRootBounds.width(); lp.height = mRootBounds.height(); lp.token = new Binder(); lp.setTitle(WINDOW_NAME); lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports // TRUSTED_OVERLAY for windowless window without input channel. mViewHost.setView(rootLayout, lp); mLeash = getSurfaceControl(mViewHost.getWindowToken()); drawOutline(); } void release() { if (mViewHost != null) { mViewHost.release(); mViewHost = null; } mRootBounds.setEmpty(); mLastOutlineBounds.setEmpty(); mOutlineView = null; mHostLeash = null; mLeash = null; } @Nullable SurfaceControl getOutlineLeash() { return mLeash; } void setVisibility(boolean visible) { if (mOutlineView != null) { mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } } void setRootBounds(Rect rootBounds) { if (mViewHost == null || mViewHost.getView() == null) { return; } if (!mRootBounds.equals(rootBounds)) { WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams(); lp.width = rootBounds.width(); lp.height = rootBounds.height(); mViewHost.relayout(lp); mRootBounds.set(rootBounds); drawOutline(); } } void onInsetsChanged(InsetsState insetsState) { if (!mInsetsState.equals(insetsState)) { mInsetsState.set(insetsState); drawOutline(); } } private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) { outBounds.set(rootBounds); final InsetsSource taskBarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); // Only insets the divider bar with task bar when it's expanded so that the rounded corners // will be drawn against task bar. if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) { outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds)); } // Offset the coordinate from screen based to surface based. outBounds.offset(-rootBounds.left, -rootBounds.top); } void drawOutline() { if (mOutlineView == null) { return; } computeOutlineBounds(mRootBounds, mInsetsState, mTempRect); if (mTempRect.equals(mLastOutlineBounds)) { return; } ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams(); lp.leftMargin = mTempRect.left; lp.topMargin = mTempRect.top; lp.width = mTempRect.width(); lp.height = mTempRect.height(); mOutlineView.setLayoutParams(lp); mLastOutlineBounds.set(mTempRect); } }
libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.stagesplit; import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT; import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT; import static android.view.RoundedCorner.POSITION_TOP_LEFT; import static android.view.RoundedCorner.POSITION_TOP_RIGHT; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.view.RoundedCorner; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.R; /** View for drawing split outline. */ public class OutlineView extends View { private final Paint mPaint = new Paint(); private final Path mPath = new Path(); private final float[] mRadii = new float[8]; public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth( getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width)); mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null)); } @Override protected void onAttachedToWindow() { // TODO(b/200850654): match the screen corners with the actual display decor. mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT); mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT); mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT); mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT); } private int getCornerRadius(@RoundedCorner.Position int position) { final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position); return roundedCorner == null ? 0 : roundedCorner.getRadius(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (changed) { mPath.reset(); mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW); } } @Override protected void onDraw(Canvas canvas) { canvas.drawPath(mPath, mPaint); } @Override public boolean hasOverlappingRendering() { return false; } }