Merge "Remove stagesplit backup package" into tm-qpr-dev am: 1e2ddcc2b7
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18157294 Change-Id: I8d730af6811facfac64fc03aadfc7485dcf12908 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
@@ -1,104 +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.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# WM shell sub-modules stagesplit owner
|
|
||||||
chenghsiuchang@google.com
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +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.stagesplit;
|
|
||||||
|
|
||||||
import android.annotation.CallSuper;
|
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.app.ActivityManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.view.InsetsSourceControl;
|
|
||||||
import android.view.InsetsState;
|
|
||||||
import android.view.SurfaceControl;
|
|
||||||
import android.view.SurfaceSession;
|
|
||||||
import android.window.WindowContainerToken;
|
|
||||||
import android.window.WindowContainerTransaction;
|
|
||||||
|
|
||||||
import com.android.wm.shell.ShellTaskOrganizer;
|
|
||||||
import com.android.wm.shell.common.DisplayInsetsController;
|
|
||||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
|
|
||||||
* here. All other task are launch in the {@link MainStage}.
|
|
||||||
*
|
|
||||||
* @see StageCoordinator
|
|
||||||
*/
|
|
||||||
class SideStage extends StageTaskListener implements
|
|
||||||
DisplayInsetsController.OnInsetsChangedListener {
|
|
||||||
private static final String TAG = SideStage.class.getSimpleName();
|
|
||||||
private final Context mContext;
|
|
||||||
private OutlineManager mOutlineManager;
|
|
||||||
|
|
||||||
SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
|
|
||||||
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
|
|
||||||
SurfaceSession surfaceSession,
|
|
||||||
@Nullable StageTaskUnfoldController stageTaskUnfoldController) {
|
|
||||||
super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
|
|
||||||
stageTaskUnfoldController);
|
|
||||||
mContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
|
|
||||||
WindowContainerTransaction wct) {
|
|
||||||
final WindowContainerToken rootToken = mRootTaskInfo.token;
|
|
||||||
wct.setBounds(rootToken, rootBounds)
|
|
||||||
.reparent(task.token, rootToken, true /* onTop*/)
|
|
||||||
// Moving the root task to top after the child tasks were reparented , or the root
|
|
||||||
// task cannot be visible and focused.
|
|
||||||
.reorder(rootToken, true /* onTop */);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
|
|
||||||
// No matter if the root task is empty or not, moving the root to bottom because it no
|
|
||||||
// longer preserves visible child task.
|
|
||||||
wct.reorder(mRootTaskInfo.token, false /* onTop */);
|
|
||||||
if (mChildrenTaskInfo.size() == 0) return false;
|
|
||||||
wct.reparentTasks(
|
|
||||||
mRootTaskInfo.token,
|
|
||||||
null /* newParent */,
|
|
||||||
CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
|
|
||||||
CONTROLLED_ACTIVITY_TYPES,
|
|
||||||
toTop);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
|
|
||||||
final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
|
|
||||||
if (task == null) return false;
|
|
||||||
wct.reparent(task.token, newParent, false /* onTop */);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public SurfaceControl getOutlineLeash() {
|
|
||||||
return mOutlineManager.getOutlineLeash();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
|
|
||||||
super.onTaskAppeared(taskInfo, leash);
|
|
||||||
if (isRootTask(taskInfo)) {
|
|
||||||
mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
|
|
||||||
enableOutline(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
|
|
||||||
super.onTaskInfoChanged(taskInfo);
|
|
||||||
if (isRootTask(taskInfo)) {
|
|
||||||
mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
|
|
||||||
return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void enableOutline(boolean enable) {
|
|
||||||
if (mOutlineManager == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enable) {
|
|
||||||
if (mRootTaskInfo != null) {
|
|
||||||
mOutlineManager.inflate(mRootLeash,
|
|
||||||
mRootTaskInfo.configuration.windowConfiguration.getBounds());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mOutlineManager.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setOutlineVisibility(boolean visible) {
|
|
||||||
mOutlineManager.setVisibility(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void insetsChanged(InsetsState insetsState) {
|
|
||||||
mOutlineManager.onInsetsChanged(insetsState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void insetsControlChanged(InsetsState insetsState,
|
|
||||||
InsetsSourceControl[] activeControls) {
|
|
||||||
insetsChanged(insetsState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +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.stagesplit;
|
|
||||||
|
|
||||||
import android.annotation.IntDef;
|
|
||||||
import android.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.android.wm.shell.common.annotations.ExternalThread;
|
|
||||||
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface to engage split-screen feature.
|
|
||||||
* TODO: Figure out which of these are actually needed outside of the Shell
|
|
||||||
*/
|
|
||||||
@ExternalThread
|
|
||||||
public interface SplitScreen {
|
|
||||||
/**
|
|
||||||
* Stage type isn't specified normally meaning to use what ever the default is.
|
|
||||||
* E.g. exit split-screen and launch the app in fullscreen.
|
|
||||||
*/
|
|
||||||
int STAGE_TYPE_UNDEFINED = -1;
|
|
||||||
/**
|
|
||||||
* The main stage type.
|
|
||||||
* @see MainStage
|
|
||||||
*/
|
|
||||||
int STAGE_TYPE_MAIN = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The side stage type.
|
|
||||||
* @see SideStage
|
|
||||||
*/
|
|
||||||
int STAGE_TYPE_SIDE = 1;
|
|
||||||
|
|
||||||
@IntDef(prefix = { "STAGE_TYPE_" }, value = {
|
|
||||||
STAGE_TYPE_UNDEFINED,
|
|
||||||
STAGE_TYPE_MAIN,
|
|
||||||
STAGE_TYPE_SIDE
|
|
||||||
})
|
|
||||||
@interface StageType {}
|
|
||||||
|
|
||||||
/** Callback interface for listening to changes in a split-screen stage. */
|
|
||||||
interface SplitScreenListener {
|
|
||||||
default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
|
|
||||||
default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
|
|
||||||
default void onSplitVisibilityChanged(boolean visible) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Registers listener that gets split screen callback. */
|
|
||||||
void registerSplitScreenListener(@NonNull SplitScreenListener listener,
|
|
||||||
@NonNull Executor executor);
|
|
||||||
|
|
||||||
/** Unregisters listener that gets split screen callback. */
|
|
||||||
void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a binder that can be passed to an external process to manipulate SplitScreen.
|
|
||||||
*/
|
|
||||||
default ISplitScreen createExternalInterface() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the keyguard occluded state changes.
|
|
||||||
* @param occluded Indicates if the keyguard is now occluded.
|
|
||||||
*/
|
|
||||||
void onKeyguardOccludedChanged(boolean occluded);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the visibility of the keyguard changes.
|
|
||||||
* @param showing Indicates if the keyguard is now visible.
|
|
||||||
*/
|
|
||||||
void onKeyguardVisibilityChanged(boolean showing);
|
|
||||||
|
|
||||||
/** Get a string representation of a stage type */
|
|
||||||
static String stageTypeToString(@StageType int stage) {
|
|
||||||
switch (stage) {
|
|
||||||
case STAGE_TYPE_UNDEFINED: return "UNDEFINED";
|
|
||||||
case STAGE_TYPE_MAIN: return "MAIN";
|
|
||||||
case STAGE_TYPE_SIDE: return "SIDE";
|
|
||||||
default: return "UNKNOWN(" + stage + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,595 +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.stagesplit;
|
|
||||||
|
|
||||||
import static android.view.Display.DEFAULT_DISPLAY;
|
|
||||||
import static android.view.RemoteAnimationTarget.MODE_OPENING;
|
|
||||||
|
|
||||||
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
|
|
||||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
|
|
||||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
|
|
||||||
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
|
|
||||||
|
|
||||||
import android.app.ActivityManager;
|
|
||||||
import android.app.ActivityTaskManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.LauncherApps;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.util.ArrayMap;
|
|
||||||
import android.util.Slog;
|
|
||||||
import android.view.IRemoteAnimationFinishedCallback;
|
|
||||||
import android.view.RemoteAnimationAdapter;
|
|
||||||
import android.view.RemoteAnimationTarget;
|
|
||||||
import android.view.SurfaceControl;
|
|
||||||
import android.view.SurfaceSession;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.window.RemoteTransition;
|
|
||||||
import android.window.WindowContainerTransaction;
|
|
||||||
|
|
||||||
import androidx.annotation.BinderThread;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.android.internal.logging.InstanceId;
|
|
||||||
import com.android.internal.util.FrameworkStatsLog;
|
|
||||||
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
|
|
||||||
import com.android.wm.shell.ShellTaskOrganizer;
|
|
||||||
import com.android.wm.shell.common.DisplayImeController;
|
|
||||||
import com.android.wm.shell.common.DisplayInsetsController;
|
|
||||||
import com.android.wm.shell.common.RemoteCallable;
|
|
||||||
import com.android.wm.shell.common.ShellExecutor;
|
|
||||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
|
||||||
import com.android.wm.shell.common.TransactionPool;
|
|
||||||
import com.android.wm.shell.common.annotations.ExternalThread;
|
|
||||||
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
|
|
||||||
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
|
|
||||||
import com.android.wm.shell.transition.LegacyTransitions;
|
|
||||||
import com.android.wm.shell.transition.Transitions;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import javax.inject.Provider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class manages split-screen multitasking mode and implements the main interface
|
|
||||||
* {@link SplitScreen}.
|
|
||||||
* @see StageCoordinator
|
|
||||||
*/
|
|
||||||
// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
|
|
||||||
public class SplitScreenController implements DragAndDropPolicy.Starter,
|
|
||||||
RemoteCallable<SplitScreenController> {
|
|
||||||
private static final String TAG = SplitScreenController.class.getSimpleName();
|
|
||||||
|
|
||||||
private final ShellTaskOrganizer mTaskOrganizer;
|
|
||||||
private final SyncTransactionQueue mSyncQueue;
|
|
||||||
private final Context mContext;
|
|
||||||
private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
|
|
||||||
private final ShellExecutor mMainExecutor;
|
|
||||||
private final SplitScreenImpl mImpl = new SplitScreenImpl();
|
|
||||||
private final DisplayImeController mDisplayImeController;
|
|
||||||
private final DisplayInsetsController mDisplayInsetsController;
|
|
||||||
private final Transitions mTransitions;
|
|
||||||
private final TransactionPool mTransactionPool;
|
|
||||||
private final SplitscreenEventLogger mLogger;
|
|
||||||
private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
|
|
||||||
|
|
||||||
private StageCoordinator mStageCoordinator;
|
|
||||||
|
|
||||||
public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
|
|
||||||
SyncTransactionQueue syncQueue, Context context,
|
|
||||||
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
|
|
||||||
ShellExecutor mainExecutor, DisplayImeController displayImeController,
|
|
||||||
DisplayInsetsController displayInsetsController,
|
|
||||||
Transitions transitions, TransactionPool transactionPool,
|
|
||||||
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
|
|
||||||
mTaskOrganizer = shellTaskOrganizer;
|
|
||||||
mSyncQueue = syncQueue;
|
|
||||||
mContext = context;
|
|
||||||
mRootTDAOrganizer = rootTDAOrganizer;
|
|
||||||
mMainExecutor = mainExecutor;
|
|
||||||
mDisplayImeController = displayImeController;
|
|
||||||
mDisplayInsetsController = displayInsetsController;
|
|
||||||
mTransitions = transitions;
|
|
||||||
mTransactionPool = transactionPool;
|
|
||||||
mUnfoldControllerProvider = unfoldControllerProvider;
|
|
||||||
mLogger = new SplitscreenEventLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SplitScreen asSplitScreen() {
|
|
||||||
return mImpl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Context getContext() {
|
|
||||||
return mContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ShellExecutor getRemoteCallExecutor() {
|
|
||||||
return mMainExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onOrganizerRegistered() {
|
|
||||||
if (mStageCoordinator == null) {
|
|
||||||
// TODO: Multi-display
|
|
||||||
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
|
|
||||||
mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
|
|
||||||
mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
|
|
||||||
mUnfoldControllerProvider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSplitScreenVisible() {
|
|
||||||
return mStageCoordinator.isSplitScreenVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
|
|
||||||
final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
|
|
||||||
if (task == null) {
|
|
||||||
throw new IllegalArgumentException("Unknown taskId" + taskId);
|
|
||||||
}
|
|
||||||
return moveToSideStage(task, sideStagePosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
|
|
||||||
@SplitPosition int sideStagePosition) {
|
|
||||||
return mStageCoordinator.moveToSideStage(task, sideStagePosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeFromSideStage(int taskId) {
|
|
||||||
return mStageCoordinator.removeFromSideStage(taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSideStageOutline(boolean enable) {
|
|
||||||
mStageCoordinator.setSideStageOutline(enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSideStagePosition(@SplitPosition int sideStagePosition) {
|
|
||||||
mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSideStageVisibility(boolean visible) {
|
|
||||||
mStageCoordinator.setSideStageVisibility(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enterSplitScreen(int taskId, boolean leftOrTop) {
|
|
||||||
moveToSideStage(taskId,
|
|
||||||
leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void exitSplitScreen(int toTopTaskId, int exitReason) {
|
|
||||||
mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onKeyguardOccludedChanged(boolean occluded) {
|
|
||||||
mStageCoordinator.onKeyguardOccludedChanged(occluded);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onKeyguardVisibilityChanged(boolean showing) {
|
|
||||||
mStageCoordinator.onKeyguardVisibilityChanged(showing);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
|
|
||||||
mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
|
|
||||||
mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
|
|
||||||
mStageCoordinator.registerSplitScreenListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
|
|
||||||
mStageCoordinator.unregisterSplitScreenListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
|
|
||||||
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
|
|
||||||
null /* wct */);
|
|
||||||
|
|
||||||
try {
|
|
||||||
ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Slog.e(TAG, "Failed to launch task", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
|
|
||||||
@Nullable Bundle options, UserHandle user) {
|
|
||||||
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
|
|
||||||
null /* wct */);
|
|
||||||
|
|
||||||
try {
|
|
||||||
LauncherApps launcherApps =
|
|
||||||
mContext.getSystemService(LauncherApps.class);
|
|
||||||
launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
|
|
||||||
options, user);
|
|
||||||
} catch (ActivityNotFoundException e) {
|
|
||||||
Slog.e(TAG, "Failed to launch shortcut", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
|
|
||||||
@Nullable Bundle options) {
|
|
||||||
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
|
|
||||||
startIntentLegacy(intent, fillInIntent, position, options);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mStageCoordinator.startIntent(intent, fillInIntent, STAGE_TYPE_UNDEFINED, position, options,
|
|
||||||
null /* remote */);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
|
|
||||||
@SplitPosition int position, @Nullable Bundle options) {
|
|
||||||
LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
|
|
||||||
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
|
|
||||||
IRemoteAnimationFinishedCallback finishedCallback,
|
|
||||||
SurfaceControl.Transaction t) {
|
|
||||||
mStageCoordinator.updateSurfaceBounds(null /* layout */, t,
|
|
||||||
false /* applyResizingOffset */);
|
|
||||||
|
|
||||||
if (apps != null) {
|
|
||||||
for (int i = 0; i < apps.length; ++i) {
|
|
||||||
if (apps[i].mode == MODE_OPENING) {
|
|
||||||
t.show(apps[i].leash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.apply();
|
|
||||||
if (finishedCallback != null) {
|
|
||||||
try {
|
|
||||||
finishedCallback.onAnimationFinished();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Slog.e(TAG, "Error finishing legacy transition: ", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
WindowContainerTransaction wct = new WindowContainerTransaction();
|
|
||||||
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
|
|
||||||
wct.sendPendingIntent(intent, fillInIntent, options);
|
|
||||||
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
|
|
||||||
if (!isSplitScreenVisible()) return null;
|
|
||||||
final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
|
|
||||||
.setContainerLayer()
|
|
||||||
.setName("RecentsAnimationSplitTasks")
|
|
||||||
.setHidden(false)
|
|
||||||
.setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
|
|
||||||
mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
|
|
||||||
SurfaceControl sc = builder.build();
|
|
||||||
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
|
|
||||||
|
|
||||||
// Ensure that we order these in the parent in the right z-order as their previous order
|
|
||||||
Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
|
|
||||||
int layer = 1;
|
|
||||||
for (RemoteAnimationTarget appTarget : apps) {
|
|
||||||
transaction.reparent(appTarget.leash, sc);
|
|
||||||
transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
|
|
||||||
appTarget.screenSpaceBounds.top);
|
|
||||||
transaction.setLayer(appTarget.leash, layer++);
|
|
||||||
}
|
|
||||||
transaction.apply();
|
|
||||||
transaction.close();
|
|
||||||
return new RemoteAnimationTarget[]{
|
|
||||||
mStageCoordinator.getDividerBarLegacyTarget(),
|
|
||||||
mStageCoordinator.getOutlineLegacyTarget()};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets drag info to be logged when splitscreen is entered.
|
|
||||||
*/
|
|
||||||
public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
|
|
||||||
mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dump(@NonNull PrintWriter pw, String prefix) {
|
|
||||||
pw.println(prefix + TAG);
|
|
||||||
if (mStageCoordinator != null) {
|
|
||||||
mStageCoordinator.dump(pw, prefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The interface for calls from outside the Shell, within the host process.
|
|
||||||
*/
|
|
||||||
@ExternalThread
|
|
||||||
private class SplitScreenImpl implements SplitScreen {
|
|
||||||
private ISplitScreenImpl mISplitScreen;
|
|
||||||
private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
|
|
||||||
private final SplitScreenListener mListener = new SplitScreenListener() {
|
|
||||||
@Override
|
|
||||||
public void onStagePositionChanged(int stage, int position) {
|
|
||||||
for (int i = 0; i < mExecutors.size(); i++) {
|
|
||||||
final int index = i;
|
|
||||||
mExecutors.valueAt(index).execute(() -> {
|
|
||||||
mExecutors.keyAt(index).onStagePositionChanged(stage, position);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTaskStageChanged(int taskId, int stage, boolean visible) {
|
|
||||||
for (int i = 0; i < mExecutors.size(); i++) {
|
|
||||||
final int index = i;
|
|
||||||
mExecutors.valueAt(index).execute(() -> {
|
|
||||||
mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSplitVisibilityChanged(boolean visible) {
|
|
||||||
for (int i = 0; i < mExecutors.size(); i++) {
|
|
||||||
final int index = i;
|
|
||||||
mExecutors.valueAt(index).execute(() -> {
|
|
||||||
mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ISplitScreen createExternalInterface() {
|
|
||||||
if (mISplitScreen != null) {
|
|
||||||
mISplitScreen.invalidate();
|
|
||||||
}
|
|
||||||
mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
|
|
||||||
return mISplitScreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onKeyguardOccludedChanged(boolean occluded) {
|
|
||||||
mMainExecutor.execute(() -> {
|
|
||||||
SplitScreenController.this.onKeyguardOccludedChanged(occluded);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
|
|
||||||
if (mExecutors.containsKey(listener)) return;
|
|
||||||
|
|
||||||
mMainExecutor.execute(() -> {
|
|
||||||
if (mExecutors.size() == 0) {
|
|
||||||
SplitScreenController.this.registerSplitScreenListener(mListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
mExecutors.put(listener, executor);
|
|
||||||
});
|
|
||||||
|
|
||||||
executor.execute(() -> {
|
|
||||||
mStageCoordinator.sendStatusToListener(listener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unregisterSplitScreenListener(SplitScreenListener listener) {
|
|
||||||
mMainExecutor.execute(() -> {
|
|
||||||
mExecutors.remove(listener);
|
|
||||||
|
|
||||||
if (mExecutors.size() == 0) {
|
|
||||||
SplitScreenController.this.unregisterSplitScreenListener(mListener);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onKeyguardVisibilityChanged(boolean showing) {
|
|
||||||
mMainExecutor.execute(() -> {
|
|
||||||
SplitScreenController.this.onKeyguardVisibilityChanged(showing);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The interface for calls from outside the host process.
|
|
||||||
*/
|
|
||||||
@BinderThread
|
|
||||||
private static class ISplitScreenImpl extends ISplitScreen.Stub {
|
|
||||||
private SplitScreenController mController;
|
|
||||||
private ISplitScreenListener mListener;
|
|
||||||
private final SplitScreen.SplitScreenListener mSplitScreenListener =
|
|
||||||
new SplitScreen.SplitScreenListener() {
|
|
||||||
@Override
|
|
||||||
public void onStagePositionChanged(int stage, int position) {
|
|
||||||
try {
|
|
||||||
if (mListener != null) {
|
|
||||||
mListener.onStagePositionChanged(stage, position);
|
|
||||||
}
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Slog.e(TAG, "onStagePositionChanged", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTaskStageChanged(int taskId, int stage, boolean visible) {
|
|
||||||
try {
|
|
||||||
if (mListener != null) {
|
|
||||||
mListener.onTaskStageChanged(taskId, stage, visible);
|
|
||||||
}
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Slog.e(TAG, "onTaskStageChanged", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private final IBinder.DeathRecipient mListenerDeathRecipient =
|
|
||||||
new IBinder.DeathRecipient() {
|
|
||||||
@Override
|
|
||||||
@BinderThread
|
|
||||||
public void binderDied() {
|
|
||||||
final SplitScreenController controller = mController;
|
|
||||||
controller.getRemoteCallExecutor().execute(() -> {
|
|
||||||
mListener = null;
|
|
||||||
controller.unregisterSplitScreenListener(mSplitScreenListener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public ISplitScreenImpl(SplitScreenController controller) {
|
|
||||||
mController = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalidates this instance, preventing future calls from updating the controller.
|
|
||||||
*/
|
|
||||||
void invalidate() {
|
|
||||||
mController = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerSplitScreenListener(ISplitScreenListener listener) {
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
|
|
||||||
(controller) -> {
|
|
||||||
if (mListener != null) {
|
|
||||||
mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
|
|
||||||
0 /* flags */);
|
|
||||||
}
|
|
||||||
if (listener != null) {
|
|
||||||
try {
|
|
||||||
listener.asBinder().linkToDeath(mListenerDeathRecipient,
|
|
||||||
0 /* flags */);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Slog.e(TAG, "Failed to link to death");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mListener = listener;
|
|
||||||
controller.registerSplitScreenListener(mSplitScreenListener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unregisterSplitScreenListener(ISplitScreenListener listener) {
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
|
|
||||||
(controller) -> {
|
|
||||||
if (mListener != null) {
|
|
||||||
mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
|
|
||||||
0 /* flags */);
|
|
||||||
}
|
|
||||||
mListener = null;
|
|
||||||
controller.unregisterSplitScreenListener(mSplitScreenListener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void exitSplitScreen(int toTopTaskId) {
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
|
|
||||||
(controller) -> {
|
|
||||||
controller.exitSplitScreen(toTopTaskId,
|
|
||||||
FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
|
|
||||||
(controller) -> {
|
|
||||||
controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSideStageVisibility(boolean visible) {
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
|
|
||||||
(controller) -> {
|
|
||||||
controller.setSideStageVisibility(visible);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeFromSideStage(int taskId) {
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
|
|
||||||
(controller) -> {
|
|
||||||
controller.removeFromSideStage(taskId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "startTask",
|
|
||||||
(controller) -> {
|
|
||||||
controller.startTask(taskId, position, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
|
|
||||||
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
|
|
||||||
RemoteAnimationAdapter adapter) {
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "startTasks",
|
|
||||||
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
|
|
||||||
mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
|
|
||||||
adapter));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
|
|
||||||
int sideTaskId, @Nullable Bundle sideOptions,
|
|
||||||
@SplitPosition int sidePosition,
|
|
||||||
@Nullable RemoteTransition remoteTransition) {
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "startTasks",
|
|
||||||
(controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
|
|
||||||
sideTaskId, sideOptions, sidePosition, remoteTransition));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startShortcut(String packageName, String shortcutId, int stage, int position,
|
|
||||||
@Nullable Bundle options, UserHandle user) {
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "startShortcut",
|
|
||||||
(controller) -> {
|
|
||||||
controller.startShortcut(packageName, shortcutId, position,
|
|
||||||
options, user);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
|
|
||||||
@Nullable Bundle options) {
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "startIntent",
|
|
||||||
(controller) -> {
|
|
||||||
controller.startIntent(intent, fillInIntent, position, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
|
|
||||||
RemoteAnimationTarget[] apps) {
|
|
||||||
final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
|
|
||||||
executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
|
|
||||||
(controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps),
|
|
||||||
true /* blocking */);
|
|
||||||
return out[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.TRANSIT_CHANGE;
|
|
||||||
import static android.view.WindowManager.TRANSIT_CLOSE;
|
|
||||||
import static android.view.WindowManager.TRANSIT_OPEN;
|
|
||||||
import static android.view.WindowManager.TRANSIT_TO_BACK;
|
|
||||||
import static android.view.WindowManager.TRANSIT_TO_FRONT;
|
|
||||||
import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
|
|
||||||
|
|
||||||
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
|
|
||||||
import static com.android.wm.shell.transition.Transitions.isOpeningType;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.annotation.NonNull;
|
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.view.SurfaceControl;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.window.RemoteTransition;
|
|
||||||
import android.window.TransitionInfo;
|
|
||||||
import android.window.WindowContainerToken;
|
|
||||||
import android.window.WindowContainerTransaction;
|
|
||||||
|
|
||||||
import com.android.wm.shell.common.TransactionPool;
|
|
||||||
import com.android.wm.shell.transition.OneShotRemoteHandler;
|
|
||||||
import com.android.wm.shell.transition.Transitions;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/** Manages transition animations for split-screen. */
|
|
||||||
class SplitScreenTransitions {
|
|
||||||
private static final String TAG = "SplitScreenTransitions";
|
|
||||||
|
|
||||||
/** Flag applied to a transition change to identify it as a divider bar for animation. */
|
|
||||||
public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
|
|
||||||
|
|
||||||
private final TransactionPool mTransactionPool;
|
|
||||||
private final Transitions mTransitions;
|
|
||||||
private final Runnable mOnFinish;
|
|
||||||
|
|
||||||
IBinder mPendingDismiss = null;
|
|
||||||
IBinder mPendingEnter = null;
|
|
||||||
|
|
||||||
private IBinder mAnimatingTransition = null;
|
|
||||||
private OneShotRemoteHandler mRemoteHandler = null;
|
|
||||||
|
|
||||||
private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> {
|
|
||||||
if (wct != null || wctCB != null) {
|
|
||||||
throw new UnsupportedOperationException("finish transactions not supported yet.");
|
|
||||||
}
|
|
||||||
onFinish();
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Keeps track of currently running animations */
|
|
||||||
private final ArrayList<Animator> mAnimations = new ArrayList<>();
|
|
||||||
|
|
||||||
private Transitions.TransitionFinishCallback mFinishCallback = null;
|
|
||||||
private SurfaceControl.Transaction mFinishTransaction;
|
|
||||||
|
|
||||||
SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
|
|
||||||
@NonNull Runnable onFinishCallback) {
|
|
||||||
mTransactionPool = pool;
|
|
||||||
mTransitions = transitions;
|
|
||||||
mOnFinish = onFinishCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
|
|
||||||
@NonNull SurfaceControl.Transaction startTransaction,
|
|
||||||
@NonNull SurfaceControl.Transaction finishTransaction,
|
|
||||||
@NonNull Transitions.TransitionFinishCallback finishCallback,
|
|
||||||
@NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
|
|
||||||
mFinishCallback = finishCallback;
|
|
||||||
mAnimatingTransition = transition;
|
|
||||||
if (mRemoteHandler != null) {
|
|
||||||
mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
|
|
||||||
mRemoteFinishCB);
|
|
||||||
mRemoteHandler = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
|
|
||||||
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
|
|
||||||
@NonNull WindowContainerToken sideRoot) {
|
|
||||||
mFinishTransaction = mTransactionPool.acquire();
|
|
||||||
|
|
||||||
// Play some place-holder fade animations
|
|
||||||
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
|
||||||
final TransitionInfo.Change change = info.getChanges().get(i);
|
|
||||||
final SurfaceControl leash = change.getLeash();
|
|
||||||
final int mode = info.getChanges().get(i).getMode();
|
|
||||||
|
|
||||||
if (mode == TRANSIT_CHANGE) {
|
|
||||||
if (change.getParent() != null) {
|
|
||||||
// This is probably reparented, so we want the parent to be immediately visible
|
|
||||||
final TransitionInfo.Change parentChange = info.getChange(change.getParent());
|
|
||||||
t.show(parentChange.getLeash());
|
|
||||||
t.setAlpha(parentChange.getLeash(), 1.f);
|
|
||||||
// and then animate this layer outside the parent (since, for example, this is
|
|
||||||
// the home task animating from fullscreen to part-screen).
|
|
||||||
t.reparent(leash, info.getRootLeash());
|
|
||||||
t.setLayer(leash, info.getChanges().size() - i);
|
|
||||||
// build the finish reparent/reposition
|
|
||||||
mFinishTransaction.reparent(leash, parentChange.getLeash());
|
|
||||||
mFinishTransaction.setPosition(leash,
|
|
||||||
change.getEndRelOffset().x, change.getEndRelOffset().y);
|
|
||||||
}
|
|
||||||
// TODO(shell-transitions): screenshot here
|
|
||||||
final Rect startBounds = new Rect(change.getStartAbsBounds());
|
|
||||||
final Rect endBounds = new Rect(change.getEndAbsBounds());
|
|
||||||
startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
|
|
||||||
endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
|
|
||||||
startExampleResizeAnimation(leash, startBounds, endBounds);
|
|
||||||
}
|
|
||||||
if (change.getParent() != null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
|
|
||||||
|| sideRoot.equals(change.getContainer()))) {
|
|
||||||
t.setWindowCrop(leash, change.getStartAbsBounds().width(),
|
|
||||||
change.getStartAbsBounds().height());
|
|
||||||
}
|
|
||||||
boolean isOpening = isOpeningType(info.getType());
|
|
||||||
if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
|
|
||||||
// fade in
|
|
||||||
startExampleAnimation(leash, true /* show */);
|
|
||||||
} else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
|
|
||||||
// fade out
|
|
||||||
if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
|
|
||||||
// Dismissing via snap-to-top/bottom means that the dismissed task is already
|
|
||||||
// not-visible (usually cropped to oblivion) so immediately set its alpha to 0
|
|
||||||
// and don't animate it so it doesn't pop-in when reparented.
|
|
||||||
t.setAlpha(leash, 0.f);
|
|
||||||
} else {
|
|
||||||
startExampleAnimation(leash, false /* show */);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.apply();
|
|
||||||
onFinish();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Starts a transition to enter split with a remote transition animator. */
|
|
||||||
IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
|
|
||||||
@NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
|
|
||||||
@NonNull Transitions.TransitionHandler handler) {
|
|
||||||
if (remoteTransition != null) {
|
|
||||||
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
|
|
||||||
mRemoteHandler = new OneShotRemoteHandler(
|
|
||||||
mTransitions.getMainExecutor(), remoteTransition);
|
|
||||||
}
|
|
||||||
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
|
|
||||||
mPendingEnter = transition;
|
|
||||||
if (mRemoteHandler != null) {
|
|
||||||
mRemoteHandler.setTransition(transition);
|
|
||||||
}
|
|
||||||
return transition;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Starts a transition for dismissing split after dragging the divider to a screen edge */
|
|
||||||
IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct,
|
|
||||||
@NonNull Transitions.TransitionHandler handler) {
|
|
||||||
final IBinder transition = mTransitions.startTransition(
|
|
||||||
TRANSIT_SPLIT_DISMISS_SNAP, wct, handler);
|
|
||||||
mPendingDismiss = transition;
|
|
||||||
return transition;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onFinish() {
|
|
||||||
if (!mAnimations.isEmpty()) return;
|
|
||||||
mOnFinish.run();
|
|
||||||
if (mFinishTransaction != null) {
|
|
||||||
mFinishTransaction.apply();
|
|
||||||
mTransactionPool.release(mFinishTransaction);
|
|
||||||
mFinishTransaction = null;
|
|
||||||
}
|
|
||||||
mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
|
|
||||||
mFinishCallback = null;
|
|
||||||
if (mAnimatingTransition == mPendingEnter) {
|
|
||||||
mPendingEnter = null;
|
|
||||||
}
|
|
||||||
if (mAnimatingTransition == mPendingDismiss) {
|
|
||||||
mPendingDismiss = null;
|
|
||||||
}
|
|
||||||
mAnimatingTransition = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(shell-transitions): real animations
|
|
||||||
private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
|
|
||||||
final float end = show ? 1.f : 0.f;
|
|
||||||
final float start = 1.f - end;
|
|
||||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
|
||||||
final ValueAnimator va = ValueAnimator.ofFloat(start, end);
|
|
||||||
va.setDuration(500);
|
|
||||||
va.addUpdateListener(animation -> {
|
|
||||||
float fraction = animation.getAnimatedFraction();
|
|
||||||
transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
|
|
||||||
transaction.apply();
|
|
||||||
});
|
|
||||||
final Runnable finisher = () -> {
|
|
||||||
transaction.setAlpha(leash, end);
|
|
||||||
transaction.apply();
|
|
||||||
mTransactionPool.release(transaction);
|
|
||||||
mTransitions.getMainExecutor().execute(() -> {
|
|
||||||
mAnimations.remove(va);
|
|
||||||
onFinish();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
va.addListener(new Animator.AnimatorListener() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationStart(Animator animation) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
|
||||||
finisher.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationCancel(Animator animation) {
|
|
||||||
finisher.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationRepeat(Animator animation) { }
|
|
||||||
});
|
|
||||||
mAnimations.add(va);
|
|
||||||
mTransitions.getAnimExecutor().execute(va::start);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(shell-transitions): real animations
|
|
||||||
private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
|
|
||||||
@NonNull Rect startBounds, @NonNull Rect endBounds) {
|
|
||||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
|
||||||
final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
|
|
||||||
va.setDuration(500);
|
|
||||||
va.addUpdateListener(animation -> {
|
|
||||||
float fraction = animation.getAnimatedFraction();
|
|
||||||
transaction.setWindowCrop(leash,
|
|
||||||
(int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
|
|
||||||
(int) (startBounds.height() * (1.f - fraction)
|
|
||||||
+ endBounds.height() * fraction));
|
|
||||||
transaction.setPosition(leash,
|
|
||||||
startBounds.left * (1.f - fraction) + endBounds.left * fraction,
|
|
||||||
startBounds.top * (1.f - fraction) + endBounds.top * fraction);
|
|
||||||
transaction.apply();
|
|
||||||
});
|
|
||||||
final Runnable finisher = () -> {
|
|
||||||
transaction.setWindowCrop(leash, 0, 0);
|
|
||||||
transaction.setPosition(leash, endBounds.left, endBounds.top);
|
|
||||||
transaction.apply();
|
|
||||||
mTransactionPool.release(transaction);
|
|
||||||
mTransitions.getMainExecutor().execute(() -> {
|
|
||||||
mAnimations.remove(va);
|
|
||||||
onFinish();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
va.addListener(new AnimatorListenerAdapter() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
|
||||||
finisher.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationCancel(Animator animation) {
|
|
||||||
finisher.run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mAnimations.add(va);
|
|
||||||
mTransitions.getAnimExecutor().execute(va::start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,324 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
|
|
||||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
|
|
||||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
|
|
||||||
|
|
||||||
import com.android.internal.logging.InstanceId;
|
|
||||||
import com.android.internal.logging.InstanceIdSequence;
|
|
||||||
import com.android.internal.util.FrameworkStatsLog;
|
|
||||||
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent
|
|
||||||
*/
|
|
||||||
public class SplitscreenEventLogger {
|
|
||||||
|
|
||||||
// Used to generate instance ids for this drag if one is not provided
|
|
||||||
private final InstanceIdSequence mIdSequence;
|
|
||||||
|
|
||||||
// The instance id for the current splitscreen session (from start to end)
|
|
||||||
private InstanceId mLoggerSessionId;
|
|
||||||
|
|
||||||
// Drag info
|
|
||||||
private @SplitPosition int mDragEnterPosition;
|
|
||||||
private InstanceId mDragEnterSessionId;
|
|
||||||
|
|
||||||
// For deduping async events
|
|
||||||
private int mLastMainStagePosition = -1;
|
|
||||||
private int mLastMainStageUid = -1;
|
|
||||||
private int mLastSideStagePosition = -1;
|
|
||||||
private int mLastSideStageUid = -1;
|
|
||||||
private float mLastSplitRatio = -1f;
|
|
||||||
|
|
||||||
public SplitscreenEventLogger() {
|
|
||||||
mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return whether a splitscreen session has started.
|
|
||||||
*/
|
|
||||||
public boolean hasStartedSession() {
|
|
||||||
return mLoggerSessionId != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* May be called before logEnter() to indicate that the session was started from a drag.
|
|
||||||
*/
|
|
||||||
public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
|
|
||||||
mDragEnterPosition = position;
|
|
||||||
mDragEnterSessionId = dragSessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs when the user enters splitscreen.
|
|
||||||
*/
|
|
||||||
public void logEnter(float splitRatio,
|
|
||||||
@SplitPosition int mainStagePosition, int mainStageUid,
|
|
||||||
@SplitPosition int sideStagePosition, int sideStageUid,
|
|
||||||
boolean isLandscape) {
|
|
||||||
mLoggerSessionId = mIdSequence.newInstanceId();
|
|
||||||
int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
|
|
||||||
? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
|
|
||||||
: SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
|
|
||||||
updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
|
|
||||||
mainStageUid);
|
|
||||||
updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
|
|
||||||
sideStageUid);
|
|
||||||
updateSplitRatioState(splitRatio);
|
|
||||||
FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
|
|
||||||
FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER,
|
|
||||||
enterReason,
|
|
||||||
0 /* exitReason */,
|
|
||||||
splitRatio,
|
|
||||||
mLastMainStagePosition,
|
|
||||||
mLastMainStageUid,
|
|
||||||
mLastSideStagePosition,
|
|
||||||
mLastSideStageUid,
|
|
||||||
mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
|
|
||||||
mLoggerSessionId.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs when the user exits splitscreen. Only one of the main or side stages should be
|
|
||||||
* specified to indicate which position was focused as a part of exiting (both can be unset).
|
|
||||||
*/
|
|
||||||
public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid,
|
|
||||||
@SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
|
|
||||||
if (mLoggerSessionId == null) {
|
|
||||||
// Ignore changes until we've started logging the session
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((mainStagePosition != SPLIT_POSITION_UNDEFINED
|
|
||||||
&& sideStagePosition != SPLIT_POSITION_UNDEFINED)
|
|
||||||
|| (mainStageUid != 0 && sideStageUid != 0)) {
|
|
||||||
throw new IllegalArgumentException("Only main or side stage should be set");
|
|
||||||
}
|
|
||||||
FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
|
|
||||||
FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT,
|
|
||||||
0 /* enterReason */,
|
|
||||||
exitReason,
|
|
||||||
0f /* splitRatio */,
|
|
||||||
getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
|
|
||||||
mainStageUid,
|
|
||||||
getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
|
|
||||||
sideStageUid,
|
|
||||||
0 /* dragInstanceId */,
|
|
||||||
mLoggerSessionId.getId());
|
|
||||||
|
|
||||||
// Reset states
|
|
||||||
mLoggerSessionId = null;
|
|
||||||
mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
|
|
||||||
mDragEnterSessionId = null;
|
|
||||||
mLastMainStagePosition = -1;
|
|
||||||
mLastMainStageUid = -1;
|
|
||||||
mLastSideStagePosition = -1;
|
|
||||||
mLastSideStageUid = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs when an app in the main stage changes.
|
|
||||||
*/
|
|
||||||
public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid,
|
|
||||||
boolean isLandscape) {
|
|
||||||
if (mLoggerSessionId == null) {
|
|
||||||
// Ignore changes until we've started logging the session
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition,
|
|
||||||
isLandscape), mainStageUid)) {
|
|
||||||
// Ignore if there are no user perceived changes
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
|
|
||||||
FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
|
|
||||||
0 /* enterReason */,
|
|
||||||
0 /* exitReason */,
|
|
||||||
0f /* splitRatio */,
|
|
||||||
mLastMainStagePosition,
|
|
||||||
mLastMainStageUid,
|
|
||||||
0 /* sideStagePosition */,
|
|
||||||
0 /* sideStageUid */,
|
|
||||||
0 /* dragInstanceId */,
|
|
||||||
mLoggerSessionId.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs when an app in the side stage changes.
|
|
||||||
*/
|
|
||||||
public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid,
|
|
||||||
boolean isLandscape) {
|
|
||||||
if (mLoggerSessionId == null) {
|
|
||||||
// Ignore changes until we've started logging the session
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition,
|
|
||||||
isLandscape), sideStageUid)) {
|
|
||||||
// Ignore if there are no user perceived changes
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
|
|
||||||
FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
|
|
||||||
0 /* enterReason */,
|
|
||||||
0 /* exitReason */,
|
|
||||||
0f /* splitRatio */,
|
|
||||||
0 /* mainStagePosition */,
|
|
||||||
0 /* mainStageUid */,
|
|
||||||
mLastSideStagePosition,
|
|
||||||
mLastSideStageUid,
|
|
||||||
0 /* dragInstanceId */,
|
|
||||||
mLoggerSessionId.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs when the splitscreen ratio changes.
|
|
||||||
*/
|
|
||||||
public void logResize(float splitRatio) {
|
|
||||||
if (mLoggerSessionId == null) {
|
|
||||||
// Ignore changes until we've started logging the session
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (splitRatio <= 0f || splitRatio >= 1f) {
|
|
||||||
// Don't bother reporting resizes that end up dismissing the split, that will be logged
|
|
||||||
// via the exit event
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!updateSplitRatioState(splitRatio)) {
|
|
||||||
// Ignore if there are no user perceived changes
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
|
|
||||||
FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE,
|
|
||||||
0 /* enterReason */,
|
|
||||||
0 /* exitReason */,
|
|
||||||
mLastSplitRatio,
|
|
||||||
0 /* mainStagePosition */, 0 /* mainStageUid */,
|
|
||||||
0 /* sideStagePosition */, 0 /* sideStageUid */,
|
|
||||||
0 /* dragInstanceId */,
|
|
||||||
mLoggerSessionId.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs when the apps in splitscreen are swapped.
|
|
||||||
*/
|
|
||||||
public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid,
|
|
||||||
@SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
|
|
||||||
if (mLoggerSessionId == null) {
|
|
||||||
// Ignore changes until we've started logging the session
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
|
|
||||||
mainStageUid);
|
|
||||||
updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
|
|
||||||
sideStageUid);
|
|
||||||
FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
|
|
||||||
FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP,
|
|
||||||
0 /* enterReason */,
|
|
||||||
0 /* exitReason */,
|
|
||||||
0f /* splitRatio */,
|
|
||||||
mLastMainStagePosition,
|
|
||||||
mLastMainStageUid,
|
|
||||||
mLastSideStagePosition,
|
|
||||||
mLastSideStageUid,
|
|
||||||
0 /* dragInstanceId */,
|
|
||||||
mLoggerSessionId.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean updateMainStageState(int mainStagePosition, int mainStageUid) {
|
|
||||||
boolean changed = (mLastMainStagePosition != mainStagePosition)
|
|
||||||
|| (mLastMainStageUid != mainStageUid);
|
|
||||||
if (!changed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mLastMainStagePosition = mainStagePosition;
|
|
||||||
mLastMainStageUid = mainStageUid;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean updateSideStageState(int sideStagePosition, int sideStageUid) {
|
|
||||||
boolean changed = (mLastSideStagePosition != sideStagePosition)
|
|
||||||
|| (mLastSideStageUid != sideStageUid);
|
|
||||||
if (!changed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mLastSideStagePosition = sideStagePosition;
|
|
||||||
mLastSideStageUid = sideStageUid;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean updateSplitRatioState(float splitRatio) {
|
|
||||||
boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0;
|
|
||||||
if (!changed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mLastSplitRatio = splitRatio;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDragEnterReasonFromSplitPosition(@SplitPosition int position,
|
|
||||||
boolean isLandscape) {
|
|
||||||
if (isLandscape) {
|
|
||||||
return position == SPLIT_POSITION_TOP_OR_LEFT
|
|
||||||
? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT
|
|
||||||
: FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT;
|
|
||||||
} else {
|
|
||||||
return position == SPLIT_POSITION_TOP_OR_LEFT
|
|
||||||
? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP
|
|
||||||
: FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getMainStagePositionFromSplitPosition(@SplitPosition int position,
|
|
||||||
boolean isLandscape) {
|
|
||||||
if (position == SPLIT_POSITION_UNDEFINED) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (isLandscape) {
|
|
||||||
return position == SPLIT_POSITION_TOP_OR_LEFT
|
|
||||||
? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT
|
|
||||||
: FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT;
|
|
||||||
} else {
|
|
||||||
return position == SPLIT_POSITION_TOP_OR_LEFT
|
|
||||||
? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP
|
|
||||||
: FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getSideStagePositionFromSplitPosition(@SplitPosition int position,
|
|
||||||
boolean isLandscape) {
|
|
||||||
if (position == SPLIT_POSITION_UNDEFINED) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (isLandscape) {
|
|
||||||
return position == SPLIT_POSITION_TOP_OR_LEFT
|
|
||||||
? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT
|
|
||||||
: FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT;
|
|
||||||
} else {
|
|
||||||
return position == SPLIT_POSITION_TOP_OR_LEFT
|
|
||||||
? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP
|
|
||||||
: FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,298 +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.stagesplit;
|
|
||||||
|
|
||||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
|
|
||||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
|
||||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
|
||||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
|
||||||
|
|
||||||
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
|
|
||||||
|
|
||||||
import android.annotation.CallSuper;
|
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.app.ActivityManager;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.util.SparseArray;
|
|
||||||
import android.view.SurfaceControl;
|
|
||||||
import android.view.SurfaceSession;
|
|
||||||
import android.window.WindowContainerTransaction;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.android.wm.shell.ShellTaskOrganizer;
|
|
||||||
import com.android.wm.shell.common.SurfaceUtils;
|
|
||||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class that handle common task org. related for split-screen stages.
|
|
||||||
* Note that this class and its sub-class do not directly perform hierarchy operations.
|
|
||||||
* They only serve to hold a collection of tasks and provide APIs like
|
|
||||||
* {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
|
|
||||||
* to perform operations in-sync with other containers.
|
|
||||||
*
|
|
||||||
* @see StageCoordinator
|
|
||||||
*/
|
|
||||||
class StageTaskListener implements ShellTaskOrganizer.TaskListener {
|
|
||||||
private static final String TAG = StageTaskListener.class.getSimpleName();
|
|
||||||
|
|
||||||
protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
|
|
||||||
protected static final int[] CONTROLLED_WINDOWING_MODES =
|
|
||||||
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
|
|
||||||
protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
|
|
||||||
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
|
|
||||||
|
|
||||||
/** Callback interface for listening to changes in a split-screen stage. */
|
|
||||||
public interface StageListenerCallbacks {
|
|
||||||
void onRootTaskAppeared();
|
|
||||||
|
|
||||||
void onStatusChanged(boolean visible, boolean hasChildren);
|
|
||||||
|
|
||||||
void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
|
|
||||||
|
|
||||||
void onRootTaskVanished();
|
|
||||||
void onNoLongerSupportMultiWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final StageListenerCallbacks mCallbacks;
|
|
||||||
private final SurfaceSession mSurfaceSession;
|
|
||||||
protected final SyncTransactionQueue mSyncQueue;
|
|
||||||
|
|
||||||
protected ActivityManager.RunningTaskInfo mRootTaskInfo;
|
|
||||||
protected SurfaceControl mRootLeash;
|
|
||||||
protected SurfaceControl mDimLayer;
|
|
||||||
protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
|
|
||||||
private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
|
|
||||||
|
|
||||||
private final StageTaskUnfoldController mStageTaskUnfoldController;
|
|
||||||
|
|
||||||
StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
|
|
||||||
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
|
|
||||||
SurfaceSession surfaceSession,
|
|
||||||
@Nullable StageTaskUnfoldController stageTaskUnfoldController) {
|
|
||||||
mCallbacks = callbacks;
|
|
||||||
mSyncQueue = syncQueue;
|
|
||||||
mSurfaceSession = surfaceSession;
|
|
||||||
mStageTaskUnfoldController = stageTaskUnfoldController;
|
|
||||||
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
int getChildCount() {
|
|
||||||
return mChildrenTaskInfo.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean containsTask(int taskId) {
|
|
||||||
return mChildrenTaskInfo.contains(taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the top activity uid for the top child task.
|
|
||||||
*/
|
|
||||||
int getTopChildTaskUid() {
|
|
||||||
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
|
|
||||||
final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
|
|
||||||
if (info.topActivityInfo == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return info.topActivityInfo.applicationInfo.uid;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return {@code true} if this listener contains the currently focused task. */
|
|
||||||
boolean isFocused() {
|
|
||||||
if (mRootTaskInfo == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mRootTaskInfo.isFocused) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
|
|
||||||
if (mChildrenTaskInfo.valueAt(i).isFocused) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
|
|
||||||
if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
|
|
||||||
mRootLeash = leash;
|
|
||||||
mRootTaskInfo = taskInfo;
|
|
||||||
mCallbacks.onRootTaskAppeared();
|
|
||||||
sendStatusChanged();
|
|
||||||
mSyncQueue.runInSync(t -> {
|
|
||||||
t.hide(mRootLeash);
|
|
||||||
mDimLayer =
|
|
||||||
SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
|
|
||||||
});
|
|
||||||
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
|
|
||||||
final int taskId = taskInfo.taskId;
|
|
||||||
mChildrenLeashes.put(taskId, leash);
|
|
||||||
mChildrenTaskInfo.put(taskId, taskInfo);
|
|
||||||
updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
|
|
||||||
mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
|
|
||||||
if (ENABLE_SHELL_TRANSITIONS) {
|
|
||||||
// Status is managed/synchronized by the transition lifecycle.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendStatusChanged();
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
|
|
||||||
+ "\n mRootTaskInfo: " + mRootTaskInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mStageTaskUnfoldController != null) {
|
|
||||||
mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
|
|
||||||
if (!taskInfo.supportsMultiWindow) {
|
|
||||||
// Leave split screen if the task no longer supports multi window.
|
|
||||||
mCallbacks.onNoLongerSupportMultiWindow();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mRootTaskInfo.taskId == taskInfo.taskId) {
|
|
||||||
mRootTaskInfo = taskInfo;
|
|
||||||
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
|
|
||||||
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
|
|
||||||
mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
|
|
||||||
taskInfo.isVisible);
|
|
||||||
if (!ENABLE_SHELL_TRANSITIONS) {
|
|
||||||
updateChildTaskSurface(
|
|
||||||
taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
|
|
||||||
+ "\n mRootTaskInfo: " + mRootTaskInfo);
|
|
||||||
}
|
|
||||||
if (ENABLE_SHELL_TRANSITIONS) {
|
|
||||||
// Status is managed/synchronized by the transition lifecycle.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendStatusChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
|
|
||||||
final int taskId = taskInfo.taskId;
|
|
||||||
if (mRootTaskInfo.taskId == taskId) {
|
|
||||||
mCallbacks.onRootTaskVanished();
|
|
||||||
mSyncQueue.runInSync(t -> t.remove(mDimLayer));
|
|
||||||
mRootTaskInfo = null;
|
|
||||||
} else if (mChildrenTaskInfo.contains(taskId)) {
|
|
||||||
mChildrenTaskInfo.remove(taskId);
|
|
||||||
mChildrenLeashes.remove(taskId);
|
|
||||||
mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
|
|
||||||
if (ENABLE_SHELL_TRANSITIONS) {
|
|
||||||
// Status is managed/synchronized by the transition lifecycle.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendStatusChanged();
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
|
|
||||||
+ "\n mRootTaskInfo: " + mRootTaskInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mStageTaskUnfoldController != null) {
|
|
||||||
mStageTaskUnfoldController.onTaskVanished(taskInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
|
|
||||||
b.setParent(findTaskSurface(taskId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
|
|
||||||
SurfaceControl.Transaction t) {
|
|
||||||
t.reparent(sc, findTaskSurface(taskId));
|
|
||||||
}
|
|
||||||
|
|
||||||
private SurfaceControl findTaskSurface(int taskId) {
|
|
||||||
if (mRootTaskInfo.taskId == taskId) {
|
|
||||||
return mRootLeash;
|
|
||||||
} else if (mChildrenLeashes.contains(taskId)) {
|
|
||||||
return mChildrenLeashes.get(taskId);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBounds(Rect bounds, WindowContainerTransaction wct) {
|
|
||||||
wct.setBounds(mRootTaskInfo.token, bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
|
|
||||||
if (!containsTask(taskId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setVisibility(boolean visible, WindowContainerTransaction wct) {
|
|
||||||
wct.reorder(mRootTaskInfo.token, visible /* onTop */);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
|
|
||||||
@SplitScreen.StageType int stage) {
|
|
||||||
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
|
|
||||||
int taskId = mChildrenTaskInfo.keyAt(i);
|
|
||||||
listener.onTaskStageChanged(taskId, stage,
|
|
||||||
mChildrenTaskInfo.get(taskId).isVisible);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
|
|
||||||
SurfaceControl leash, boolean firstAppeared) {
|
|
||||||
final Point taskPositionInParent = taskInfo.positionInParent;
|
|
||||||
mSyncQueue.runInSync(t -> {
|
|
||||||
t.setWindowCrop(leash, null);
|
|
||||||
t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
|
|
||||||
if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
|
|
||||||
t.setAlpha(leash, 1f);
|
|
||||||
t.setMatrix(leash, 1, 0, 0, 1);
|
|
||||||
t.show(leash);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendStatusChanged() {
|
|
||||||
mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
public void dump(@NonNull PrintWriter pw, String prefix) {
|
|
||||||
final String innerPrefix = prefix + " ";
|
|
||||||
final String childPrefix = innerPrefix + " ";
|
|
||||||
pw.println(prefix + this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.Display.DEFAULT_DISPLAY;
|
|
||||||
|
|
||||||
import android.animation.RectEvaluator;
|
|
||||||
import android.animation.TypeEvaluator;
|
|
||||||
import android.annotation.NonNull;
|
|
||||||
import android.app.ActivityManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.util.SparseArray;
|
|
||||||
import android.view.InsetsSource;
|
|
||||||
import android.view.InsetsState;
|
|
||||||
import android.view.SurfaceControl;
|
|
||||||
|
|
||||||
import com.android.internal.policy.ScreenDecorationsUtils;
|
|
||||||
import com.android.wm.shell.common.DisplayInsetsController;
|
|
||||||
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
|
|
||||||
import com.android.wm.shell.common.TransactionPool;
|
|
||||||
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
|
|
||||||
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
|
|
||||||
import com.android.wm.shell.unfold.UnfoldBackgroundController;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controls transformations of the split screen task surfaces in response
|
|
||||||
* to the unfolding/folding action on foldable devices
|
|
||||||
*/
|
|
||||||
public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
|
|
||||||
|
|
||||||
private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
|
|
||||||
private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
|
|
||||||
|
|
||||||
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
|
|
||||||
private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
|
|
||||||
private final DisplayInsetsController mDisplayInsetsController;
|
|
||||||
private final UnfoldBackgroundController mBackgroundController;
|
|
||||||
private final Executor mExecutor;
|
|
||||||
private final int mExpandedTaskBarHeight;
|
|
||||||
private final float mWindowCornerRadiusPx;
|
|
||||||
private final Rect mStageBounds = new Rect();
|
|
||||||
private final TransactionPool mTransactionPool;
|
|
||||||
|
|
||||||
private InsetsSource mTaskbarInsetsSource;
|
|
||||||
private boolean mBothStagesVisible;
|
|
||||||
|
|
||||||
public StageTaskUnfoldController(@NonNull Context context,
|
|
||||||
@NonNull TransactionPool transactionPool,
|
|
||||||
@NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
|
|
||||||
@NonNull DisplayInsetsController displayInsetsController,
|
|
||||||
@NonNull UnfoldBackgroundController backgroundController,
|
|
||||||
@NonNull Executor executor) {
|
|
||||||
mUnfoldProgressProvider = unfoldProgressProvider;
|
|
||||||
mTransactionPool = transactionPool;
|
|
||||||
mExecutor = executor;
|
|
||||||
mBackgroundController = backgroundController;
|
|
||||||
mDisplayInsetsController = displayInsetsController;
|
|
||||||
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
|
|
||||||
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
|
|
||||||
com.android.internal.R.dimen.taskbar_frame_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the controller, starts listening for the external events
|
|
||||||
*/
|
|
||||||
public void init() {
|
|
||||||
mUnfoldProgressProvider.addListener(mExecutor, this);
|
|
||||||
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void insetsChanged(InsetsState insetsState) {
|
|
||||||
mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
|
|
||||||
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
|
|
||||||
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
|
|
||||||
context.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when split screen task appeared
|
|
||||||
* @param taskInfo info for the appeared task
|
|
||||||
* @param leash surface leash for the appeared task
|
|
||||||
*/
|
|
||||||
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
|
|
||||||
AnimationContext context = new AnimationContext(leash);
|
|
||||||
mAnimationContextByTaskId.put(taskInfo.taskId, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a split screen task vanished
|
|
||||||
* @param taskInfo info for the vanished task
|
|
||||||
*/
|
|
||||||
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
|
|
||||||
AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
|
|
||||||
if (context != null) {
|
|
||||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
|
||||||
resetSurface(transaction, context);
|
|
||||||
transaction.apply();
|
|
||||||
mTransactionPool.release(transaction);
|
|
||||||
}
|
|
||||||
mAnimationContextByTaskId.remove(taskInfo.taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStateChangeProgress(float progress) {
|
|
||||||
if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
|
|
||||||
|
|
||||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
|
||||||
mBackgroundController.ensureBackground(transaction);
|
|
||||||
|
|
||||||
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
|
|
||||||
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
|
|
||||||
|
|
||||||
context.mCurrentCropRect.set(RECT_EVALUATOR
|
|
||||||
.evaluate(progress, context.mStartCropRect, context.mEndCropRect));
|
|
||||||
|
|
||||||
transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
|
|
||||||
.setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.apply();
|
|
||||||
|
|
||||||
mTransactionPool.release(transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStateChangeFinished() {
|
|
||||||
resetTransformations();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when split screen visibility changes
|
|
||||||
* @param bothStagesVisible true if both stages of the split screen are visible
|
|
||||||
*/
|
|
||||||
public void onSplitVisibilityChanged(boolean bothStagesVisible) {
|
|
||||||
mBothStagesVisible = bothStagesVisible;
|
|
||||||
if (!bothStagesVisible) {
|
|
||||||
resetTransformations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when split screen stage bounds changed
|
|
||||||
* @param bounds new bounds for this stage
|
|
||||||
*/
|
|
||||||
public void onLayoutChanged(Rect bounds) {
|
|
||||||
mStageBounds.set(bounds);
|
|
||||||
|
|
||||||
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
|
|
||||||
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
|
|
||||||
context.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetTransformations() {
|
|
||||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
|
||||||
|
|
||||||
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
|
|
||||||
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
|
|
||||||
resetSurface(transaction, context);
|
|
||||||
}
|
|
||||||
mBackgroundController.removeBackground(transaction);
|
|
||||||
transaction.apply();
|
|
||||||
|
|
||||||
mTransactionPool.release(transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
|
|
||||||
transaction
|
|
||||||
.setWindowCrop(context.mLeash, null)
|
|
||||||
.setCornerRadius(context.mLeash, 0.0F);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AnimationContext {
|
|
||||||
final SurfaceControl mLeash;
|
|
||||||
final Rect mStartCropRect = new Rect();
|
|
||||||
final Rect mEndCropRect = new Rect();
|
|
||||||
final Rect mCurrentCropRect = new Rect();
|
|
||||||
|
|
||||||
private AnimationContext(SurfaceControl leash) {
|
|
||||||
this.mLeash = leash;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update() {
|
|
||||||
mStartCropRect.set(mStageBounds);
|
|
||||||
|
|
||||||
if (mTaskbarInsetsSource != null) {
|
|
||||||
// Only insets the cropping window with taskbar when taskbar is expanded
|
|
||||||
if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
|
|
||||||
mStartCropRect.inset(mTaskbarInsetsSource
|
|
||||||
.calculateVisibleInsets(mStartCropRect));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Offset to surface coordinates as layout bounds are in screen coordinates
|
|
||||||
mStartCropRect.offsetTo(0, 0);
|
|
||||||
|
|
||||||
mEndCropRect.set(mStartCropRect);
|
|
||||||
|
|
||||||
int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
|
|
||||||
int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
|
|
||||||
mStartCropRect.inset(margin, margin, margin, margin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user