436 lines
20 KiB
Java
436 lines
20 KiB
Java
/*
|
|
* 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__LAUNCHER;
|
|
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__MULTI_INSTANCE;
|
|
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__UNKNOWN_ENTER;
|
|
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__CHILD_TASK_ENTER_PIP;
|
|
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__FULLSCREEN_SHORTCUT;
|
|
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DESKTOP_MODE;
|
|
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
|
|
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
|
|
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
|
|
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
|
|
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
|
|
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT;
|
|
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 static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_DRAG;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_UNKNOWN;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
|
|
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
|
|
|
|
import android.annotation.Nullable;
|
|
import android.util.Slog;
|
|
|
|
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;
|
|
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
|
|
|
|
/**
|
|
* 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 @Nullable InstanceId mEnterSessionId;
|
|
|
|
// For deduping async events
|
|
private int mLastMainStagePosition = -1;
|
|
private int mLastMainStageUid = -1;
|
|
private int mLastSideStagePosition = -1;
|
|
private int mLastSideStageUid = -1;
|
|
private float mLastSplitRatio = -1f;
|
|
private @SplitScreenController.SplitEnterReason int mEnterReason = ENTER_REASON_UNKNOWN;
|
|
|
|
public SplitscreenEventLogger() {
|
|
mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
|
|
}
|
|
|
|
/**
|
|
* Return whether a splitscreen session has started.
|
|
*/
|
|
public boolean hasStartedSession() {
|
|
return mLoggerSessionId != null;
|
|
}
|
|
|
|
public boolean isEnterRequestedByDrag() {
|
|
return mEnterReason == ENTER_REASON_DRAG;
|
|
}
|
|
|
|
/**
|
|
* May be called before logEnter() to indicate that the session was started from a drag.
|
|
*/
|
|
public void enterRequestedByDrag(@SplitPosition int position, InstanceId enterSessionId) {
|
|
mDragEnterPosition = position;
|
|
enterRequested(enterSessionId, ENTER_REASON_DRAG);
|
|
}
|
|
|
|
/**
|
|
* May be called before logEnter() to indicate that the session was started from launcher.
|
|
* This specifically is for all the scenarios where split started without a drag interaction
|
|
*/
|
|
public void enterRequested(@Nullable InstanceId enterSessionId,
|
|
@SplitScreenController.SplitEnterReason int enterReason) {
|
|
mEnterSessionId = enterSessionId;
|
|
mEnterReason = enterReason;
|
|
}
|
|
|
|
/**
|
|
* @return if an enterSessionId has been set via either
|
|
* {@link #enterRequested(InstanceId, int)} or
|
|
* {@link #enterRequestedByDrag(int, InstanceId)}
|
|
*/
|
|
public boolean hasValidEnterSessionId() {
|
|
return mEnterSessionId != null;
|
|
}
|
|
|
|
/**
|
|
* 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 = getLoggerEnterReason(isLandscape);
|
|
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,
|
|
mEnterSessionId != null ? mEnterSessionId.getId() : 0,
|
|
mLoggerSessionId.getId());
|
|
}
|
|
|
|
private int getLoggerEnterReason(boolean isLandscape) {
|
|
switch (mEnterReason) {
|
|
case ENTER_REASON_MULTI_INSTANCE:
|
|
return SPLITSCREEN_UICHANGED__ENTER_REASON__MULTI_INSTANCE;
|
|
case ENTER_REASON_LAUNCHER:
|
|
return SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER;
|
|
case ENTER_REASON_DRAG:
|
|
return getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape);
|
|
case ENTER_REASON_UNKNOWN:
|
|
default:
|
|
return SPLITSCREEN_UICHANGED__ENTER_REASON__UNKNOWN_ENTER;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the framework logging constant given a splitscreen exit reason.
|
|
*/
|
|
private int getLoggerExitReason(@ExitReason int exitReason) {
|
|
switch (exitReason) {
|
|
case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
|
|
case EXIT_REASON_APP_FINISHED:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
|
|
case EXIT_REASON_DEVICE_FOLDED:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
|
|
case EXIT_REASON_DRAG_DIVIDER:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
|
|
case EXIT_REASON_RETURN_HOME:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
|
|
case EXIT_REASON_ROOT_TASK_VANISHED:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
|
|
case EXIT_REASON_SCREEN_LOCKED:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
|
|
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
|
|
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__CHILD_TASK_ENTER_PIP;
|
|
case EXIT_REASON_RECREATE_SPLIT:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
|
|
case EXIT_REASON_FULLSCREEN_SHORTCUT:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
|
|
case EXIT_REASON_DESKTOP_MODE:
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__DESKTOP_MODE;
|
|
case EXIT_REASON_UNKNOWN:
|
|
// Fall through
|
|
default:
|
|
Slog.e("SplitscreenEventLogger", "Unknown exit reason: " + exitReason);
|
|
return SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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(@ExitReason 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 */,
|
|
getLoggerExitReason(exitReason),
|
|
0f /* splitRatio */,
|
|
getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
|
|
mainStageUid,
|
|
getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
|
|
sideStageUid,
|
|
0 /* dragInstanceId */,
|
|
mLoggerSessionId.getId());
|
|
|
|
// Reset states
|
|
mLoggerSessionId = null;
|
|
mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
|
|
mEnterSessionId = null;
|
|
mLastMainStagePosition = -1;
|
|
mLastMainStageUid = -1;
|
|
mLastSideStagePosition = -1;
|
|
mLastSideStageUid = -1;
|
|
mEnterReason = ENTER_REASON_UNKNOWN;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
}
|