Merge "Add logging for splitscreen" into sc-v2-dev

This commit is contained in:
Winson Chung
2021-08-18 16:11:16 +00:00
committed by Android (Google) Code Review
13 changed files with 494 additions and 41 deletions

View File

@@ -158,6 +158,15 @@ public final class SplitLayout {
return mDividePosition;
}
/**
* Returns the divider position as a fraction from 0 to 1.
*/
public float getDividerPositionAsFraction() {
return Math.min(1f, Math.max(0f, isLandscape()
? (float) ((mBounds1.right + mBounds2.left) / 2f) / mBounds2.right
: (float) ((mBounds1.bottom + mBounds2.top) / 2f) / mBounds2.bottom));
}
/** Applies new configuration, returns {@code false} if there's no effect to the layout. */
public boolean updateConfiguration(Configuration configuration) {
boolean affectsLayout = false;
@@ -342,6 +351,13 @@ public final class SplitLayout {
return bounds.width() > bounds.height();
}
/**
* Return if this layout is landscape.
*/
public boolean isLandscape() {
return isLandscape(mRootBounds);
}
/** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */
public void applySurfaceChanges(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2) {

View File

@@ -49,6 +49,7 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
@@ -180,11 +181,11 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
Slog.w(TAG, "Unexpected drag start during an active drag");
return false;
}
InstanceId loggerSessionId = mLogger.logStart(event);
pd.activeDragCount++;
pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId),
event.getClipData());
event.getClipData(), loggerSessionId);
setDropTargetWindowVisibility(pd, View.VISIBLE);
mLogger.logStart(event);
break;
case ACTION_DRAG_ENTERED:
pd.dragLayout.show();

View File

