blob: 3e7a1004ed7aebc95ed0749fd71a549d2489f09d [file] [log] [blame]
/*
* 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.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__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.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_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
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.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 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());
}
/**
* 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_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;
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;
}
}
}