@@ -51,7 +51,7 @@ public class DragAndDropEventLogger {
/**
* Logs the start of a drag.
*/
public void logStart(DragEvent event) {
public InstanceId logStart(DragEvent event) {
final ClipDescription description = event.getClipDescription();
final ClipData data = event.getClipData();
final ClipData.Item item = data.getItemAt(0);
@@ -64,6 +64,7 @@ public class DragAndDropEventLogger {
mUiEventLogger.logWithInstanceId(getStartEnum(description),
mActivityInfo.applicationInfo.uid,
mActivityInfo.applicationInfo.packageName, mInstanceId);
return mInstanceId;
}
/**

View File

@@ -63,6 +63,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
@@ -86,6 +87,7 @@ public class DragAndDropPolicy {
private final SplitScreenController mSplitScreen;
private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
private InstanceId mLoggerSessionId;
private DragSession mSession;
public DragAndDropPolicy(Context context, SplitScreenController splitScreen) {
@@ -104,7 +106,8 @@ public class DragAndDropPolicy {
/**
* Starts a new drag session with the given initial drag data.
*/
void start(DisplayLayout displayLayout, ClipData data) {
void start(DisplayLayout displayLayout, ClipData data, InstanceId loggerSessionId) {
mLoggerSessionId = loggerSessionId;
mSession = new DragSession(mContext, mActivityTaskManager, displayLayout, data);
// TODO(b/169894807): Also update the session data with task stack changes
mSession.update();
@@ -207,6 +210,8 @@ public class DragAndDropPolicy {
// Launch in the side stage if we are not in split-screen already.
stage = STAGE_TYPE_SIDE;
}
// Add some data for logging splitscreen once it is invoked
mSplitScreen.logOnDroppedToSplit(position, mLoggerSessionId);
}
final ClipDescription description = data.getDescription();
@@ -294,7 +299,12 @@ public class DragAndDropPolicy {
@StageType int stage, @SplitPosition int position,
@Nullable Bundle options);
void enterSplitScreen(int taskId, boolean leftOrTop);
void exitSplitScreen();
/**
* Exits splitscreen, with an associated exit trigger from the SplitscreenUIChanged proto
* for logging.
*/
void exitSplitScreen(int exitTrigger);
}
/**
@@ -347,7 +357,7 @@ public class DragAndDropPolicy {
}
@Override
public void exitSplitScreen() {
public void exitSplitScreen(int exitTrigger) {
throw new UnsupportedOperationException("exitSplitScreen not implemented by starter");
}
}

View File

@@ -38,6 +38,7 @@ import android.view.WindowInsets.Type;
import androidx.annotation.NonNull;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
@@ -98,8 +99,9 @@ public class DragLayout extends View {
return mHasDropped;
}
public void prepare(DisplayLayout displayLayout, ClipData initialData) {
mPolicy.start(displayLayout, initialData);
public void prepare(DisplayLayout displayLayout, ClipData initialData,
InstanceId loggerSessionId) {
mPolicy.start(displayLayout, initialData, loggerSessionId);
mHasDropped = false;
mCurrentTarget = null;
}

View File

@@ -51,6 +51,8 @@ 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;
@@ -61,7 +63,6 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -86,6 +87,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final DisplayImeController mDisplayImeController;
private final Transitions mTransitions;
private final TransactionPool mTransactionPool;
private final SplitscreenEventLogger mLogger;
private StageCoordinator mStageCoordinator;
@@ -102,6 +104,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mDisplayImeController = displayImeController;
mTransitions = transitions;
mTransactionPool = transactionPool;
mLogger = new SplitscreenEventLogger();
}
public SplitScreen asSplitScreen() {
@@ -123,7 +126,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
// TODO: Multi-display
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController, mTransitions,
mTransactionPool);
mTransactionPool, mLogger);
}
}
@@ -165,8 +168,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
}
public void exitSplitScreen() {
mStageCoordinator.exitSplitScreen();
public void exitSplitScreen(int exitReason) {
mStageCoordinator.exitSplitScreen(exitReason);
}
public void onKeyguardOccludedChanged(boolean occluded) {
@@ -303,6 +306,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
}
/**
* 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) {
@@ -495,7 +505,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void exitSplitScreen() {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
(controller) -> {
controller.exitSplitScreen();
controller.exitSplitScreen(
FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
});
}

View File

@@ -0,0 +1,324 @@
/*
* 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.splitscreen;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitLayout.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.SplitLayout.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;
}
}
}

View File

@@ -26,6 +26,12 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
@@ -73,6 +79,7 @@ import android.window.WindowContainerTransaction;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -130,6 +137,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
private final DisplayImeController mDisplayImeController;
private final SplitScreenTransitions mSplitTransitions;
private final SplitscreenEventLogger mLogger;
private boolean mExitSplitScreenOnHide;
private boolean mKeyguardOccluded;
@@ -156,12 +164,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
DisplayImeController displayImeController, Transitions transitions,
TransactionPool transactionPool) {
TransactionPool transactionPool, SplitscreenEventLogger logger) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
mRootTDAOrganizer = rootTDAOrganizer;
mTaskOrganizer = taskOrganizer;
mLogger = logger;
mMainStage = new MainStage(
mTaskOrganizer,
mDisplayId,
@@ -190,7 +199,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool) {
SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -203,6 +213,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout = splitLayout;
mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
mOnTransitionAnimationComplete);
mLogger = logger;
transitions.addHandler(this);
}
@@ -450,19 +461,20 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void onKeyguardVisibilityChanged(boolean showing) {
if (!showing && mMainStage.isActive()
&& mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage);
exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED);
}
}
void exitSplitScreen() {
exitSplitScreen(null /* childrenToTop */);
void exitSplitScreen(int exitReason) {
exitSplitScreen(null /* childrenToTop */, exitReason);
}
void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
mExitSplitScreenOnHide = exitSplitScreenOnHide;
}
private void exitSplitScreen(StageTaskListener childrenToTop) {
private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
mMainStage.deactivate(wct, childrenToTop == mMainStage);
@@ -470,6 +482,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Reset divider position.
mSplitLayout.resetDividerPosition();
mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
if (childrenToTop != null) {
logExitToStage(exitReason, childrenToTop == mMainStage);
} else {
logExit(exitReason);
}
}
/**
@@ -522,9 +539,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
private void onStageChildTaskStatusChanged(
StageListenerImpl stageListener, int taskId, boolean present, boolean visible) {
private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
boolean present, boolean visible) {
int stage;
if (present) {
stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
@@ -532,6 +548,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// No longer on any stage
stage = STAGE_TYPE_UNDEFINED;
}
if (stage == STAGE_TYPE_MAIN) {
mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
mSplitLayout.isLandscape());
} else {
mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
mSplitLayout.isLandscape());
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
@@ -598,7 +621,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Don't dismiss staged split when both stages are not visible due to sleeping display,
// like the cases keyguard showing or screen off.
|| (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
exitSplitScreen();
exitSplitScreen(SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
}
} else if (mKeyguardOccluded) {
// At least one of the stages is visible while keyguard occluded. Dismiss split because
@@ -606,7 +629,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// task contains show-when-locked activity remains on top after split dismissed.
final StageTaskListener toTop =
mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
exitSplitScreen(toTop);
exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP);
}
// When both stage's visibility changed to visible, main stage might receives visibility
@@ -683,17 +706,16 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
final boolean hasChildren = stageListener.mHasChildren;
final boolean isSideStage = stageListener == mSideStageListener;
if (!hasChildren) {
if (isSideStage && mMainStageListener.mVisible) {
// Exit to main stage if side stage no longer has children.
exitSplitScreen(mMainStage);
exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
} else if (!isSideStage && mSideStageListener.mVisible) {
// Exit to side stage if main stage no longer has children.
exitSplitScreen(mSideStage);
exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
}
} else if (isSideStage) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -702,6 +724,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.setBounds(getSideStageBounds(), wct);
mTaskOrganizer.applyTransaction(wct);
}
if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
&& mSideStageListener.mHasChildren) {
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
mSplitLayout.isLandscape());
}
}
@VisibleForTesting
@@ -720,13 +749,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
onSnappedToDismissTransition(mainStageToTop);
return;
}
exitSplitScreen(mainStageToTop ? mMainStage : mSideStage);
exitSplitScreen(mainStageToTop ? mMainStage : mSideStage,
SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER);
}
@Override
public void onDoubleTappedDivider() {
setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
mSplitLayout.isLandscape());
}
@Override
@@ -740,6 +773,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
updateWindowBounds(layout, wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
/**
@@ -1139,6 +1173,36 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
}
/**
* Sets drag info to be logged when splitscreen is next entered.
*/
public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
mLogger.enterRequestedByDrag(position, dragSessionId);
}
/**
* Logs the exit of splitscreen.
*/
private void logExit(int exitReason) {
mLogger.logExit(exitReason,
SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
mSplitLayout.isLandscape());
}
/**
* Logs the exit of splitscreen to a specific stage. This must be called before the exit is
* executed.
*/
private void logExitToStage(int exitReason, boolean toMainStage) {
mLogger.logExit(exitReason,
toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED,
toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
!toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
!toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
mSplitLayout.isLandscape());
}
class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
boolean mHasRootTask = false;
boolean mVisible = false;
@@ -1178,7 +1242,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onNoLongerSupportMultiWindow() {
if (mMainStage.isActive()) {
StageCoordinator.this.exitSplitScreen();
StageCoordinator.this.exitSplitScreen(
SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW);
}
}

View File

@@ -97,6 +97,20 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
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.isFocused) return true;

View File

@@ -65,6 +65,7 @@ import android.view.DisplayInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -96,6 +97,9 @@ public class DragAndDropPolicyTest {
@Mock
private SplitScreenController mSplitScreenStarter;
@Mock
private InstanceId mLoggerSessionId;
private DisplayLayout mLandscapeDisplayLayout;
private DisplayLayout mPortraitDisplayLayout;
private Insets mInsets;
@@ -201,7 +205,7 @@ public class DragAndDropPolicyTest {
@Test
public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
setRunningTask(mHomeTask);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
@@ -213,7 +217,7 @@ public class DragAndDropPolicyTest {
@Test
public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
setRunningTask(mFullscreenAppTask);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
@@ -230,7 +234,7 @@ public class DragAndDropPolicyTest {
@Test
public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
setRunningTask(mFullscreenAppTask);
mPolicy.start(mPortraitDisplayLayout, mActivityClipData);
mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
@@ -248,7 +252,7 @@ public class DragAndDropPolicyTest {
public void testDragAppOverSplitApp_expectSplitTargets_DropLeft() {
setInSplitScreen(true);
setRunningTask(mSplitPrimaryAppTask);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
@@ -261,7 +265,7 @@ public class DragAndDropPolicyTest {
public void testDragAppOverSplitApp_expectSplitTargets_DropRight() {
setInSplitScreen(true);
setRunningTask(mSplitPrimaryAppTask);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
@@ -274,7 +278,7 @@ public class DragAndDropPolicyTest {
public void testDragAppOverSplitAppPhone_expectVerticalSplitTargets_DropTop() {
setInSplitScreen(true);
setRunningTask(mSplitPrimaryAppTask);
mPolicy.start(mPortraitDisplayLayout, mActivityClipData);
mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
@@ -287,7 +291,7 @@ public class DragAndDropPolicyTest {
public void testDragAppOverSplitAppPhone_expectVerticalSplitTargets_DropBottom() {
setInSplitScreen(true);
setRunningTask(mSplitPrimaryAppTask);
mPolicy.start(mPortraitDisplayLayout, mActivityClipData);
mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
@@ -299,7 +303,7 @@ public class DragAndDropPolicyTest {
@Test
public void testTargetHitRects() {
setRunningTask(mFullscreenAppTask);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = mPolicy.getTargets(mInsets);
for (Target t : targets) {
assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.top) == t);

View File

@@ -65,9 +65,10 @@ public class SplitTestUtils {
TestStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
MainStage mainStage, SideStage sideStage, DisplayImeController imeController,
SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool) {
SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger) {
super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage,
sideStage, imeController, splitLayout, transitions, transactionPool);
sideStage, imeController, splitLayout, transitions, transactionPool, logger);
// Prepare default TaskDisplayArea for testing.
mDisplayAreaInfo = new DisplayAreaInfo(

View File

@@ -82,6 +82,7 @@ public class SplitTransitionTests extends ShellTestCase {
@Mock private TransactionPool mTransactionPool;
@Mock private Transitions mTransitions;
@Mock private SurfaceSession mSurfaceSession;
@Mock private SplitscreenEventLogger mLogger;
private SplitLayout mSplitLayout;
private MainStage mMainStage;
private SideStage mSideStage;
@@ -107,7 +108,8 @@ public class SplitTransitionTests extends ShellTestCase {
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
mDisplayImeController, mSplitLayout, mTransitions, mTransactionPool);
mDisplayImeController, mSplitLayout, mTransitions, mTransactionPool,
mLogger);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
.when(mTransitions).startTransition(anyInt(), any(), any());

View File

@@ -59,6 +59,7 @@ public class StageCoordinatorTests extends ShellTestCase {
@Mock private DisplayImeController mDisplayImeController;
@Mock private Transitions mTransitions;
@Mock private TransactionPool mTransactionPool;
@Mock private SplitscreenEventLogger mLogger;
private StageCoordinator mStageCoordinator;
@Before
@@ -66,7 +67,8 @@ public class StageCoordinatorTests extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
mDisplayImeController, null /* splitLayout */, mTransitions, mTransactionPool);
mDisplayImeController, null /* splitLayout */, mTransitions, mTransactionPool,
mLogger);
}
@Test