Merge "Fix theme of BrightnessDialog" into udc-qpr-dev
diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java
index a7cd168..690dfcf 100644
--- a/core/java/android/content/res/ThemedResourceCache.java
+++ b/core/java/android/content/res/ThemedResourceCache.java
@@ -137,8 +137,10 @@
*/
@UnsupportedAppUsage
public void onConfigurationChange(@Config int configChanges) {
- prune(configChanges);
- mGeneration++;
+ synchronized (this) {
+ pruneLocked(configChanges);
+ mGeneration++;
+ }
}
/**
@@ -214,22 +216,20 @@
* simply prune missing weak references
* @return {@code true} if the cache is completely empty after pruning
*/
- private boolean prune(@Config int configChanges) {
- synchronized (this) {
- if (mThemedEntries != null) {
- for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
- if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
- mThemedEntries.removeAt(i);
- }
+ private boolean pruneLocked(@Config int configChanges) {
+ if (mThemedEntries != null) {
+ for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
+ if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
+ mThemedEntries.removeAt(i);
}
}
-
- pruneEntriesLocked(mNullThemedEntries, configChanges);
- pruneEntriesLocked(mUnthemedEntries, configChanges);
-
- return mThemedEntries == null && mNullThemedEntries == null
- && mUnthemedEntries == null;
}
+
+ pruneEntriesLocked(mNullThemedEntries, configChanges);
+ pruneEntriesLocked(mUnthemedEntries, configChanges);
+
+ return mThemedEntries == null && mNullThemedEntries == null
+ && mUnthemedEntries == null;
}
private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 262d487..2dbc444 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -434,7 +434,7 @@
"Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
from);
mInteractive = interactive;
- if (!mInteractive && mMoving) {
+ if (!mInteractive && hideHandle && mMoving) {
final int position = mSplitLayout.getDividePosition();
mSplitLayout.flingDividePosition(
mLastDraggingPosition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index a9ccdf6..2b10377 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -323,7 +323,11 @@
}
}
if (mShown) {
- fadeOutDecor(()-> animFinishedCallback.accept(true));
+ fadeOutDecor(()-> {
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.accept(true);
+ }
+ });
} else {
// Decor surface is hidden so release it directly.
releaseDecor(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f70d3ae..e8fa638 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -593,9 +593,6 @@
void flingDividePosition(int from, int to, int duration,
@Nullable Runnable flingFinishedCallback) {
if (from == to) {
- // No animation run, still callback to stop resizing.
- mSplitLayoutHandler.onLayoutSizeChanged(this);
-
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
@@ -773,15 +770,13 @@
ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
boolean boundsChanged = false;
if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
- wct.setBounds(task1.token, mBounds1);
- wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
+ setTaskBounds(wct, task1, mBounds1);
mWinBounds1.set(mBounds1);
mWinToken1 = task1.token;
boundsChanged = true;
}
if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
- wct.setBounds(task2.token, mBounds2);
- wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
+ setTaskBounds(wct, task2, mBounds2);
mWinBounds2.set(mBounds2);
mWinToken2 = task2.token;
boundsChanged = true;
@@ -789,6 +784,13 @@
return boundsChanged;
}
+ /** Set bounds to the {@link WindowContainerTransaction} for single task. */
+ public void setTaskBounds(WindowContainerTransaction wct,
+ ActivityManager.RunningTaskInfo task, Rect bounds) {
+ wct.setBounds(task.token, bounds);
+ wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds));
+ }
+
private int getSmallestWidthDp(Rect bounds) {
mTempRect.set(bounds);
mTempRect.inset(getDisplayStableInsets(mContext));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 0289da9..d7ea1c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -89,4 +89,9 @@
int userId1, int userId2) {
return (packageName1 != null && packageName1.equals(packageName2)) && (userId1 == userId2);
}
+
+ /** Generates a common log message for split screen failures */
+ public static String splitFailureMessage(String caller, String reason) {
+ return "(" + caller + ") Splitscreen aborted: " + reason;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 4fda4b7..40ea276 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -286,7 +286,7 @@
visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
addMoveToFullscreenChanges(callbackWCT, task)
- shellTaskOrganizer.applyTransaction(callbackWCT)
+ transitions.startTransition(TRANSIT_CHANGE, callbackWCT, null /* handler */)
}
} else {
addMoveToFullscreenChanges(wct, task)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index e294229..3669bce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -31,6 +31,7 @@
import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -50,6 +51,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
@@ -551,6 +553,8 @@
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startShortcut",
+ "app package " + packageName + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
return;
@@ -580,6 +584,8 @@
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startShortcutAndTaskWithLegacyTransition",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -612,6 +618,8 @@
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startShortcutAndTask",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -647,6 +655,8 @@
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntentAndTaskWithLegacyTransition",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -677,6 +687,8 @@
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntentAndTask",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -705,6 +717,8 @@
pendingIntent2 = null;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntentsWithLegacyTransition",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -734,6 +748,8 @@
pendingIntent2 = null;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntents",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -780,6 +796,8 @@
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntent",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index bf70d48..6961dab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -28,6 +28,7 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
@@ -41,6 +42,7 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -473,6 +475,8 @@
if (isEnteringSplit && mSideStage.getChildCount() == 0) {
mMainExecutor.execute(() -> exitSplitScreen(
null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("startShortcut",
+ "side stage was not populated"));
mSplitUnsupportedToast.show();
}
@@ -560,6 +564,8 @@
if (isEnteringSplit && mSideStage.getChildCount() == 0) {
mMainExecutor.execute(() -> exitSplitScreen(
null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("startIntentLegacy",
+ "side stage was not populated"));
mSplitUnsupportedToast.show();
}
@@ -1090,6 +1096,8 @@
mMainExecutor.execute(() ->
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
+ "main or side stage was not populated."));
mSplitUnsupportedToast.show();
} else {
mSyncQueue.queue(evictWct);
@@ -1109,6 +1117,8 @@
if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
+ "main or side stage was not populated"));
mSplitUnsupportedToast.show();
return;
}
@@ -1293,20 +1303,12 @@
final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
final boolean oneStageVisible =
mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
- if (oneStageVisible) {
+ if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) {
// Dismiss split because there's show-when-locked activity showing on top of keyguard.
// Also make sure the task contains show-when-locked activity remains on top after split
// dismissed.
- if (!ENABLE_SHELL_TRANSITIONS) {
- final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
- exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- } else {
- final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(dismissTop, wct);
- mSplitTransitions.startDismissTransition(wct, this, dismissTop,
- EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- }
+ final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
+ exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
}
@@ -1561,6 +1563,8 @@
// split bounds.
wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token,
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
+ mSplitLayout.getInvisibleBounds(mTempRect1);
+ mSplitLayout.setTaskBounds(wct, mSideStage.mRootTaskInfo, mTempRect1);
}
wct.reorder(mRootTaskInfo.token, true);
setRootForceTranslucent(false, wct);
@@ -2376,6 +2380,15 @@
// so appends operations to exit split.
prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
}
+ } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null
+ && isSplitScreenVisible()) {
+ // Split include show when lock activity case, check the top activity under which
+ // stage and move it to the top.
+ int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity)
+ ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ prepareExitSplitScreen(top, out);
+ mSplitTransitions.setDismissTransition(transition, top,
+ EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
// When split in the background, it should be only opening/dismissing transition and
@@ -2704,12 +2717,13 @@
}
} else {
if (mainChild == null || sideChild == null) {
- Log.w(TAG, "Launched 2 tasks in split, but didn't receive"
- + " 2 tasks in transition. Possibly one of them failed to launch");
final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
(sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
mSplitTransitions.mPendingEnter.cancel(
(cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+ Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
+ "launched 2 tasks in split, but didn't receive "
+ + "2 tasks in transition. Possibly one of them failed to launch"));
mSplitUnsupportedToast.show();
return true;
}
@@ -3169,7 +3183,7 @@
}
@Override
- public void onNoLongerSupportMultiWindow() {
+ public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
if (mMainStage.isActive()) {
final boolean isMainStage = mMainStageListener == this;
if (!ENABLE_SHELL_TRANSITIONS) {
@@ -3184,6 +3198,9 @@
prepareExitSplitScreen(stageType, wct);
mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+ Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
+ "app package " + taskInfo.baseActivity.getPackageName()
+ + " does not support splitscreen, or is a controlled activity type"));
mSplitUnsupportedToast.show();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 3ef4f02..e2c55e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -81,7 +81,7 @@
void onRootTaskVanished();
- void onNoLongerSupportMultiWindow();
+ void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo);
}
private final Context mContext;
@@ -226,7 +226,7 @@
taskInfo.getWindowingMode())) {
// Leave split screen if the task no longer supports multi window or have
// uncontrolled task.
- mCallbacks.onNoLongerSupportMultiWindow();
+ mCallbacks.onNoLongerSupportMultiWindow(taskInfo);
return;
}
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 4faa929..0d77a2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -26,6 +26,7 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.view.SurfaceControl;
@@ -69,8 +70,10 @@
private final Rect mTmpRect = new Rect();
private final Rect mTmpRootRect = new Rect();
private final int[] mTmpLocation = new int[2];
+ private final Rect mBoundsOnScreen = new Rect();
private final TaskViewTaskController mTaskViewTaskController;
private Region mObscuredTouchRegion;
+ private Insets mCaptionInsets;
public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
super(context, null, 0, 0, true /* disableBackgroundLayer */);
@@ -169,6 +172,25 @@
}
/**
+ * Sets a region of the task to inset to allow for a caption bar. Currently only top insets
+ * are supported.
+ * <p>
+ * This region will be factored in as an area of taskview that is not touchable activity
+ * content (i.e. you don't need to additionally set {@link #setObscuredTouchRect(Rect)} for
+ * the caption area).
+ *
+ * @param captionInsets the insets to apply to task view.
+ */
+ public void setCaptionInsets(Insets captionInsets) {
+ mCaptionInsets = captionInsets;
+ if (captionInsets == null) {
+ // If captions are null we can set them now; otherwise they'll get set in
+ // onComputeInternalInsets.
+ mTaskViewTaskController.setCaptionInsets(null);
+ }
+ }
+
+ /**
* Call when view position or size has changed. Do not call when animating.
*/
public void onLocationChanged() {
@@ -230,6 +252,15 @@
getLocationInWindow(mTmpLocation);
mTmpRect.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight());
+ if (mCaptionInsets != null) {
+ mTmpRect.inset(mCaptionInsets);
+ getBoundsOnScreen(mBoundsOnScreen);
+ mTaskViewTaskController.setCaptionInsets(new Rect(
+ mBoundsOnScreen.left,
+ mBoundsOnScreen.top,
+ mBoundsOnScreen.right + getWidth(),
+ mBoundsOnScreen.top + mCaptionInsets.top));
+ }
inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE);
if (mObscuredTouchRegion != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 163cf50..064af04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -33,6 +33,7 @@
import android.util.CloseGuard;
import android.util.Slog;
import android.view.SurfaceControl;
+import android.view.WindowInsets;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -82,6 +83,10 @@
private TaskView.Listener mListener;
private Executor mListenerExecutor;
+ /** Used to inset the activity content to allow space for a caption bar. */
+ private final Binder mCaptionInsetsOwner = new Binder();
+ private Rect mCaptionInsets;
+
public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
mContext = context;
@@ -436,6 +441,32 @@
mTaskViewTransitions.closeTaskView(wct, this);
}
+ /**
+ * Sets a region of the task to inset to allow for a caption bar.
+ *
+ * @param captionInsets the rect for the insets in screen coordinates.
+ */
+ void setCaptionInsets(Rect captionInsets) {
+ if (mCaptionInsets != null && mCaptionInsets.equals(captionInsets)) {
+ return;
+ }
+ mCaptionInsets = captionInsets;
+ applyCaptionInsetsIfNeeded();
+ }
+
+ void applyCaptionInsetsIfNeeded() {
+ if (mTaskToken == null) return;
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (mCaptionInsets != null) {
+ wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+ WindowInsets.Type.captionBar(), mCaptionInsets);
+ } else {
+ wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+ WindowInsets.Type.captionBar());
+ }
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
/** Should be called when the client surface is destroyed. */
public void surfaceDestroyed() {
mSurfaceCreated = false;
@@ -564,6 +595,7 @@
mTaskViewTransitions.updateBoundsState(this, boundsOnScreen);
mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
wct.setBounds(mTaskToken, boundsOnScreen);
+ applyCaptionInsetsIfNeeded();
} else {
// The surface has already been destroyed before the task has appeared,
// so go ahead and hide the task entirely
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index b217bd3..ce81910 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -145,7 +145,9 @@
mDisplay.getDisplayId(),
0 /* taskCornerRadius */,
mDecorationContainerSurface,
- mDragPositioningCallback);
+ mDragPositioningCallback,
+ mSurfaceControlBuilderSupplier,
+ mSurfaceControlTransactionSupplier);
}
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index bc89385..a359395 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -234,7 +234,9 @@
mDisplay.getDisplayId(),
mRelayoutParams.mCornerRadius,
mDecorationContainerSurface,
- mDragPositioningCallback);
+ mDragPositioningCallback,
+ mSurfaceControlBuilderSupplier,
+ mSurfaceControlTransactionSupplier);
}
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 4e98f0c..941617d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -22,7 +22,9 @@
* Callback called when receiving drag-resize or drag-move related input events.
*/
public interface DragPositioningCallback {
- @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
+ @IntDef(flag = true, value = {
+ CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM
+ })
@interface CtrlType {}
int CTRL_TYPE_UNDEFINED = 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index e5fc66a..336d95e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -18,8 +18,11 @@
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
@@ -48,6 +51,8 @@
import com.android.internal.view.BaseIWindow;
+import java.util.function.Supplier;
+
/**
* An input event listener registered to InputDispatcher to receive input events on task edges and
* and corners. Converts them to drag resize requests.
@@ -60,6 +65,7 @@
private final Handler mHandler;
private final Choreographer mChoreographer;
private final InputManager mInputManager;
+ private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
private final int mDisplayId;
private final BaseIWindow mFakeWindow;
@@ -69,6 +75,10 @@
private final TaskResizeInputEventReceiver mInputEventReceiver;
private final DragPositioningCallback mCallback;
+ private final SurfaceControl mInputSinkSurface;
+ private final BaseIWindow mFakeSinkWindow;
+ private final InputChannel mSinkInputChannel;
+
private int mTaskWidth;
private int mTaskHeight;
private int mResizeHandleThickness;
@@ -90,15 +100,18 @@
int displayId,
int taskCornerRadius,
SurfaceControl decorationSurface,
- DragPositioningCallback callback) {
+ DragPositioningCallback callback,
+ Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
mInputManager = context.getSystemService(InputManager.class);
mHandler = handler;
mChoreographer = choreographer;
+ mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mDisplayId = displayId;
mTaskCornerRadius = taskCornerRadius;
mDecorationSurface = decorationSurface;
- // Use a fake window as the backing surface is a container layer and we don't want to create
- // a buffer layer for it so we can't use ViewRootImpl.
+ // Use a fake window as the backing surface is a container layer, and we don't want to
+ // create a buffer layer for it, so we can't use ViewRootImpl.
mFakeWindow = new BaseIWindow();
mFakeWindow.setSession(mWindowSession);
mFocusGrantToken = new Binder();
@@ -111,7 +124,7 @@
null /* hostInputToken */,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
- 0 /* inputFeatures */,
+ INPUT_FEATURE_SPY,
TYPE_APPLICATION,
null /* windowToken */,
mFocusGrantToken,
@@ -126,6 +139,35 @@
mCallback = callback;
mDragDetector = new DragDetector(mInputEventReceiver);
mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
+
+ mInputSinkSurface = surfaceControlBuilderSupplier.get()
+ .setName("TaskInputSink of " + decorationSurface)
+ .setContainerLayer()
+ .setParent(mDecorationSurface)
+ .build();
+ mSurfaceControlTransactionSupplier.get()
+ .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
+ .show(mInputSinkSurface)
+ .apply();
+ mFakeSinkWindow = new BaseIWindow();
+ mSinkInputChannel = new InputChannel();
+ try {
+ mWindowSession.grantInputChannel(
+ mDisplayId,
+ mInputSinkSurface,
+ mFakeSinkWindow,
+ null /* hostInputToken */,
+ FLAG_NOT_FOCUSABLE,
+ 0 /* privateFlags */,
+ INPUT_FEATURE_NO_INPUT_CHANNEL,
+ TYPE_INPUT_CONSUMER,
+ null /* windowToken */,
+ mFocusGrantToken,
+ "TaskInputSink of " + decorationSurface,
+ mSinkInputChannel);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
/**
@@ -219,7 +261,35 @@
mDecorationSurface,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
- 0 /* inputFeatures */,
+ INPUT_FEATURE_SPY,
+ touchRegion);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ mSurfaceControlTransactionSupplier.get()
+ .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight)
+ .apply();
+ // The touch region of the TaskInputSink should be the touch region of this
+ // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent
+ // input windows from handling down events, which will bring tasks in the back to front.
+ //
+ // Note not the entire touch region responds to both mouse and touchscreen events.
+ // Therefore, in the region that only responds to one of them, it would be a no-op to
+ // perform a gesture in the other type of events. We currently only have a mouse-only region
+ // out of the task bounds, and due to the roughness of touchscreen events, it's not a severe
+ // issue. However, were there touchscreen-only a region out of the task bounds, mouse
+ // gestures will become no-op in that region, even though the mouse gestures may appear to
+ // be performed on the input window behind the resize handle.
+ touchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
+ try {
+ mWindowSession.updateInputChannel(
+ mSinkInputChannel.getToken(),
+ mDisplayId,
+ mInputSinkSurface,
+ FLAG_NOT_FOCUSABLE,
+ 0 /* privateFlags */,
+ INPUT_FEATURE_NO_INPUT_CHANNEL,
touchRegion);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -248,6 +318,16 @@
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
+
+ mSinkInputChannel.dispose();
+ try {
+ mWindowSession.remove(mFakeSinkWindow);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ mSurfaceControlTransactionSupplier.get()
+ .remove(mInputSinkSurface)
+ .apply();
}
private class TaskResizeInputEventReceiver extends InputEventReceiver
@@ -316,6 +396,8 @@
mShouldHandleEvents = isInResizeHandleBounds(x, y);
}
if (mShouldHandleEvents) {
+ mInputManager.pilferPointers(mInputChannel.getToken());
+
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 917abf5..e1b6db5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -63,8 +63,6 @@
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
- mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
- .getStableBounds(mStableBounds);
}
@Override
@@ -80,6 +78,10 @@
mTaskOrganizer.applyTransaction(wct);
}
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+ if (mStableBounds.isEmpty()) {
+ mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(mStableBounds);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index bf3ff3f..ae3b5eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -80,8 +80,6 @@
mTransactionSupplier = supplier;
mTransitions = transitions;
mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
- mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
- .getStableBounds(mStableBounds);
}
@Override
@@ -100,6 +98,10 @@
}
mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+ if (mStableBounds.isEmpty()) {
+ mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(mStableBounds);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ddc7fef..0b0d9d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -64,6 +64,24 @@
implements AutoCloseable {
/**
+ * The Z-order of {@link #mCaptionContainerSurface}.
+ * <p>
+ * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
+ * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
+ * prior to caption view itself, treating corner inputs as resize events rather than
+ * repositioning.
+ */
+ static final int CAPTION_LAYER_Z_ORDER = -1;
+ /**
+ * The Z-order of the task input sink in {@link DragPositioningCallback}.
+ * <p>
+ * This task input sink is used to prevent undesired dispatching of motion events out of task
+ * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
+ * input events first.
+ */
+ static final int INPUT_SINK_Z_ORDER = -2;
+
+ /**
* System-wide context. Only used to create context with overridden configurations.
*/
final Context mContext;
@@ -238,11 +256,8 @@
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = taskBounds.width();
- // We use mDecorationContainerSurface to define input window for task resizing; by layering
- // it in front of mCaptionContainerSurface, we can allow it to handle input prior to
- // caption view itself, treating corner inputs as resize events rather than repositioning.
startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
- .setLayer(mCaptionContainerSurface, -1)
+ .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
.show(mCaptionContainerSurface);
if (ViewRootImpl.CAPTION_ON_SHELL) {
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 4cc6ced..89747df 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -95,6 +95,7 @@
":WMShellFlickerTestsBubbles-src",
":WMShellFlickerTestsPip-src",
":WMShellFlickerTestsSplitScreen-src",
+ ":WMShellFlickerServiceTests-src",
],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
index 6fe88ca..d3f3c5b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -18,10 +18,10 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
-import android.tools.common.flicker.assertions.FlickerTest
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.assertions.FlickerTest
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -45,7 +45,6 @@
* Swipe right from the bottom of the screen to quick switch back to the app
* ```
*/
-
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -152,9 +151,8 @@
@Test
fun appWindowBecomesAndStaysVisible() {
flicker.assertWm {
- this.isAppWindowInvisible(letterboxApp)
- .then()
- .isAppWindowVisible(letterboxApp) }
+ this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp)
+ }
}
/**
@@ -245,7 +243,8 @@
.isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
.then()
.isVisible(letterboxApp)
- .isVisible(ComponentNameMatcher.LETTERBOX) }
+ .isVisible(ComponentNameMatcher.LETTERBOX)
+ }
}
/** {@inheritDoc} */
@@ -273,4 +272,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index 8a85374..8bd44c3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -66,11 +66,12 @@
AutoEnterPipOnGoToHomeTest(flicker) {
private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
/** Second app used to enter split screen mode */
- private val secondAppForSplitScreen = SimpleAppHelper(
- instrumentation,
- ActivityOptions.SplitScreen.Primary.LABEL,
- ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
- )
+ private val secondAppForSplitScreen =
+ SimpleAppHelper(
+ instrumentation,
+ ActivityOptions.SplitScreen.Primary.LABEL,
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+ )
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..566adec
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..92b6227
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..e6d56b5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark :
+ DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..6752c58
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavPortraitBenchmark :
+ DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..7c9ab99
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..4b79571
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..0495079
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..71ef48b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..c78729c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..30bce2f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..b33ea7c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..07a86a5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..9a1d127
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..266e268
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..83fc30b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b2f1929
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..dae92dd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..732047b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..1de7efd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..1a046aa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..6e88f0e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..d26a29c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..4a552b0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b7376ea
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..b2d05e4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..6de31b1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..aab18a6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark :
+ SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b074f2c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
+ SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..c402aa4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..840401c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
index 964a785..a5c5122 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
@@ -29,9 +29,7 @@
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
- @ExpectedScenarios([])
- @Test
- override fun copyContentInSplit() = super.copyContentInSplit()
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
companion object {
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
index bc30d41..092fb67 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
@@ -29,9 +29,7 @@
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
- @ExpectedScenarios([])
- @Test
- override fun copyContentInSplit() = super.copyContentInSplit()
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
companion object {
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index a43ad9b..1d4c4d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.EdgeExtensionComponentMatcher
@@ -28,13 +27,9 @@
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -60,21 +55,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(textEditApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(textEditApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
-
- // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index a118c08..0d967eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -26,13 +25,9 @@
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -58,19 +53,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index e0a47b3..f236c2d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -28,12 +27,9 @@
import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -59,19 +55,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 8f867df..8aaa98a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -28,15 +27,10 @@
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -62,21 +56,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(thirdApp)
- flicker.appWindowIsVisibleAtStart(fourthApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.appWindowIsInvisibleAtEnd(thirdApp)
- flicker.appWindowIsInvisibleAtEnd(fourthApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerInvisibleAtMiddle() =
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index 9c68aa4..d9d22de 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -16,18 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val textEditApp = SplitScreenUtils.getIme(instrumentation)
protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
@@ -54,21 +51,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index 21ac783..7e8d60b4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -61,19 +57,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index 931bff6..770e032 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -47,19 +43,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index 7fa2c0b..46570fd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -16,19 +16,16 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +34,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -45,27 +42,11 @@
transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
- // robust enough to get the correct end state.
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 952051f..5c3d4ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
@@ -57,30 +53,11 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
index 1de1c0c..6b122c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromNotificationBenchmark(
+abstract class EnterSplitScreenByDragFromNotificationBenchmark(
override val flicker: LegacyFlickerTest
) : SplitScreenBase(flicker) {
protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -59,21 +55,6 @@
teardown { sendNotificationApp.exit(wmHelper) }
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index 929c7ea..78f9bab 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,8 +35,9 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromShortcutBenchmark(override val flicker: LegacyFlickerTest) :
- SplitScreenBase(flicker) {
+abstract class EnterSplitScreenByDragFromShortcutBenchmark(
+ override val flicker: LegacyFlickerTest
+) : SplitScreenBase(flicker) {
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
@@ -62,25 +59,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index 9f829c9..78907f0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -56,26 +52,6 @@
}
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index 1d5518f..2c91e84 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -56,19 +52,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index a7fb93e..fa09c2e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -16,8 +16,6 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -27,10 +25,9 @@
import android.tools.device.helpers.WindowUtils
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +36,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,14 +50,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
wmHelper
.StateSyncBuilder()
@@ -134,14 +123,6 @@
return displayBounds.width > displayBounds.height
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
- // robust enough to get the correct end state.
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index 8358aff..ff22006 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
@@ -55,19 +51,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index b63c765..5787b02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,19 +49,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index ce5a409b..b2d5091 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,19 +49,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index 9821bfa..f234e46 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -16,17 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -35,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thirdApp = SplitScreenUtils.getIme(instrumentation)
protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -64,8 +61,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui") @Presubmit @Test open fun cujCompleted() {}
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
index 4fc4627..61c3679 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
@@ -22,8 +22,8 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.SplitScreenUtils
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -33,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -47,14 +47,6 @@
}
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index df1e2e1..946a7ef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -161,7 +161,7 @@
childTask.supportsMultiWindow = false;
mStageTaskListener.onTaskInfoChanged(childTask);
- verify(mCallbacks).onNoLongerSupportMultiWindow();
+ verify(mCallbacks).onNoLongerSupportMultiWindow(childTask);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 1b38956..50435a0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -40,6 +40,7 @@
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.Context;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.testing.AndroidTestingRunner;
@@ -563,4 +564,47 @@
mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
verify(mTaskViewTaskController, never()).cleanUpPendingTask();
}
+
+ @Test
+ public void testSetCaptionInsets_noTaskInitially() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ Rect insets = new Rect(0, 400, 0, 0);
+ mTaskView.setCaptionInsets(Insets.of(insets));
+ mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+ verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+ verify(mOrganizer, never()).applyTransaction(any());
+
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ reset(mOrganizer);
+ reset(mTaskViewTaskController);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+ mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+ verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+ verify(mOrganizer).applyTransaction(any());
+ }
+
+ @Test
+ public void testSetCaptionInsets_withTask() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+ reset(mTaskViewTaskController);
+ reset(mOrganizer);
+
+ Rect insets = new Rect(0, 400, 0, 0);
+ mTaskView.setCaptionInsets(Insets.of(insets));
+ mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+ verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+ verify(mOrganizer).applyTransaction(any());
+ }
}
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
index 917276b..ad18a9d 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
@@ -81,5 +81,7 @@
Dismiss flow
</button>
<p id="dismiss_flow"></p>
+
+ <h2>Test <a href="http://www.google.com">hyperlink</a></h2>
</body>
</html>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 2530257d6..b100980 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -29,6 +29,7 @@
import android.view.KeyEvent;
import android.webkit.CookieManager;
import android.webkit.WebView;
+import android.webkit.WebViewClient;
import com.android.phone.slice.SlicePurchaseController;
@@ -113,8 +114,10 @@
return;
}
- // Create and configure WebView
- setupWebView();
+ // Clear any cookies that might be persisted from previous sessions before loading WebView
+ CookieManager.getInstance().removeAllCookies(value -> {
+ setupWebView();
+ });
}
protected void onPurchaseSuccessful() {
@@ -176,12 +179,7 @@
private void setupWebView() {
// Create WebView
mWebView = new WebView(this);
-
- // Clear any cookies and state that might be saved from previous sessions
- CookieManager.getInstance().removeAllCookies(null);
- CookieManager.getInstance().flush();
- mWebView.clearCache(true);
- mWebView.clearHistory();
+ mWebView.setWebViewClient(new WebViewClient());
// Enable JavaScript for the carrier purchase website to send results back to
// the slice purchase application.
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index 5c55a43..c037c40 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -42,7 +42,10 @@
name: "SettingsLibRoboTests",
srcs: ["src/**/*.java"],
static_libs: [
+ "Settings_robolectric_meta_service_file",
+ "Robolectric_shadows_androidx_fragment_upstream",
"SettingsLib-robo-testutils",
+ "androidx.fragment_fragment",
"androidx.test.core",
"androidx.core_core",
"testng", // TODO: remove once JUnit on Android provides assertThrows
@@ -53,6 +56,20 @@
test_options: {
timeout: 36000,
},
+ upstream: true,
+}
+
+java_genrule {
+ name: "Settings_robolectric_meta_service_file",
+ out: ["robolectric_meta_service_file.jar"],
+ tools: ["soong_zip"],
+ cmd: "mkdir -p $(genDir)/META-INF/services/ && touch $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider &&" +
+ "echo -e 'org.robolectric.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+ "echo -e 'org.robolectric.shadows.multidex.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+ "echo -e 'org.robolectric.shadows.httpclient.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+ //"echo -e 'com.android.settings.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+ "echo -e 'com.android.settingslib.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+ "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)/META-INF/services/",
}
java_library {
@@ -60,9 +77,23 @@
srcs: [
"testutils/com/android/settingslib/testutils/**/*.java",
],
-
+ javacflags: [
+ "-Aorg.robolectric.annotation.processing.shadowPackage=com.android.settingslib.testutils.shadow",
+ "-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
+ // Uncomment the below to debug annotation processors not firing.
+ //"-verbose",
+ //"-XprintRounds",
+ //"-XprintProcessorInfo",
+ //"-Xlint",
+ //"-J-verbose",
+ ],
+ plugins: [
+ "auto_value_plugin_1.9",
+ "auto_value_builder_plugin_1.9",
+ "Robolectric_processor_upstream",
+ ],
libs: [
- "Robolectric_all-target",
+ "Robolectric_all-target_upstream",
"mockito-robolectric-prebuilt",
"truth-prebuilt",
],
diff --git a/packages/SettingsLib/tests/robotests/config/robolectric.properties b/packages/SettingsLib/tests/robotests/config/robolectric.properties
index fab7251..2a9e50d 100644
--- a/packages/SettingsLib/tests/robotests/config/robolectric.properties
+++ b/packages/SettingsLib/tests/robotests/config/robolectric.properties
@@ -1 +1,2 @@
sdk=NEWEST_SDK
+instrumentedPackages=androidx.preference
diff --git a/packages/SettingsLib/tests/robotests/fragment/Android.bp b/packages/SettingsLib/tests/robotests/fragment/Android.bp
new file mode 100644
index 0000000..3e67156
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/Android.bp
@@ -0,0 +1,40 @@
+//#############################################
+// Compile Robolectric shadows framework misapplied to androidx
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library {
+ name: "Robolectric_shadows_androidx_fragment_upstream",
+ srcs: [
+ "src/main/java/**/*.java",
+ "src/main/java/**/*.kt",
+ ],
+ javacflags: [
+ "-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment",
+ "-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
+ // Uncomment the below to debug annotation processors not firing.
+ //"-verbose",
+ //"-XprintRounds",
+ //"-XprintProcessorInfo",
+ //"-Xlint",
+ //"-J-verbose",
+ ],
+ libs: [
+ "Robolectric_all-target_upstream",
+ "androidx.fragment_fragment",
+ ],
+ plugins: [
+ "auto_value_plugin_1.9",
+ "auto_value_builder_plugin_1.9",
+ "Robolectric_processor_upstream",
+ ],
+
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/BUILD b/packages/SettingsLib/tests/robotests/fragment/BUILD
new file mode 100644
index 0000000..393a02e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/BUILD
@@ -0,0 +1,69 @@
+load("//third_party/java/android/android_sdk_linux/extras/android/compatibility/jetify:jetify.bzl", "jetify_android_library", "jetify_android_local_test")
+
+package(
+ default_applicable_licenses = ["//third_party/java_src/robolectric:license"],
+ default_visibility = ["//third_party/java_src/robolectric:__subpackages__"],
+)
+
+licenses(["notice"])
+
+#==============================================================================
+# Test resources library
+#==============================================================================
+jetify_android_library(
+ name = "test_resources",
+ custom_package = "org.robolectric.shadows.androidx.fragment",
+ manifest = "src/test/AndroidManifest.xml",
+ resource_files = glob(
+ ["src/test/resources/**/*"],
+ ),
+)
+
+#==============================================================================
+# AndroidX fragment module library
+#==============================================================================
+jetify_android_library(
+ name = "androidx_fragment",
+ testonly = 1,
+ srcs = glob(
+ ["src/main/java/**"],
+ ),
+ custom_package = "org.robolectric.shadows.androidx.fragment",
+ javacopts = [
+ "-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment",
+ ],
+ jetify_sources = True,
+ plugins = [
+ "//java/com/google/thirdparty/robolectric/processor",
+ ],
+ deps = [
+ "//third_party/java/androidx/core",
+ "//third_party/java/androidx/fragment",
+ "//third_party/java/androidx/lifecycle",
+ "//third_party/java_src/robolectric/shadowapi",
+ "//third_party/java_src/robolectric/shadows/framework",
+ ],
+)
+
+[
+ jetify_android_local_test(
+ name = "test_" + src.rstrip(".java"),
+ size = "small",
+ srcs = glob(
+ ["src/test/java/**/*.java"],
+ ),
+ jetify_sources = True,
+ deps = [
+ ":androidx_fragment",
+ ":test_resources",
+ "//third_party/java/androidx/fragment",
+ "//third_party/java/androidx/loader",
+ "//third_party/java/mockito",
+ "//third_party/java/robolectric",
+ "//third_party/java/truth",
+ ],
+ )
+ for src in glob(
+ ["src/test/java/**/*Test.java"],
+ )
+]
diff --git a/packages/SettingsLib/tests/robotests/fragment/build.gradle b/packages/SettingsLib/tests/robotests/fragment/build.gradle
new file mode 100644
index 0000000..d9dcd84
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/build.gradle
@@ -0,0 +1,48 @@
+plugins {
+ id "net.ltgt.errorprone" version "0.0.13"
+}
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 28
+
+ android {
+ sourceSets {
+ main {
+ res.srcDirs = ['src/test/resources/res']
+ }
+ }
+ testOptions {
+ unitTests {
+ includeAndroidResources = true
+ }
+ }
+ }
+}
+
+dependencies {
+ // Project dependencies
+ compileOnly project(":robolectric")
+
+ // Compile dependencies
+ compileOnly AndroidSdk.MAX_SDK.coordinates
+ compileOnly "androidx.core:core:1.0.0-rc02"
+ compileOnly 'androidx.fragment:fragment:1.0.0-rc02'
+ compileOnly "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01"
+ compileOnly "androidx.lifecycle:lifecycle-common:2.0.0-beta01"
+
+ // Testing dependencies
+ testImplementation "com.google.truth:truth:0.44"
+ testImplementation "org.mockito:mockito-core:2.5.4"
+ testImplementation "androidx.arch.core:core-common:2.0.0-beta01"
+ testImplementation "androidx.arch.core:core-runtime:2.0.0-rc01"
+ testImplementation "androidx.collection:collection:1.0.0-rc01"
+ testImplementation "androidx.core:core:1.0.0-rc02"
+ testImplementation 'androidx.fragment:fragment:1.0.0-rc02'
+ testImplementation "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01"
+ testImplementation "androidx.lifecycle:lifecycle-common:2.0.0-beta01"
+ testImplementation "androidx.lifecycle:lifecycle-runtime:2.0.0-rc01"
+ testImplementation "androidx.lifecycle:lifecycle-livedata-core:2.0.0-rc01"
+ testImplementation "androidx.loader:loader:1.0.0-rc02"
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java
new file mode 100644
index 0000000..c688683
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2023 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 org.robolectric.shadows.androidx.fragment;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.android.controller.ComponentController;
+import org.robolectric.util.ReflectionHelpers;
+
+/** A Controller that can be used to drive the lifecycle of a {@link Fragment} */
+public class FragmentController<F extends Fragment>
+ extends ComponentController<FragmentController<F>, F> {
+
+ private final F mFragment;
+ private final ActivityController<? extends FragmentActivity> mActivityController;
+
+ private FragmentController(F fragment, Class<? extends FragmentActivity> activityClass) {
+ this(fragment, activityClass, null /*intent*/, null /*arguments*/);
+ }
+
+ private FragmentController(
+ F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
+ this(fragment, activityClass, intent, null /*arguments*/);
+ }
+
+ private FragmentController(
+ F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) {
+ this(fragment, activityClass, null /*intent*/, arguments);
+ }
+
+ private FragmentController(
+ F fragment,
+ Class<? extends FragmentActivity> activityClass,
+ Intent intent,
+ Bundle arguments) {
+ super(fragment, intent);
+ this.mFragment = fragment;
+ if (arguments != null) {
+ this.mFragment.setArguments(arguments);
+ }
+ this.mActivityController =
+ ActivityController.of(ReflectionHelpers.callConstructor(activityClass), intent);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(F fragment) {
+ return new FragmentController<>(fragment, FragmentControllerActivity.class);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment and intent.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param intent the intent which will be retained by activity
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(F fragment, Intent intent) {
+ return new FragmentController<>(fragment, FragmentControllerActivity.class, intent);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment and arguments.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param arguments the arguments which will be retained by fragment
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(F fragment, Bundle arguments) {
+ return new FragmentController<>(fragment, FragmentControllerActivity.class, arguments);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment and activity class.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param activityClass the activity which will be attached by fragment
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(
+ F fragment, Class<? extends FragmentActivity> activityClass) {
+ return new FragmentController<>(fragment, activityClass);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment, intent and arguments.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param intent the intent which will be retained by activity
+ * @param arguments the arguments which will be retained by fragment
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(
+ F fragment, Intent intent, Bundle arguments) {
+ return new FragmentController<>(fragment, FragmentControllerActivity.class, intent,
+ arguments);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment, activity class and intent.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param activityClass the activity which will be attached by fragment
+ * @param intent the intent which will be retained by activity
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(
+ F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
+ return new FragmentController<>(fragment, activityClass, intent);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment, activity class and arguments.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param activityClass the activity which will be attached by fragment
+ * @param arguments the arguments which will be retained by fragment
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(
+ F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) {
+ return new FragmentController<>(fragment, activityClass, arguments);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment, activity class, intent and
+ * arguments.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param activityClass the activity which will be attached by fragment
+ * @param intent the intent which will be retained by activity
+ * @param arguments the arguments which will be retained by fragment
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(
+ F fragment,
+ Class<? extends FragmentActivity> activityClass,
+ Intent intent,
+ Bundle arguments) {
+ return new FragmentController<>(fragment, activityClass, intent, arguments);
+ }
+
+ /**
+ * Sets up the given fragment by attaching it to an activity, calling its onCreate() through
+ * onResume() lifecycle methods, and then making it visible. Note that the fragment will be
+ * added
+ * to the view with ID 1.
+ */
+ public static <F extends Fragment> F setupFragment(F fragment) {
+ return FragmentController.of(fragment).create().start().resume().visible().get();
+ }
+
+ /**
+ * Sets up the given fragment by attaching it to an activity, calling its onCreate() through
+ * onResume() lifecycle methods, and then making it visible. Note that the fragment will be
+ * added
+ * to the view with ID 1.
+ */
+ public static <F extends Fragment> F setupFragment(
+ F fragment, Class<? extends FragmentActivity> fragmentActivityClass) {
+ return FragmentController.of(fragment, fragmentActivityClass)
+ .create()
+ .start()
+ .resume()
+ .visible()
+ .get();
+ }
+
+ /**
+ * Sets up the given fragment by attaching it to an activity created with the given bundle,
+ * calling its onCreate() through onResume() lifecycle methods, and then making it visible. Note
+ * that the fragment will be added to the view with ID 1.
+ */
+ public static <F extends Fragment> F setupFragment(
+ F fragment, Class<? extends FragmentActivity> fragmentActivityClass, Bundle bundle) {
+ return FragmentController.of(fragment, fragmentActivityClass)
+ .create(bundle)
+ .start()
+ .resume()
+ .visible()
+ .get();
+ }
+
+ /**
+ * Sets up the given fragment by attaching it to an activity created with the given bundle and
+ * container id, calling its onCreate() through onResume() lifecycle methods, and then making it
+ * visible.
+ */
+ public static <F extends Fragment> F setupFragment(
+ F fragment,
+ Class<? extends FragmentActivity> fragmentActivityClass,
+ int containerViewId,
+ Bundle bundle) {
+ return FragmentController.of(fragment, fragmentActivityClass)
+ .create(containerViewId, bundle)
+ .start()
+ .resume()
+ .visible()
+ .get();
+ }
+
+ /**
+ * Creates the activity with {@link Bundle} and adds the fragment to the view with ID {@code
+ * contentViewId}.
+ */
+ public FragmentController<F> create(final int contentViewId, final Bundle bundle) {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController
+ .create(bundle)
+ .get()
+ .getSupportFragmentManager()
+ .beginTransaction()
+ .add(contentViewId, mFragment)
+ .commit();
+ }
+ });
+ return this;
+ }
+
+ /**
+ * Creates the activity with {@link Bundle} and adds the fragment to it. Note that the fragment
+ * will be added to the view with ID 1.
+ */
+ public FragmentController<F> create(final Bundle bundle) {
+ return create(1, bundle);
+ }
+
+ /**
+ * Creates the {@link Fragment} in a newly initialized state and hence will receive a null
+ * savedInstanceState {@link Bundle parameter}
+ */
+ @Override
+ public FragmentController<F> create() {
+ return create(null);
+ }
+
+ /** Drive lifecycle of activity to Start lifetime */
+ public FragmentController<F> start() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.start();
+ }
+ });
+ return this;
+ }
+
+ /** Drive lifecycle of activity to Resume lifetime */
+ public FragmentController<F> resume() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.resume();
+ }
+ });
+ return this;
+ }
+
+ /** Drive lifecycle of activity to Pause lifetime */
+ public FragmentController<F> pause() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.pause();
+ }
+ });
+ return this;
+ }
+
+ /** Drive lifecycle of activity to Stop lifetime */
+ public FragmentController<F> stop() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.stop();
+ }
+ });
+ return this;
+ }
+
+ /** Drive lifecycle of activity to Destroy lifetime */
+ @Override
+ public FragmentController<F> destroy() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.destroy();
+ }
+ });
+ return this;
+ }
+
+ /** Let activity can be visible lifetime */
+ public FragmentController<F> visible() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.visible();
+ }
+ });
+ return this;
+ }
+
+ private static class FragmentControllerActivity extends FragmentActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout view = new LinearLayout(this);
+ view.setId(1);
+
+ setContentView(view);
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
new file mode 100644
index 0000000..dd89441
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+/**
+ * Testing infrastructure for androidx.fragment library.
+ *
+ * <p>To use this in your project, add the artifact {@code
+ * org.robolectric:shadows-androidx-fragment} to your project.
+ */
+package org.robolectric.shadows.androidx.fragment;
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..8493c02
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.robolectric.shadows.androidx.fragment">
+
+ <uses-sdk android:targetSdkVersion="28"/>
+</manifest>
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java b/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java
new file mode 100644
index 0000000..ef63058
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2023 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 org.robolectric.shadows.androidx.fragment;
+
+import static android.os.Looper.getMainLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for {@link FragmentController} */
+@RunWith(RobolectricTestRunner.class)
+public class FragmentControllerTest {
+
+ @After
+ public void tearDown() {
+ TranscriptFragment.clearLifecycleEvents();
+ }
+
+ @Test
+ public void initialNotAttached() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment());
+
+ assertThat(controller.get().getView()).isNull();
+ assertThat(controller.get().getActivity()).isNull();
+ assertThat(controller.get().isAdded()).isFalse();
+ }
+
+ @Test
+ public void initialNotAttached_customActivity() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ assertThat(controller.get().getView()).isNull();
+ assertThat(controller.get().getActivity()).isNull();
+ assertThat(controller.get().isAdded()).isFalse();
+ }
+
+ @Test
+ public void attachedAfterCreate() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment());
+
+ controller.create();
+ shadowOf(getMainLooper()).idle();
+
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isFalse();
+ }
+
+ @Test
+ public void attachedAfterCreate_customActivity() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ controller.create();
+ shadowOf(getMainLooper()).idle();
+
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().getActivity()).isInstanceOf(TestActivity.class);
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isFalse();
+ }
+
+ @Test
+ public void attachedAfterCreate_customizedViewId() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), CustomizedViewIdTestActivity.class);
+
+ controller.create(R.id.custom_activity_view, null).start();
+
+ assertThat(controller.get().getView()).isNotNull();
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isFalse();
+ assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull();
+ }
+
+ @Test
+ public void hasViewAfterStart() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment());
+
+ controller.create().start();
+
+ assertThat(controller.get().getView()).isNotNull();
+ }
+
+ @Test
+ public void isResumed() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ controller.create().start().resume();
+
+ assertThat(controller.get().getView()).isNotNull();
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isTrue();
+ assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull();
+ }
+
+ @Test
+ public void isPaused() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ controller.create().start().resume().pause();
+
+ assertThat(controller.get().getView()).isNotNull();
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isFalse();
+ assertThat(controller.get().getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume", "onPause")
+ .inOrder();
+ }
+
+ @Test
+ public void isStopped() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ controller.create().start().resume().pause().stop();
+
+ assertThat(controller.get().getView()).isNotNull();
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isFalse();
+ assertThat(controller.get().getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume", "onPause", "onStop")
+ .inOrder();
+ }
+
+ @Test
+ public void withIntent() {
+ final Intent intent = generateTestIntent();
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class, intent);
+
+ controller.create();
+ shadowOf(getMainLooper()).idle();
+ final Intent intentInFragment = controller.get().getActivity().getIntent();
+
+ assertThat(intentInFragment.getAction()).isEqualTo("test_action");
+ assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value");
+ }
+
+ @Test
+ public void withArguments() {
+ final Bundle bundle = generateTestBundle();
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class, bundle);
+
+ controller.create();
+ final Bundle args = controller.get().getArguments();
+
+ assertThat(args.getString("test_key")).isEqualTo("test_value");
+ }
+
+ @Test
+ public void withIntentAndArguments() {
+ final Bundle bundle = generateTestBundle();
+ final Intent intent = generateTestIntent();
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class, intent, bundle);
+
+ controller.create();
+ shadowOf(getMainLooper()).idle();
+ final Intent intentInFragment = controller.get().getActivity().getIntent();
+ final Bundle args = controller.get().getArguments();
+
+ assertThat(intentInFragment.getAction()).isEqualTo("test_action");
+ assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value");
+ assertThat(args.getString("test_key")).isEqualTo("test_value");
+ }
+
+ @Test
+ public void visible() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ controller.create().start().resume();
+
+ assertThat(controller.get().isVisible()).isFalse();
+
+ controller.visible();
+
+ assertThat(controller.get().isVisible()).isTrue();
+ }
+
+ @Test
+ public void setupFragmentWithFragment_fragmentHasCorrectLifecycle() {
+ TranscriptFragment fragment = FragmentController.setupFragment(new TranscriptFragment());
+
+ assertThat(fragment.getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume")
+ .inOrder();
+ assertThat(fragment.isVisible()).isTrue();
+ }
+
+ @Test
+ public void setupFragmentWithFragmentAndActivity_fragmentHasCorrectLifecycle() {
+ TranscriptFragment fragment =
+ FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class);
+
+ assertThat(fragment.getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume")
+ .inOrder();
+ assertThat(fragment.isVisible()).isTrue();
+ }
+
+ @Test
+ public void setupFragmentWithFragmentAndActivityAndBundle_HasCorrectLifecycle() {
+ Bundle testBundle = generateTestBundle();
+ TranscriptFragment fragment =
+ FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class,
+ testBundle);
+
+ assertThat(fragment.getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume")
+ .inOrder();
+ assertThat(fragment.isVisible()).isTrue();
+ }
+
+ @Test
+ public void
+ setupFragmentWithFragment_Activity_ContainViewIdAndBundle_HasCorrectLifecycle() {
+ Bundle testBundle = generateTestBundle();
+ TranscriptFragment fragment =
+ FragmentController.setupFragment(
+ new TranscriptFragment(),
+ CustomizedViewIdTestActivity.class,
+ R.id.custom_activity_view,
+ testBundle);
+
+ assertThat(fragment.getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume")
+ .inOrder();
+ assertThat(fragment.isVisible()).isTrue();
+ }
+
+ private Intent generateTestIntent() {
+ final Intent testIntent = new Intent("test_action").putExtra("test_key", "test_value");
+ return testIntent;
+ }
+
+ private Bundle generateTestBundle() {
+ final Bundle testBundle = new Bundle();
+ testBundle.putString("test_key", "test_value");
+
+ return testBundle;
+ }
+
+ /** A Fragment which can record lifecycle status for test. */
+ public static class TranscriptFragment extends Fragment {
+
+ public static final List<String> sLifecycleEvents = new ArrayList<>();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sLifecycleEvents.add("onCreate");
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ sLifecycleEvents.add("onStart");
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ sLifecycleEvents.add("onResume");
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ sLifecycleEvents.add("onPause");
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ sLifecycleEvents.add("onStop");
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_contents, container, false);
+ }
+
+ public List<String> getLifecycleEvents() {
+ return sLifecycleEvents;
+ }
+
+ public static void clearLifecycleEvents() {
+ sLifecycleEvents.clear();
+ }
+ }
+
+ /** A Activity which set a default view for test. */
+ public static class TestActivity extends FragmentActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout view = new LinearLayout(this);
+ view.setId(1);
+
+ setContentView(view);
+ }
+ }
+
+ /** A Activity which has a custom view for test. */
+ public static class CustomizedViewIdTestActivity extends FragmentActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.custom_activity_view);
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml
new file mode 100644
index 0000000..c074f30
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/custom_activity_view"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+</LinearLayout>
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml
new file mode 100644
index 0000000..425b2bb
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/tacos"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="TACOS"/>
+
+ <TextView
+ android:id="@+id/burritos"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="BURRITOS"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 4a913c8..bb72375 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -25,7 +25,6 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -58,12 +57,10 @@
import org.robolectric.shadows.ShadowSettings;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {UtilsTest.ShadowSecure.class, UtilsTest.ShadowLocationManager.class})
+@Config(shadows = {UtilsTest.ShadowLocationManager.class})
public class UtilsTest {
private static final double[] TEST_PERCENTAGES = {0, 0.4, 0.5, 0.6, 49, 49.3, 49.8, 50, 100};
private static final String TAG = "UtilsTest";
@@ -94,7 +91,7 @@
mContext = spy(RuntimeEnvironment.application);
when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
when(mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
- ShadowSecure.reset();
+ ShadowSettings.ShadowSecure.reset();
mAudioManager = mContext.getSystemService(AudioManager.class);
}
@@ -111,15 +108,16 @@
Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.LOCATION_CHANGER, Settings.Secure.LOCATION_CHANGER_UNKNOWN))
- .isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+ Settings.Secure.LOCATION_CHANGER,
+ Settings.Secure.LOCATION_CHANGER_UNKNOWN)).isEqualTo(
+ Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
}
@Test
public void testFormatPercentage_RoundTrue_RoundUpIfPossible() {
- final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1,
- PERCENTAGE_1, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50,
- PERCENTAGE_100};
+ final String[] expectedPercentages =
+ {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1, PERCENTAGE_1, PERCENTAGE_49,
+ PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50, PERCENTAGE_100};
for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], true);
@@ -129,9 +127,9 @@
@Test
public void testFormatPercentage_RoundFalse_NoRound() {
- final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0,
- PERCENTAGE_0, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50,
- PERCENTAGE_100};
+ final String[] expectedPercentages =
+ {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_49,
+ PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_100};
for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], false);
@@ -143,12 +141,7 @@
public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() {
Resources resources = mock(Resources.class);
when(resources.getInteger(
- eq(
- com.android
- .internal
- .R
- .integer
- .config_storageManagerDaystoRetainDefault)))
+ eq(com.android.internal.R.integer.config_storageManagerDaystoRetainDefault)))
.thenReturn(60);
assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
}
@@ -163,31 +156,6 @@
return intent -> TextUtils.equals(expected, intent.getAction());
}
- @Implements(value = Settings.Secure.class)
- public static class ShadowSecure extends ShadowSettings.ShadowSecure {
- private static Map<String, Integer> map = new HashMap<>();
-
- @Implementation
- public static boolean putIntForUser(ContentResolver cr, String name, int value,
- int userHandle) {
- map.put(name, value);
- return true;
- }
-
- @Implementation
- public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
- if (map.containsKey(name)) {
- return map.get(name);
- } else {
- return def;
- }
- }
-
- public static void reset() {
- map.clear();
- }
- }
-
@Implements(value = LocationManager.class)
public static class ShadowLocationManager {
@@ -337,9 +305,8 @@
@Test
public void getBatteryStatus_statusIsFull_returnFullString() {
- final Intent intent = new Intent()
- .putExtra(BatteryManager.EXTRA_LEVEL, 100)
- .putExtra(BatteryManager.EXTRA_SCALE, 100);
+ final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
+ BatteryManager.EXTRA_SCALE, 100);
final Resources resources = mContext.getResources();
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
@@ -348,9 +315,8 @@
@Test
public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
- final Intent intent = new Intent()
- .putExtra(BatteryManager.EXTRA_LEVEL, 100)
- .putExtra(BatteryManager.EXTRA_SCALE, 100);
+ final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
+ BatteryManager.EXTRA_SCALE, 100);
final Resources resources = mContext.getResources();
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
@@ -516,7 +482,6 @@
when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
when(mUsbPortStatus.isConnected()).thenReturn(true);
- when(mUsbPortStatus.getComplianceWarnings())
- .thenReturn(new int[]{complianceWarningType});
+ when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{complianceWarningType});
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
index 44fdaec..3de8446 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
@@ -23,13 +23,17 @@
import android.os.UserHandle;
import android.provider.Settings;
+import com.android.settingslib.testutils.shadow.ShadowSecure;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowSecure.class})
public class AccessibilityUtilsTest {
private Context mContext;
@@ -46,7 +50,7 @@
@Test
public void getEnabledServicesFromSettings_badFormat_emptyResult() {
- Settings.Secure.putStringForUser(
+ ShadowSecure.putStringForUser(
mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
":",
UserHandle.myUserId());
@@ -57,7 +61,7 @@
@Test
public void getEnabledServicesFromSettings_1Service_1result() {
final ComponentName cn = new ComponentName("pkg", "serv");
- Settings.Secure.putStringForUser(
+ ShadowSecure.putStringForUser(
mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
cn.flattenToString() + ":",
UserHandle.myUserId());
@@ -70,7 +74,7 @@
public void getEnabledServicesFromSettings_2Services_2results() {
final ComponentName cn1 = new ComponentName("pkg", "serv");
final ComponentName cn2 = new ComponentName("pkg", "serv2");
- Settings.Secure.putStringForUser(
+ ShadowSecure.putStringForUser(
mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
cn1.flattenToString() + ":" + cn2.flattenToString(),
UserHandle.myUserId());
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
index cb62a73..f9505dd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
@@ -37,6 +37,8 @@
import android.os.UserManager;
import android.util.LongSparseArray;
+import com.android.settingslib.testutils.shadow.ShadowPermissionChecker;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,7 +47,6 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowPermissionChecker;
import java.time.Clock;
import java.util.ArrayList;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index dd8d54a..a2e8c59 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -38,6 +38,7 @@
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
@@ -167,6 +168,7 @@
}
@Test
+ @LooperMode(LooperMode.Mode.PAUSED)
public void getAttribution_notSet_shouldReturnUnknown() {
final Activity activity = Robolectric.setupActivity(Activity.class);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
index d67d44b..25833b3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
@@ -36,6 +36,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.CujType;
+import com.android.settingslib.testutils.OverpoweredReflectionHelper;
import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
import org.junit.Before;
@@ -51,7 +52,6 @@
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
-import org.robolectric.util.ReflectionHelpers;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -83,8 +83,10 @@
public void setUp() {
ShadowInteractionJankMonitor.reset();
when(ShadowInteractionJankMonitor.MOCK_INSTANCE.begin(any())).thenReturn(true);
- ReflectionHelpers.setStaticField(SettingsJankMonitor.class, "scheduledExecutorService",
- mScheduledExecutorService);
+ OverpoweredReflectionHelper
+ .setStaticField(SettingsJankMonitor.class,
+ "scheduledExecutorService",
+ mScheduledExecutorService);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
index cf702b53..471dac0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
@@ -37,8 +37,10 @@
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.LooperMode;
@RunWith(RobolectricTestRunner.class)
+@LooperMode(LooperMode.Mode.PAUSED)
public class HideNonSystemOverlayMixinTest {
private ActivityController<TestActivity> mActivityController;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
index 3475ff7..b009abd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
@@ -22,13 +22,14 @@
import android.os.UserManager;
import android.provider.Settings;
+import com.android.settingslib.testutils.shadow.ShadowUserManager;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowUserManager;
@RunWith(RobolectricTestRunner.class)
public class DevelopmentSettingsEnablerTest {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index 8e33ca3..0cabab2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -21,6 +21,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.LooperMode;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
@@ -269,6 +270,7 @@
}
@Test
+ @LooperMode(LooperMode.Mode.PAUSED)
public void testGenerateHtmlWithCustomHeading() throws Exception {
List<File> xmlFiles = new ArrayList<>();
Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
@@ -292,6 +294,7 @@
}
@Test
+ @LooperMode(LooperMode.Mode.PAUSED)
public void testGenerateNewHtmlWithCustomHeading() throws Exception {
List<File> xmlFiles = new ArrayList<>();
Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
new file mode 100644
index 0000000..9e9725f
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@LooperMode(LooperMode.Mode.LEGACY)
+package com.android.settingslib;
+
+import org.robolectric.annotation.LooperMode;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
index d41d511..faec02f7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
@@ -27,6 +27,7 @@
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.LooperMode;
@RunWith(RobolectricTestRunner.class)
public class AnimatedImageViewTest {
@@ -40,6 +41,7 @@
}
@Test
+ @LooperMode(LooperMode.Mode.PAUSED)
public void testAnimation_ViewVisible_AnimationRunning() {
mAnimatedImageView.setVisibility(View.VISIBLE);
mAnimatedImageView.setAnimating(true);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
index 0a48f19..0d88913 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
@@ -41,6 +41,8 @@
import androidx.preference.PreferenceViewHolder;
import androidx.preference.R;
+import com.android.settingslib.testutils.OverpoweredReflectionHelper;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -502,14 +504,18 @@
private void assumeAndroidR() {
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 30);
ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "R");
- ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false);
+ OverpoweredReflectionHelper
+ .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false);
// Reset view holder to use correct layout.
}
+
+
private void assumeAndroidS() {
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 31);
ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "S");
- ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true);
+ OverpoweredReflectionHelper
+ .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true);
// Re-inflate view to update layout.
setUpViewHolder();
}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java
new file mode 100644
index 0000000..4fcc5a1
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 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.settingslib.testutils;
+
+import org.robolectric.util.ReflectionHelpers;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+public class OverpoweredReflectionHelper extends ReflectionHelpers {
+
+ /**
+ * Robolectric upstream does not rely on or encourage this behaviour.
+ *
+ * @param field
+ */
+ private static void makeFieldVeryAccessible(Field field) {
+ field.setAccessible(true);
+ // remove 'final' modifier if present
+ if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
+ Field modifiersField = getModifiersField();
+ modifiersField.setAccessible(true);
+ try {
+ modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+ } catch (IllegalAccessException e) {
+
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ private static Field getModifiersField() {
+ try {
+ return Field.class.getDeclaredField("modifiers");
+ } catch (NoSuchFieldException e) {
+ try {
+ Method getFieldsMethod =
+ Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
+ getFieldsMethod.setAccessible(true);
+ Field[] fields = (Field[]) getFieldsMethod.invoke(Field.class, false);
+ for (Field modifiersField : fields) {
+ if ("modifiers".equals(modifiersField.getName())) {
+ return modifiersField;
+ }
+ }
+ } catch (ReflectiveOperationException innerE) {
+ throw new AssertionError(innerE);
+ }
+ }
+ throw new AssertionError();
+ }
+
+ /**
+ * Reflectively set the value of a static field.
+ *
+ * @param field Field object.
+ * @param fieldNewValue The new value.
+ */
+ public static void setStaticField(Field field, Object fieldNewValue) {
+ try {
+ makeFieldVeryAccessible(field);
+ field.setAccessible(true);
+ field.set(null, fieldNewValue);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Reflectively set the value of a static field.
+ *
+ * @param clazz Target class.
+ * @param fieldName The field name.
+ * @param fieldNewValue The new value.
+ */
+ public static void setStaticField(Class<?> clazz, String fieldName, Object fieldNewValue) {
+ try {
+ setStaticField(clazz.getDeclaredField(fieldName), fieldNewValue);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
index 924eb04..0b9ba8d 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
@@ -16,23 +16,27 @@
package com.android.settingslib.testutils.shadow;
+import static android.os.Build.VERSION_CODES.O;
+
import android.app.ActivityManager;
+import android.app.IActivityManager;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
@Implements(ActivityManager.class)
public class ShadowActivityManager {
private static int sCurrentUserId = 0;
- private int mUserSwitchedTo = -1;
+ private static int sUserSwitchedTo = -1;
@Resetter
- public void reset() {
+ public static void reset() {
sCurrentUserId = 0;
- mUserSwitchedTo = 0;
+ sUserSwitchedTo = 0;
}
@Implementation
@@ -42,16 +46,21 @@
@Implementation
protected boolean switchUser(int userId) {
- mUserSwitchedTo = userId;
+ sUserSwitchedTo = userId;
return true;
}
+ @Implementation(minSdk = O)
+ protected static IActivityManager getService() {
+ return ReflectionHelpers.createNullProxy(IActivityManager.class);
+ }
+
public boolean getSwitchUserCalled() {
- return mUserSwitchedTo != -1;
+ return sUserSwitchedTo != -1;
}
public int getUserSwitchedTo() {
- return mUserSwitchedTo;
+ return sUserSwitchedTo;
}
public static void setCurrentUser(int userId) {
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
index 2c0792f..bbfdb7f 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
@@ -29,7 +29,7 @@
private static String sDefaultDialer;
@Resetter
- public void reset() {
+ public static void reset() {
sDefaultDialer = null;
}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
new file mode 100644
index 0000000..fae3aea
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 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.settingslib.testutils.shadow;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.PermissionChecker;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.HashMap;
+import java.util.Map;
+/** Shadow class of {@link PermissionChecker}. */
+@Implements(PermissionChecker.class)
+public class ShadowPermissionChecker {
+ private static final Map<String, Map<String, Integer>> RESULTS = new HashMap<>();
+ /** Set the result of permission check for a specific permission. */
+ public static void setResult(String packageName, String permission, int result) {
+ if (!RESULTS.containsKey(packageName)) {
+ RESULTS.put(packageName, new HashMap<>());
+ }
+ RESULTS.get(packageName).put(permission, result);
+ }
+ /** Check the permission of calling package. */
+ @Implementation
+ public static int checkCallingPermissionForDataDelivery(
+ Context context,
+ String permission,
+ String packageName,
+ String attributionTag,
+ String message) {
+ return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+ ? RESULTS.get(packageName).get(permission)
+ : PermissionChecker.checkCallingPermissionForDataDelivery(
+ context, permission, packageName, attributionTag, message);
+ }
+ /** Check general permission. */
+ @Implementation
+ public static int checkPermissionForDataDelivery(
+ Context context,
+ String permission,
+ int pid,
+ int uid,
+ String packageName,
+ String attributionTag,
+ String message) {
+ return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+ ? RESULTS.get(packageName).get(permission)
+ : PermissionChecker.checkPermissionForDataDelivery(
+ context, permission, pid, uid, packageName, attributionTag, message);
+ }
+ /** Check general permission. */
+ @Implementation
+ public static int checkPermissionForPreflight(@NonNull Context context,
+ @NonNull String permission, int pid, int uid, @Nullable String packageName) {
+ return checkPermissionForPreflight(context, permission, new AttributionSource(
+ uid, packageName, null /*attributionTag*/));
+ }
+ /** Check general permission. */
+ @Implementation
+ public static int checkPermissionForPreflight(@NonNull Context context,
+ @NonNull String permission, @NonNull AttributionSource attributionSource) {
+ final String packageName = attributionSource.getPackageName();
+ return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+ ? RESULTS.get(packageName).get(permission)
+ : PermissionChecker.checkPermissionForPreflight(
+ context, permission, attributionSource);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java
new file mode 100644
index 0000000..70ebc67
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.settingslib.testutils.shadow;
+
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowSettings;
+
+@Implements(value = Settings.Secure.class)
+public class ShadowSecure extends ShadowSettings.ShadowSecure {
+ @Implementation(minSdk = JELLY_BEAN_MR1)
+ public static boolean putStringForUser(ContentResolver cr, String name, String value,
+ int userHandle) {
+ return putString(cr, name, value);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
index 381d072..5ac0a87 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
@@ -31,7 +31,7 @@
private static ComponentName sDefaultSmsApplication;
@Resetter
- public void reset() {
+ public static void reset() {
sDefaultSmsApplication = null;
}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
index ca1eefc..60d7721 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
@@ -16,20 +16,28 @@
package com.android.settingslib.testutils.shadow;
+import static android.os.Build.VERSION_CODES.N_MR1;
+
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
+import android.os.UserHandle;
import android.os.UserManager;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowBuild;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@Implements(value = UserManager.class)
public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
private List<UserInfo> mUserInfos = addProfile(0, "Owner");
+ private final Map<Integer, UserProperties> mUserPropertiesMap = new HashMap<>();
@Implementation
protected static UserManager get(Context context) {
@@ -62,4 +70,37 @@
protected List<UserInfo> getProfiles(@UserIdInt int userHandle) {
return getProfiles();
}
+
+ /**
+ * @return {@code false} by default, or the value specified via {@link #setIsAdminUser(boolean)}
+ */
+ @Implementation(minSdk = N_MR1)
+ public boolean isAdminUser() {
+ return getUserInfo(UserHandle.myUserId()).isAdmin();
+ }
+
+ /**
+ * Sets that the current user is an admin user; controls the return value of
+ * {@link UserManager#isAdminUser}.
+ */
+ public void setIsAdminUser(boolean isAdminUser) {
+ UserInfo userInfo = getUserInfo(UserHandle.myUserId());
+ if (isAdminUser) {
+ userInfo.flags |= UserInfo.FLAG_ADMIN;
+ } else {
+ userInfo.flags &= ~UserInfo.FLAG_ADMIN;
+ }
+ }
+
+ public void setupUserProperty(int userId, int showInSettings) {
+ UserProperties userProperties = new UserProperties(new UserProperties.Builder()
+ .setShowInSettings(showInSettings).build());
+ mUserPropertiesMap.putIfAbsent(userId, userProperties);
+ }
+
+ @Implementation(minSdk = ShadowBuild.UPSIDE_DOWN_CAKE)
+ protected UserProperties getUserProperties(UserHandle user) {
+ return mUserPropertiesMap.getOrDefault(user.getIdentifier(),
+ new UserProperties(new UserProperties.Builder().build()));
+ }
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index a443b5c..73fb0f0 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -65,6 +65,7 @@
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
"androidx.activity_activity-compose",
+ "androidx.compose.animation_animation-graphics",
],
// By default, Compose is disabled and we compile the ComposeFacade
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index e255f5c..8ef6c2e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -56,6 +56,7 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.biometrics.UdfpsController;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -113,6 +114,7 @@
@NonNull private final VibratorHelper mVibrator;
@Nullable private final AuthRippleController mAuthRippleController;
@NonNull private final FeatureFlags mFeatureFlags;
+ @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@NonNull private final KeyguardTransitionInteractor mTransitionInteractor;
@NonNull private final KeyguardInteractor mKeyguardInteractor;
@@ -181,7 +183,8 @@
@NonNull @Main Resources resources,
@NonNull KeyguardTransitionInteractor transitionInteractor,
@NonNull KeyguardInteractor keyguardInteractor,
- @NonNull FeatureFlags featureFlags
+ @NonNull FeatureFlags featureFlags,
+ PrimaryBouncerInteractor primaryBouncerInteractor
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -198,6 +201,7 @@
mTransitionInteractor = transitionInteractor;
mKeyguardInteractor = keyguardInteractor;
mFeatureFlags = featureFlags;
+ mPrimaryBouncerInteractor = primaryBouncerInteractor;
mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
@@ -326,8 +330,14 @@
mView.setContentDescription(null);
}
+ boolean accessibilityEnabled =
+ !mPrimaryBouncerInteractor.isAnimatingAway() && mView.isVisibleToUser();
+ mView.setImportantForAccessibility(
+ accessibilityEnabled ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+
if (!Objects.equals(prevContentDescription, mView.getContentDescription())
- && mView.getContentDescription() != null && mView.isVisibleToUser()) {
+ && mView.getContentDescription() != null && accessibilityEnabled) {
mView.announceForAccessibility(mView.getContentDescription());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index de7a669..ff395da 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -36,6 +36,7 @@
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.hardware.graphics.common.AlphaInterpretation;
@@ -171,7 +172,7 @@
private int mTintColor = Color.BLACK;
@VisibleForTesting
protected DisplayDecorationSupport mHwcScreenDecorationSupport;
- private Display.Mode mDisplayMode;
+ private final Point mDisplaySize = new Point();
@VisibleForTesting
protected DisplayInfo mDisplayInfo = new DisplayInfo();
private DisplayCutout mDisplayCutout;
@@ -484,7 +485,8 @@
mWindowManager = mContext.getSystemService(WindowManager.class);
mContext.getDisplay().getDisplayInfo(mDisplayInfo);
mRotation = mDisplayInfo.rotation;
- mDisplayMode = mDisplayInfo.getMode();
+ mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+ mDisplaySize.y = mDisplayInfo.getNaturalHeight();
mDisplayUniqueId = mDisplayInfo.uniqueId;
mDisplayCutout = mDisplayInfo.displayCutout;
mRoundedCornerResDelegate =
@@ -505,10 +507,12 @@
public void onDisplayChanged(int displayId) {
mContext.getDisplay().getDisplayInfo(mDisplayInfo);
final int newRotation = mDisplayInfo.rotation;
- final Display.Mode newDisplayMode = mDisplayInfo.getMode();
if ((mOverlays != null || mScreenDecorHwcWindow != null)
&& (mRotation != newRotation
- || displayModeChanged(mDisplayMode, newDisplayMode))) {
+ || displaySizeChanged(mDisplaySize, mDisplayInfo))) {
+ final Point newSize = new Point();
+ newSize.x = mDisplayInfo.getNaturalWidth();
+ newSize.y = mDisplayInfo.getNaturalHeight();
// We cannot immediately update the orientation. Otherwise
// WindowManager is still deferring layout until it has finished dispatching
// the config changes, which may cause divergence between what we draw
@@ -520,9 +524,8 @@
if (mRotation != newRotation) {
mLogger.logRotationChangeDeferred(mRotation, newRotation);
}
- if (displayModeChanged(mDisplayMode, newDisplayMode)) {
- mLogger.logDisplayModeChanged(
- newDisplayMode.getModeId(), mDisplayMode.getModeId());
+ if (!mDisplaySize.equals(newSize)) {
+ mLogger.logDisplaySizeChanged(mDisplaySize, newSize);
}
if (mOverlays != null) {
@@ -531,7 +534,7 @@
final ViewGroup overlayView = mOverlays[i].getRootView();
overlayView.getViewTreeObserver().addOnPreDrawListener(
new RestartingPreDrawListener(
- overlayView, i, newRotation, newDisplayMode));
+ overlayView, i, newRotation, newSize));
}
}
}
@@ -541,7 +544,7 @@
new RestartingPreDrawListener(
mScreenDecorHwcWindow,
-1, // Pass -1 for views with no specific position.
- newRotation, newDisplayMode));
+ newRotation, newSize));
}
if (mScreenDecorHwcLayer != null) {
mScreenDecorHwcLayer.pendingConfigChange = true;
@@ -943,15 +946,8 @@
}
}
- private static boolean displayModeChanged(Display.Mode oldMode, Display.Mode newMode) {
- if (oldMode == null) {
- return true;
- }
-
- // We purposely ignore refresh rate and id changes here, because we don't need to
- // invalidate for those, and they can trigger the refresh rate to increase
- return oldMode.getPhysicalWidth() != newMode.getPhysicalWidth()
- || oldMode.getPhysicalHeight() != newMode.getPhysicalHeight();
+ private static boolean displaySizeChanged(Point size, DisplayInfo info) {
+ return size.x != info.getNaturalWidth() || size.y != info.getNaturalHeight();
}
private int getOverlayWindowGravity(@BoundsPosition int pos) {
@@ -1170,14 +1166,14 @@
if (mRotation != newRotation) {
mDotViewController.setNewRotation(newRotation);
}
- final Display.Mode newMod = mDisplayInfo.getMode();
final DisplayCutout newCutout = mDisplayInfo.displayCutout;
if (!mPendingConfigChange
- && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod)
+ && (newRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo)
|| !Objects.equals(newCutout, mDisplayCutout))) {
mRotation = newRotation;
- mDisplayMode = newMod;
+ mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+ mDisplaySize.y = mDisplayInfo.getNaturalHeight();
mDisplayCutout = newCutout;
float ratio = getPhysicalPixelDisplaySizeRatio();
mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(ratio);
@@ -1494,31 +1490,29 @@
private final View mView;
private final int mTargetRotation;
- private final Display.Mode mTargetDisplayMode;
+ private final Point mTargetDisplaySize;
// Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific
// position.
private final int mPosition;
private RestartingPreDrawListener(View view, @BoundsPosition int position,
- int targetRotation, Display.Mode targetDisplayMode) {
+ int targetRotation, Point targetDisplaySize) {
mView = view;
mTargetRotation = targetRotation;
- mTargetDisplayMode = targetDisplayMode;
+ mTargetDisplaySize = targetDisplaySize;
mPosition = position;
}
@Override
public boolean onPreDraw() {
mView.getViewTreeObserver().removeOnPreDrawListener(this);
- if (mTargetRotation == mRotation
- && !displayModeChanged(mDisplayMode, mTargetDisplayMode)) {
+ if (mTargetRotation == mRotation && mDisplaySize.equals(mTargetDisplaySize)) {
if (DEBUG_LOGGING) {
final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
: getWindowTitleByPos(mPosition);
Log.i(TAG, title + " already in target rot "
+ mTargetRotation + " and in target resolution "
- + mTargetDisplayMode.getPhysicalWidth() + "x"
- + mTargetDisplayMode.getPhysicalHeight()
+ + mTargetDisplaySize.x + "x" + mTargetDisplaySize.y
+ ", allow draw without restarting it");
}
return true;
@@ -1533,8 +1527,7 @@
: getWindowTitleByPos(mPosition);
Log.i(TAG, title
+ " restarting listener fired, restarting draw for rot " + mRotation
- + ", resolution " + mDisplayMode.getPhysicalWidth() + "x"
- + mDisplayMode.getPhysicalHeight());
+ + ", resolution " + mDisplaySize.x + "x" + mDisplaySize.y);
}
mView.invalidate();
return false;
@@ -1560,19 +1553,18 @@
public boolean onPreDraw() {
mContext.getDisplay().getDisplayInfo(mDisplayInfo);
final int displayRotation = mDisplayInfo.rotation;
- final Display.Mode displayMode = mDisplayInfo.getMode();
- if ((displayRotation != mRotation || displayModeChanged(mDisplayMode, displayMode))
+ if ((displayRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo))
&& !mPendingConfigChange) {
if (DEBUG_LOGGING) {
if (displayRotation != mRotation) {
Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
+ displayRotation + ". Restarting draw");
}
- if (displayModeChanged(mDisplayMode, displayMode)) {
- Log.i(TAG, "Drawing at " + mDisplayMode.getPhysicalWidth()
- + "x" + mDisplayMode.getPhysicalHeight() + ", but display is at "
- + displayMode.getPhysicalWidth() + "x"
- + displayMode.getPhysicalHeight() + ". Restarting draw");
+ if (displaySizeChanged(mDisplaySize, mDisplayInfo)) {
+ Log.i(TAG, "Drawing at " + mDisplaySize.x + "x" + mDisplaySize.y
+ + ", but display is at "
+ + mDisplayInfo.getNaturalWidth() + "x"
+ + mDisplayInfo.getNaturalHeight() + ". Restarting draw");
}
}
mView.invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index e6aeb43..802eea3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -37,4 +37,8 @@
dumpManager
) {
override val tag = "UdfpsBpViewController"
+
+ override fun shouldPauseAuth(): Boolean {
+ return false
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 40db63d..b1f513d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -42,6 +42,7 @@
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
import com.android.systemui.power.PowerUI
import com.android.systemui.reardisplay.RearDisplayDialogController
import com.android.systemui.recents.Recents
@@ -111,6 +112,14 @@
@ClassKey(KeyboardUI::class)
abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable
+ /** Inject into MediaProjectionTaskSwitcherCoreStartable. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaProjectionTaskSwitcherCoreStartable::class)
+ abstract fun bindProjectedTaskListener(
+ sysui: MediaProjectionTaskSwitcherCoreStartable
+ ): CoreStartable
+
/** Inject into KeyguardBiometricLockoutLogger */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index ead24ae..3b89739 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -59,6 +59,7 @@
import com.android.systemui.log.dagger.MonitorLog;
import com.android.systemui.log.table.TableLogBuffer;
import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.motiontool.MotionToolModule;
import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -182,6 +183,7 @@
LockscreenLayoutModule.class,
LogModule.class,
MediaProjectionModule.class,
+ MediaProjectionTaskSwitcherModule.class,
MotionToolModule.class,
PeopleHubModule.class,
PeopleModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
index 150de26..702a23e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -189,15 +189,15 @@
)
}
- fun logDisplayModeChanged(currentMode: Int, newMode: Int) {
+ fun logDisplaySizeChanged(currentSize: Point, newSize: Point) {
logBuffer.log(
TAG,
INFO,
{
- int1 = currentMode
- int2 = newMode
+ str1 = currentSize.flattenToString()
+ str2 = newSize.flattenToString()
},
- { "Resolution changed, deferring mode change to $int2, staying at $int1" },
+ { "Resolution changed, deferring size change to $str2, staying at $str1" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
new file mode 100644
index 0000000..3c50127
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import javax.inject.Inject
+
+@SysUISingleton
+class MediaProjectionTaskSwitcherCoreStartable
+@Inject
+constructor(
+ private val notificationCoordinator: TaskSwitcherNotificationCoordinator,
+ private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+ override fun start() {
+ if (featureFlags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)) {
+ notificationCoordinator.start()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
new file mode 100644
index 0000000..22ad07e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher
+
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface MediaProjectionTaskSwitcherModule {
+
+ @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository
+
+ @Binds fun tasksRepository(impl: ActivityTaskManagerTasksRepository): TasksRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
new file mode 100644
index 0000000..9938f11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.data.model
+
+import android.app.TaskInfo
+
+/** Represents the state of media projection. */
+sealed interface MediaProjectionState {
+ object NotProjecting : MediaProjectionState
+ object EntireScreen : MediaProjectionState
+ data class SingleTask(val task: TaskInfo) : MediaProjectionState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
new file mode 100644
index 0000000..492d482
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.os.IBinder
+import android.util.Log
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.withContext
+
+/** Implementation of [TasksRepository] that uses [ActivityTaskManager] as the data source. */
+@SysUISingleton
+class ActivityTaskManagerTasksRepository
+@Inject
+constructor(
+ private val activityTaskManager: ActivityTaskManager,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : TasksRepository {
+
+ override suspend fun findRunningTaskFromWindowContainerToken(
+ windowContainerToken: IBinder
+ ): RunningTaskInfo? =
+ getRunningTasks().firstOrNull { taskInfo ->
+ taskInfo.token.asBinder() == windowContainerToken
+ }
+
+ private suspend fun getRunningTasks(): List<RunningTaskInfo> =
+ withContext(backgroundDispatcher) { activityTaskManager.getTasks(Integer.MAX_VALUE) }
+
+ override val foregroundTask: Flow<RunningTaskInfo> =
+ conflatedCallbackFlow {
+ val listener =
+ object : TaskStackListener() {
+ override fun onTaskMovedToFront(taskInfo: RunningTaskInfo) {
+ Log.d(TAG, "onTaskMovedToFront: $taskInfo")
+ trySendWithFailureLogging(taskInfo, TAG)
+ }
+ }
+ activityTaskManager.registerTaskStackListener(listener)
+ awaitClose { activityTaskManager.unregisterTaskStackListener(listener) }
+ }
+ .shareIn(applicationScope, SharingStarted.Lazily, replay = 1)
+
+ companion object {
+ private const val TAG = "TasksRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
new file mode 100644
index 0000000..38d4e69
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.util.Log
+import android.view.ContentRecordingSession
+import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class MediaProjectionManagerRepository
+@Inject
+constructor(
+ private val mediaProjectionManager: MediaProjectionManager,
+ @Main private val handler: Handler,
+ @Application private val applicationScope: CoroutineScope,
+ private val tasksRepository: TasksRepository,
+) : MediaProjectionRepository {
+
+ override val mediaProjectionState: Flow<MediaProjectionState> =
+ conflatedCallbackFlow {
+ val callback =
+ object : MediaProjectionManager.Callback() {
+ override fun onStart(info: MediaProjectionInfo?) {
+ Log.d(TAG, "MediaProjectionManager.Callback#onStart")
+ trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+ }
+
+ override fun onStop(info: MediaProjectionInfo?) {
+ Log.d(TAG, "MediaProjectionManager.Callback#onStop")
+ trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+ }
+
+ override fun onRecordingSessionSet(
+ info: MediaProjectionInfo,
+ session: ContentRecordingSession?
+ ) {
+ Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session")
+ launch { trySendWithFailureLogging(stateForSession(session), TAG) }
+ }
+ }
+ mediaProjectionManager.addCallback(callback, handler)
+ awaitClose { mediaProjectionManager.removeCallback(callback) }
+ }
+ .shareIn(scope = applicationScope, started = SharingStarted.Lazily, replay = 1)
+
+ private suspend fun stateForSession(session: ContentRecordingSession?): MediaProjectionState {
+ if (session == null) {
+ return MediaProjectionState.NotProjecting
+ }
+ if (session.contentToRecord == RECORD_CONTENT_DISPLAY || session.tokenToRecord == null) {
+ return MediaProjectionState.EntireScreen
+ }
+ val matchingTask =
+ tasksRepository.findRunningTaskFromWindowContainerToken(session.tokenToRecord)
+ ?: return MediaProjectionState.EntireScreen
+ return MediaProjectionState.SingleTask(matchingTask)
+ }
+
+ companion object {
+ private const val TAG = "MediaProjectionMngrRepo"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
new file mode 100644
index 0000000..5bec692
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+
+/** Represents a repository to retrieve and change data related to media projection. */
+interface MediaProjectionRepository {
+
+ /** Represents the current [MediaProjectionState]. */
+ val mediaProjectionState: Flow<MediaProjectionState>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
new file mode 100644
index 0000000..544eb6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * No-op implementation of [MediaProjectionRepository] that does nothing. Currently used as a
+ * placeholder, while the real implementation is not completed.
+ */
+@SysUISingleton
+class NoOpMediaProjectionRepository @Inject constructor() : MediaProjectionRepository {
+
+ override val mediaProjectionState: Flow<MediaProjectionState> = emptyFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
new file mode 100644
index 0000000..6a535e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.os.IBinder
+import kotlinx.coroutines.flow.Flow
+
+/** Repository responsible for retrieving data related to running tasks. */
+interface TasksRepository {
+
+ /**
+ * Tries to find a [RunningTaskInfo] with a matching window container token. Returns `null` when
+ * no matching task was found.
+ */
+ suspend fun findRunningTaskFromWindowContainerToken(
+ windowContainerToken: IBinder
+ ): RunningTaskInfo?
+
+ /**
+ * Emits a stream of [RunningTaskInfo] that have been moved to the foreground.
+ *
+ * Note: when subscribing for the first time, it will not immediately emit the current
+ * foreground task. Only after a change in foreground task has occurred.
+ */
+ val foregroundTask: Flow<RunningTaskInfo>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
new file mode 100644
index 0000000..fc5cf7d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.domain.interactor
+
+import android.app.TaskInfo
+import android.content.Intent
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Interactor with logic related to task switching in the context of media projection. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class TaskSwitchInteractor
+@Inject
+constructor(
+ mediaProjectionRepository: MediaProjectionRepository,
+ private val tasksRepository: TasksRepository,
+) {
+
+ /**
+ * Emits a stream of changes to the state of task switching, in the context of media projection.
+ */
+ val taskSwitchChanges: Flow<TaskSwitchState> =
+ mediaProjectionRepository.mediaProjectionState.flatMapLatest { projectionState ->
+ Log.d(TAG, "MediaProjectionState -> $projectionState")
+ when (projectionState) {
+ is MediaProjectionState.SingleTask -> {
+ val projectedTask = projectionState.task
+ tasksRepository.foregroundTask.map { foregroundTask ->
+ if (hasForegroundTaskSwitched(projectedTask, foregroundTask)) {
+ TaskSwitchState.TaskSwitched(projectedTask, foregroundTask)
+ } else {
+ TaskSwitchState.TaskUnchanged
+ }
+ }
+ }
+ is MediaProjectionState.EntireScreen,
+ is MediaProjectionState.NotProjecting -> {
+ flowOf(TaskSwitchState.NotProjectingTask)
+ }
+ }
+ }
+
+ /**
+ * Returns whether tasks have been switched.
+ *
+ * Always returns `false` when launcher is in the foreground. The reason is that when going to
+ * recents to switch apps, launcher becomes the new foreground task, and we don't want to show
+ * the notification then.
+ */
+ private fun hasForegroundTaskSwitched(projectedTask: TaskInfo, foregroundTask: TaskInfo) =
+ projectedTask.taskId != foregroundTask.taskId && !foregroundTask.isLauncher
+
+ private val TaskInfo.isLauncher
+ get() =
+ baseIntent.hasCategory(Intent.CATEGORY_HOME) && baseIntent.action == Intent.ACTION_MAIN
+
+ companion object {
+ private const val TAG = "TaskSwitchInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
new file mode 100644
index 0000000..cd1258e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.domain.model
+
+import android.app.TaskInfo
+
+/** Represents tha state of task switching in the context of single task media projection. */
+sealed interface TaskSwitchState {
+ /** Currently no task is being projected. */
+ object NotProjectingTask : TaskSwitchState
+ /** The foreground task is the same as the task that is currently being projected. */
+ object TaskUnchanged : TaskSwitchState
+ /** The foreground task is a different one to the task it currently being projected. */
+ data class TaskSwitched(val projectedTask: TaskInfo, val foregroundTask: TaskInfo) :
+ TaskSwitchState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
new file mode 100644
index 0000000..a4f4076
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.ui
+
+import android.content.Context
+import android.util.Log
+import android.widget.Toast
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.launch
+
+/** Coordinator responsible for showing/hiding the task switcher notification. */
+@SysUISingleton
+class TaskSwitcherNotificationCoordinator
+@Inject
+constructor(
+ private val context: Context,
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val viewModel: TaskSwitcherNotificationViewModel,
+) {
+
+ fun start() {
+ applicationScope.launch {
+ viewModel.uiState.flowOn(mainDispatcher).collect { uiState ->
+ Log.d(TAG, "uiState -> $uiState")
+ when (uiState) {
+ is Showing -> showNotification(uiState)
+ is NotShowing -> hideNotification()
+ }
+ }
+ }
+ }
+
+ private fun showNotification(uiState: Showing) {
+ val text =
+ """
+ Sharing pauses when you switch apps.
+ Share this app instead.
+ Switch back.
+ """
+ .trimIndent()
+ // TODO(b/286201515): Create actual notification.
+ Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
+ }
+
+ private fun hideNotification() {}
+
+ companion object {
+ private const val TAG = "TaskSwitchNotifCoord"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
new file mode 100644
index 0000000..21aee72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.ui.model
+
+import android.app.TaskInfo
+
+/** Represents the UI state for the task switcher notification. */
+sealed interface TaskSwitcherNotificationUiState {
+ /** The notification should not be shown. */
+ object NotShowing : TaskSwitcherNotificationUiState
+ /** The notification should be shown. */
+ data class Showing(
+ val projectedTask: TaskInfo,
+ val foregroundTask: TaskInfo,
+ ) : TaskSwitcherNotificationUiState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
new file mode 100644
index 0000000..d9754d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.util.Log
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwitchInteractor) {
+
+ val uiState: Flow<TaskSwitcherNotificationUiState> =
+ interactor.taskSwitchChanges.map { taskSwitchChange ->
+ Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
+ when (taskSwitchChange) {
+ is TaskSwitchState.TaskSwitched -> {
+ TaskSwitcherNotificationUiState.Showing(
+ projectedTask = taskSwitchChange.projectedTask,
+ foregroundTask = taskSwitchChange.foregroundTask,
+ )
+ }
+ is TaskSwitchState.NotProjectingTask,
+ is TaskSwitchState.TaskUnchanged -> {
+ TaskSwitcherNotificationUiState.NotShowing
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "TaskSwitchNotifVM"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index e7e1cc9..75106e7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.doze.util.BurnInHelperKt;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -93,6 +94,7 @@
protected @Mock KeyguardTransitionRepository mTransitionRepository;
protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
protected FakeFeatureFlags mFeatureFlags;
+ protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
protected LockIconViewController mUnderTest;
@@ -163,7 +165,8 @@
new KeyguardTransitionInteractor(mTransitionRepository,
TestScopeProvider.getTestScope().getBackgroundScope()),
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
- mFeatureFlags
+ mFeatureFlags,
+ mPrimaryBouncerInteractor
);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index b6287598..ed6a891 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -33,6 +33,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -267,4 +268,75 @@
// THEN the lock icon is shown
verify(mLockIconView).setContentDescription(LOCKED_LABEL);
}
+
+ @Test
+ public void lockIconAccessibility_notVisibleToUser() {
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardStateCallback();
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ // and biometric running state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+ reset(mLockIconView);
+ when(mLockIconView.isVisibleToUser()).thenReturn(false);
+
+ // WHEN the unlocked state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the lock icon is shown
+ verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+
+ @Test
+ public void lockIconAccessibility_bouncerAnimatingAway() {
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardStateCallback();
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ // and biometric running state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+ reset(mLockIconView);
+ when(mLockIconView.isVisibleToUser()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
+
+ // WHEN the unlocked state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the lock icon is shown
+ verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+
+ @Test
+ public void lockIconAccessibility_bouncerNotAnimatingAway_viewVisible() {
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardStateCallback();
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ // and biometric running state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+ reset(mLockIconView);
+ when(mLockIconView.isVisibleToUser()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(false);
+
+ // WHEN the unlocked state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the lock icon is shown
+ verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
new file mode 100644
index 0000000..7de78a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.systemui.biometrics
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class UdfpsBpViewControllerTest : SysuiTestCase() {
+
+ @JvmField @Rule var rule = MockitoJUnit.rule()
+
+ @Mock lateinit var udfpsBpView: UdfpsBpView
+ @Mock lateinit var statusBarStateController: StatusBarStateController
+ @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock lateinit var systemUIDialogManager: SystemUIDialogManager
+ @Mock lateinit var dumpManager: DumpManager
+
+ private lateinit var udfpsBpViewController: UdfpsBpViewController
+
+ @Before
+ fun setup() {
+ udfpsBpViewController =
+ UdfpsBpViewController(
+ udfpsBpView,
+ statusBarStateController,
+ shadeExpansionStateManager,
+ systemUIDialogManager,
+ dumpManager
+ )
+ }
+
+ @Test
+ fun testShouldNeverPauseAuth() {
+ assertFalse(udfpsBpViewController.shouldPauseAuth())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
new file mode 100644
index 0000000..bcbf666
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import com.android.systemui.util.mockito.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
+
+ @Mock private lateinit var flags: FeatureFlags
+ @Mock private lateinit var coordinator: TaskSwitcherNotificationCoordinator
+
+ private lateinit var coreStartable: MediaProjectionTaskSwitcherCoreStartable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ coreStartable = MediaProjectionTaskSwitcherCoreStartable(coordinator, flags)
+ }
+
+ @Test
+ fun start_flagEnabled_startsCoordinator() {
+ whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(true)
+
+ coreStartable.start()
+
+ verify(coordinator).start()
+ }
+
+ @Test
+ fun start_flagDisabled_doesNotStartCoordinator() {
+ whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(false)
+
+ coreStartable.start()
+
+ verifyZeroInteractions(coordinator)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
new file mode 100644
index 0000000..83932b0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.os.Binder
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
+
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ private val repo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+
+ @Test
+ fun findRunningTaskFromWindowContainerToken_noMatch_returnsNull() {
+ fakeActivityTaskManager.addRunningTasks(createTask(taskId = 1), createTask(taskId = 2))
+
+ testScope.runTest {
+ val matchingTask =
+ repo.findRunningTaskFromWindowContainerToken(windowContainerToken = Binder())
+
+ assertThat(matchingTask).isNull()
+ }
+ }
+
+ @Test
+ fun findRunningTaskFromWindowContainerToken_matchingToken_returnsTaskInfo() {
+ val expectedToken = createToken()
+ val expectedTask = createTask(taskId = 1, token = expectedToken)
+
+ fakeActivityTaskManager.addRunningTasks(
+ createTask(taskId = 2),
+ expectedTask,
+ )
+
+ testScope.runTest {
+ val actualTask =
+ repo.findRunningTaskFromWindowContainerToken(
+ windowContainerToken = expectedToken.asBinder()
+ )
+
+ assertThat(actualTask).isEqualTo(expectedTask)
+ }
+ }
+
+ @Test
+ fun foregroundTask_returnsStreamOfTasksMovedToFront() =
+ testScope.runTest {
+ val foregroundTask by collectLastValue(repo.foregroundTask)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ assertThat(foregroundTask?.taskId).isEqualTo(1)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 2))
+ assertThat(foregroundTask?.taskId).isEqualTo(2)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 3))
+ assertThat(foregroundTask?.taskId).isEqualTo(3)
+ }
+
+ @Test
+ fun foregroundTask_lastValueIsCached() =
+ testScope.runTest {
+ val foregroundTaskA by collectLastValue(repo.foregroundTask)
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ assertThat(foregroundTaskA?.taskId).isEqualTo(1)
+
+ val foregroundTaskB by collectLastValue(repo.foregroundTask)
+ assertThat(foregroundTaskB?.taskId).isEqualTo(1)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
new file mode 100644
index 0000000..1c4870b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.content.Intent
+import android.window.IWindowContainerToken
+import android.window.WindowContainerToken
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeActivityTaskManager {
+
+ private val runningTasks = mutableListOf<RunningTaskInfo>()
+ private val taskTaskListeners = mutableListOf<TaskStackListener>()
+
+ val activityTaskManager = mock<ActivityTaskManager>()
+
+ init {
+ whenever(activityTaskManager.registerTaskStackListener(any())).thenAnswer {
+ taskTaskListeners += it.arguments[0] as TaskStackListener
+ return@thenAnswer Unit
+ }
+ whenever(activityTaskManager.unregisterTaskStackListener(any())).thenAnswer {
+ taskTaskListeners -= it.arguments[0] as TaskStackListener
+ return@thenAnswer Unit
+ }
+ whenever(activityTaskManager.getTasks(any())).thenAnswer {
+ val maxNumTasks = it.arguments[0] as Int
+ return@thenAnswer runningTasks.take(maxNumTasks)
+ }
+ }
+
+ fun moveTaskToForeground(task: RunningTaskInfo) {
+ taskTaskListeners.forEach { it.onTaskMovedToFront(task) }
+ }
+
+ fun addRunningTasks(vararg tasks: RunningTaskInfo) {
+ runningTasks += tasks
+ }
+
+ companion object {
+
+ fun createTask(
+ taskId: Int,
+ token: WindowContainerToken = createToken(),
+ baseIntent: Intent = Intent()
+ ) =
+ RunningTaskInfo().apply {
+ this.taskId = taskId
+ this.token = token
+ this.baseIntent = baseIntent
+ }
+
+ fun createToken(): WindowContainerToken {
+ val realToken = object : IWindowContainerToken.Stub() {}
+ return WindowContainerToken(realToken)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
new file mode 100644
index 0000000..c59fd60
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.app.TaskInfo
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeMediaProjectionRepository : MediaProjectionRepository {
+
+ private val state = MutableStateFlow<MediaProjectionState>(MediaProjectionState.NotProjecting)
+
+ fun switchProjectedTask(newTask: TaskInfo) {
+ state.value = MediaProjectionState.SingleTask(newTask)
+ }
+
+ override val mediaProjectionState: Flow<MediaProjectionState> = state.asStateFlow()
+
+ fun projectEntireScreen() {
+ state.value = MediaProjectionState.EntireScreen
+ }
+
+ fun stopProjecting() {
+ state.value = MediaProjectionState.NotProjecting
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
new file mode 100644
index 0000000..593e389
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Intent
+import android.os.IBinder
+import android.window.IWindowContainerToken
+import android.window.WindowContainerToken
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeTasksRepository : TasksRepository {
+
+ private val _foregroundTask = MutableStateFlow(DEFAULT_TASK)
+
+ override val foregroundTask: Flow<RunningTaskInfo> = _foregroundTask.asStateFlow()
+
+ private val runningTasks = mutableListOf(DEFAULT_TASK)
+
+ override suspend fun findRunningTaskFromWindowContainerToken(
+ windowContainerToken: IBinder
+ ): RunningTaskInfo? = runningTasks.firstOrNull { it.token.asBinder() == windowContainerToken }
+
+ fun addRunningTask(task: RunningTaskInfo) {
+ runningTasks.add(task)
+ }
+
+ fun moveTaskToForeground(task: RunningTaskInfo) {
+ _foregroundTask.value = task
+ }
+
+ companion object {
+ val DEFAULT_TASK = createTask(taskId = -1)
+ val LAUNCHER_INTENT: Intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+
+ fun createTask(
+ taskId: Int,
+ token: WindowContainerToken = createToken(),
+ baseIntent: Intent = Intent()
+ ) =
+ RunningTaskInfo().apply {
+ this.taskId = taskId
+ this.token = token
+ this.baseIntent = baseIntent
+ }
+
+ fun createToken(): WindowContainerToken {
+ val realToken = object : IWindowContainerToken.Stub() {}
+ return WindowContainerToken(realToken)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
new file mode 100644
index 0000000..2b07465
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Binder
+import android.os.Handler
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.ContentRecordingSession
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
+
+ private val mediaProjectionManager = mock<MediaProjectionManager>()
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+ private val tasksRepo = FakeTasksRepository()
+
+ private lateinit var callback: MediaProjectionManager.Callback
+ private lateinit var repo: MediaProjectionManagerRepository
+
+ @Before
+ fun setUp() {
+ whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
+ callback = it.arguments[0] as MediaProjectionManager.Callback
+ return@thenAnswer Unit
+ }
+ repo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo
+ )
+ }
+
+ @Test
+ fun mediaProjectionState_onStart_emitsNotProjecting() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ callback.onStart(TEST_MEDIA_INFO)
+
+ assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+ }
+
+ @Test
+ fun mediaProjectionState_onStop_emitsNotProjecting() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ callback.onStop(TEST_MEDIA_INFO)
+
+ assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+ }
+
+ @Test
+ fun mediaProjectionState_onSessionSet_sessionNull_emitsNotProjecting() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, /* session= */ null)
+
+ assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+ }
+
+ @Test
+ fun mediaProjectionState_onSessionSet_contentToRecordDisplay_emitsEntireScreen() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+ }
+
+ @Test
+ fun mediaProjectionState_onSessionSet_tokenNull_emitsEntireScreen() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val session =
+ ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+ }
+
+ @Test
+ fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val taskWindowContainerToken = Binder()
+ val session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+ }
+
+ @Test
+ fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() =
+ testScope.runTest {
+ val token = FakeTasksRepository.createToken()
+ val task = FakeTasksRepository.createTask(taskId = 1, token = token)
+ tasksRepo.addRunningTask(task)
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val session = ContentRecordingSession.createTaskSession(token.asBinder())
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task))
+ }
+
+ companion object {
+ val TEST_MEDIA_INFO =
+ MediaProjectionInfo(/* packageName= */ "com.test.package", UserHandle.CURRENT)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
new file mode 100644
index 0000000..112950b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.domain.interactor
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitchInteractorTest : SysuiTestCase() {
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
+ private val mediaRepo = FakeMediaProjectionRepository()
+ private val tasksRepo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+
+ private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+ @Test
+ fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
+ testScope.runTest {
+ mediaRepo.stopProjecting()
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingScreen_foregroundTaskChange_emitsNotProjectingTask() =
+ testScope.runTest {
+ mediaRepo.projectEntireScreen()
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_emitsTaskChanged() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(taskSwitchState)
+ .isEqualTo(
+ TaskSwitchState.TaskSwitched(
+ projectedTask = projectedTask,
+ foregroundTask = foregroundTask
+ )
+ )
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingTask_foregroundTaskLauncher_emitsTaskUnchanged() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1, baseIntent = LAUNCHER_INTENT)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingTask_foregroundTaskSame_emitsTaskUnchanged() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 0)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+ }
+
+ companion object {
+ private val LAUNCHER_INTENT: Intent =
+ Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
new file mode 100644
index 0000000..ea44fb3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 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.systemui.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
+ private val mediaRepo = FakeMediaProjectionRepository()
+ private val tasksRepo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+ private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+ private val viewModel = TaskSwitcherNotificationViewModel(interactor)
+
+ @Test
+ fun uiState_notProjecting_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.stopProjecting()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_notProjecting_foregroundTaskChanged_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.stopProjecting()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ mediaRepo.switchProjectedTask(createTask(taskId = 1))
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingEntireScreen_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.projectEntireScreen()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingEntireScreen_foregroundTaskChanged_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.projectEntireScreen()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ mediaRepo.switchProjectedTask(createTask(taskId = 1))
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingTask_foregroundTaskChanged_different_emitsShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(uiState)
+ .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask))
+ }
+
+ @Test
+ fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.moveTaskToForeground(projectedTask)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingTask_foregroundTaskChanged_different_taskIsLauncher_emitsNotShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2, baseIntent = LAUNCHER_INTENT)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ companion object {
+ private val LAUNCHER_INTENT: Intent =
+ Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index e85eee81..e3262cf 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -22,7 +22,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
@@ -280,15 +279,22 @@
private static final int VPN_DEFAULT_SCORE = 101;
/**
- * The reset session timer for data stall. If a session has not successfully revalidated after
- * the delay, the session will be torn down and restarted in an attempt to recover. Delay
+ * The recovery timer for data stall. If a session has not successfully revalidated after
+ * the delay, the session will perform MOBIKE or be restarted in an attempt to recover. Delay
* counter is reset on successful validation only.
*
+ * <p>The first {@code MOBIKE_RECOVERY_ATTEMPT} timers are used for performing MOBIKE.
+ * System will perform session reset for the remaining timers.
* <p>If retries have exceeded the length of this array, the last entry in the array will be
* used as a repeating interval.
*/
- private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L};
-
+ // TODO: use ms instead to speed up the test.
+ private static final long[] DATA_STALL_RECOVERY_DELAYS_SEC =
+ {1L, 5L, 30L, 60L, 120L, 240L, 480L, 960L};
+ /**
+ * Maximum attempts to perform MOBIKE when the network is bad.
+ */
+ private static final int MAX_MOBIKE_RECOVERY_ATTEMPT = 2;
/**
* The initial token value of IKE session.
*/
@@ -380,6 +386,7 @@
private final INetworkManagementService mNms;
private final INetd mNetd;
@VisibleForTesting
+ @GuardedBy("this")
protected VpnConfig mConfig;
private final NetworkProvider mNetworkProvider;
@VisibleForTesting
@@ -392,7 +399,6 @@
private final UserManager mUserManager;
private final VpnProfileStore mVpnProfileStore;
- protected boolean mDataStallSuspected = false;
@VisibleForTesting
VpnProfileStore getVpnProfileStore() {
@@ -685,14 +691,14 @@
}
/**
- * Get the length of time to wait before resetting the ike session when a data stall is
- * suspected.
+ * Get the length of time to wait before perform data stall recovery when the validation
+ * result is bad.
*/
- public long getDataStallResetSessionSeconds(int count) {
- if (count >= DATA_STALL_RESET_DELAYS_SEC.length) {
- return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1];
+ public long getValidationFailRecoverySeconds(int count) {
+ if (count >= DATA_STALL_RECOVERY_DELAYS_SEC.length) {
+ return DATA_STALL_RECOVERY_DELAYS_SEC[DATA_STALL_RECOVERY_DELAYS_SEC.length - 1];
} else {
- return DATA_STALL_RESET_DELAYS_SEC[count];
+ return DATA_STALL_RECOVERY_DELAYS_SEC[count];
}
}
@@ -1598,6 +1604,8 @@
return network;
}
+ // TODO : this is not synchronized(this) but reads from mConfig, which is dangerous
+ // This file makes an effort to avoid partly initializing mConfig, but this is still not great
private LinkProperties makeLinkProperties() {
// The design of disabling IPv6 is only enabled for IKEv2 VPN because it needs additional
// logic to handle IPv6 only VPN, and the IPv6 only VPN may be restarted when its MTU
@@ -1679,6 +1687,7 @@
* registering a new NetworkAgent. This is not always possible if the new VPN configuration
* has certain changes, in which case this method would just return {@code false}.
*/
+ // TODO : this method is not synchronized(this) but reads from mConfig
private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
// NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
// Strictly speaking, bypassability is affected by lockdown and therefore it's possible
@@ -2269,7 +2278,12 @@
*/
public synchronized VpnConfig getVpnConfig() {
enforceControlPermission();
- return mConfig;
+ // Constructor of VpnConfig cannot take a null parameter. Return null directly if mConfig is
+ // null
+ if (mConfig == null) return null;
+ // mConfig is guarded by "this" and can be modified by another thread as soon as
+ // this method returns, so this method must return a copy.
+ return new VpnConfig(mConfig);
}
@Deprecated
@@ -2315,6 +2329,7 @@
}
};
+ @GuardedBy("this")
private void cleanupVpnStateLocked() {
mStatusIntent = null;
resetNetworkCapabilities();
@@ -2837,9 +2852,7 @@
}
final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner;
-
mVpnRunner.exit();
- mVpnRunner = null;
// LegacyVpn uses daemons that must be shut down before new ones are brought up.
// The same limitation does not apply to Platform VPNs.
@@ -3044,7 +3057,6 @@
@Nullable private IkeSessionWrapper mSession;
@Nullable private IkeSessionConnectionInfo mIkeConnectionInfo;
- @Nullable private VpnConnectivityDiagnosticsCallback mDiagnosticsCallback;
// mMobikeEnabled can only be updated after IKE AUTH is finished.
private boolean mMobikeEnabled = false;
@@ -3055,7 +3067,7 @@
* <p>This variable controls the retry delay, and is reset when the VPN pass network
* validation.
*/
- private int mDataStallRetryCount = 0;
+ private int mValidationFailRetryCount = 0;
/**
* The number of attempts since the last successful connection.
@@ -3084,6 +3096,7 @@
}
};
+ // GuardedBy("Vpn.this") (annotation can't be applied to constructor)
IkeV2VpnRunner(
@NonNull Ikev2VpnProfile profile, @NonNull ScheduledThreadPoolExecutor executor) {
super(TAG);
@@ -3136,15 +3149,6 @@
mConnectivityManager.registerSystemDefaultNetworkCallback(mNetworkCallback,
new Handler(mLooper));
}
-
- // DiagnosticsCallback may return more than one alive VPNs, but VPN will filter based on
- // Network object.
- final NetworkRequest diagRequest = new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_VPN)
- .removeCapability(NET_CAPABILITY_NOT_VPN).build();
- mDiagnosticsCallback = new VpnConnectivityDiagnosticsCallback();
- mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
- diagRequest, mExecutor, mDiagnosticsCallback);
}
private boolean isActiveNetwork(@Nullable Network network) {
@@ -3710,11 +3714,14 @@
}
public void updateVpnTransportInfoAndNetCap(int keepaliveDelaySec) {
- final VpnTransportInfo info = new VpnTransportInfo(
- getActiveVpnType(),
- mConfig.session,
- mConfig.allowBypass && !mLockdown,
- areLongLivedTcpConnectionsExpensive(keepaliveDelaySec));
+ final VpnTransportInfo info;
+ synchronized (Vpn.this) {
+ info = new VpnTransportInfo(
+ getActiveVpnType(),
+ mConfig.session,
+ mConfig.allowBypass && !mLockdown,
+ areLongLivedTcpConnectionsExpensive(keepaliveDelaySec));
+ }
final boolean ncUpdateRequired = !info.equals(mNetworkCapabilities.getTransportInfo());
if (ncUpdateRequired) {
mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
@@ -3875,39 +3882,12 @@
}
}
- class VpnConnectivityDiagnosticsCallback
- extends ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
- // The callback runs in the executor thread.
- @Override
- public void onDataStallSuspected(
- ConnectivityDiagnosticsManager.DataStallReport report) {
- synchronized (Vpn.this) {
- // Ignore stale runner.
- if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return;
-
- // Handle the report only for current VPN network. If data stall is already
- // reported, ignoring the other reports. It means that the stall is not
- // recovered by MOBIKE and should be on the way to reset the ike session.
- if (mNetworkAgent != null
- && mNetworkAgent.getNetwork().equals(report.getNetwork())
- && !mDataStallSuspected) {
- Log.d(TAG, "Data stall suspected");
-
- // Trigger MOBIKE.
- maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork);
- mDataStallSuspected = true;
- }
- }
- }
- }
-
public void onValidationStatus(int status) {
mEventChanges.log("[Validation] validation status " + status);
if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
// No data stall now. Reset it.
mExecutor.execute(() -> {
- mDataStallSuspected = false;
- mDataStallRetryCount = 0;
+ mValidationFailRetryCount = 0;
if (mScheduledHandleDataStallFuture != null) {
Log.d(TAG, "Recovered from stall. Cancel pending reset action.");
mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */);
@@ -3918,8 +3898,21 @@
// Skip other invalid status if the scheduled recovery exists.
if (mScheduledHandleDataStallFuture != null) return;
+ if (mValidationFailRetryCount < MAX_MOBIKE_RECOVERY_ATTEMPT) {
+ Log.d(TAG, "Validation failed");
+
+ // Trigger MOBIKE to recover first.
+ mExecutor.schedule(() -> {
+ maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork);
+ }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++),
+ TimeUnit.SECONDS);
+ return;
+ }
+
+ // Data stall is not recovered by MOBIKE. Try to reset session to recover it.
mScheduledHandleDataStallFuture = mExecutor.schedule(() -> {
- if (mDataStallSuspected) {
+ // Only perform the recovery when the network is still bad.
+ if (mValidationFailRetryCount > 0) {
Log.d(TAG, "Reset session to recover stalled network");
// This will reset old state if it exists.
startIkeSession(mActiveNetwork);
@@ -3928,7 +3921,9 @@
// Reset mScheduledHandleDataStallFuture since it's already run on executor
// thread.
mScheduledHandleDataStallFuture = null;
- }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS);
+ // TODO: compute the delay based on the last recovery timestamp
+ }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++),
+ TimeUnit.SECONDS);
}
}
@@ -4220,7 +4215,7 @@
* consistency of the Ikev2VpnRunner fields.
*/
private void disconnectVpnRunner() {
- mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork);
+ mEventChanges.log("[VPNRunner] Disconnect runner, underlying net " + mActiveNetwork);
mActiveNetwork = null;
mUnderlyingNetworkCapabilities = null;
mUnderlyingLinkProperties = null;
@@ -4231,8 +4226,6 @@
mCarrierConfigManager.unregisterCarrierConfigChangeListener(
mCarrierConfigChangeListener);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
- mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
- mDiagnosticsCallback);
clearVpnNetworkPreference(mSessionKey);
mExecutor.shutdown();
@@ -4293,6 +4286,7 @@
}
};
+ // GuardedBy("Vpn.this") (annotation can't be applied to constructor)
LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) {
super(TAG);
if (racoon == null && mtpd == null) {
@@ -4500,46 +4494,46 @@
}
// Set the interface and the addresses in the config.
- mConfig.interfaze = parameters[0].trim();
-
- mConfig.addLegacyAddresses(parameters[1]);
- // Set the routes if they are not set in the config.
- if (mConfig.routes == null || mConfig.routes.isEmpty()) {
- mConfig.addLegacyRoutes(parameters[2]);
- }
-
- // Set the DNS servers if they are not set in the config.
- if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
- String dnsServers = parameters[3].trim();
- if (!dnsServers.isEmpty()) {
- mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
- }
- }
-
- // Set the search domains if they are not set in the config.
- if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
- String searchDomains = parameters[4].trim();
- if (!searchDomains.isEmpty()) {
- mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
- }
- }
-
- // Add a throw route for the VPN server endpoint, if one was specified.
- if (endpointAddress instanceof Inet4Address) {
- mConfig.routes.add(new RouteInfo(
- new IpPrefix(endpointAddress, 32), null /*gateway*/,
- null /*iface*/, RTN_THROW));
- } else if (endpointAddress instanceof Inet6Address) {
- mConfig.routes.add(new RouteInfo(
- new IpPrefix(endpointAddress, 128), null /*gateway*/,
- null /*iface*/, RTN_THROW));
- } else {
- Log.e(TAG, "Unknown IP address family for VPN endpoint: "
- + endpointAddress);
- }
-
- // Here is the last step and it must be done synchronously.
synchronized (Vpn.this) {
+ mConfig.interfaze = parameters[0].trim();
+
+ mConfig.addLegacyAddresses(parameters[1]);
+ // Set the routes if they are not set in the config.
+ if (mConfig.routes == null || mConfig.routes.isEmpty()) {
+ mConfig.addLegacyRoutes(parameters[2]);
+ }
+
+ // Set the DNS servers if they are not set in the config.
+ if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
+ String dnsServers = parameters[3].trim();
+ if (!dnsServers.isEmpty()) {
+ mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
+ }
+ }
+
+ // Set the search domains if they are not set in the config.
+ if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
+ String searchDomains = parameters[4].trim();
+ if (!searchDomains.isEmpty()) {
+ mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
+ }
+ }
+
+ // Add a throw route for the VPN server endpoint, if one was specified.
+ if (endpointAddress instanceof Inet4Address) {
+ mConfig.routes.add(new RouteInfo(
+ new IpPrefix(endpointAddress, 32), null /*gateway*/,
+ null /*iface*/, RTN_THROW));
+ } else if (endpointAddress instanceof Inet6Address) {
+ mConfig.routes.add(new RouteInfo(
+ new IpPrefix(endpointAddress, 128), null /*gateway*/,
+ null /*iface*/, RTN_THROW));
+ } else {
+ Log.e(TAG, "Unknown IP address family for VPN endpoint: "
+ + endpointAddress);
+ }
+
+ // Here is the last step and it must be done synchronously.
// Set the start time
mConfig.startTime = SystemClock.elapsedRealtime();
@@ -4773,25 +4767,26 @@
try {
// Build basic config
- mConfig = new VpnConfig();
+ final VpnConfig config = new VpnConfig();
if (VpnConfig.LEGACY_VPN.equals(packageName)) {
- mConfig.legacy = true;
- mConfig.session = profile.name;
- mConfig.user = profile.key;
+ config.legacy = true;
+ config.session = profile.name;
+ config.user = profile.key;
// TODO: Add support for configuring meteredness via Settings. Until then, use a
// safe default.
- mConfig.isMetered = true;
+ config.isMetered = true;
} else {
- mConfig.user = packageName;
- mConfig.isMetered = profile.isMetered;
+ config.user = packageName;
+ config.isMetered = profile.isMetered;
}
- mConfig.startTime = SystemClock.elapsedRealtime();
- mConfig.proxyInfo = profile.proxy;
- mConfig.requiresInternetValidation = profile.requiresInternetValidation;
- mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
- mConfig.allowBypass = profile.isBypassable;
- mConfig.disallowedApplications = getAppExclusionList(mPackage);
+ config.startTime = SystemClock.elapsedRealtime();
+ config.proxyInfo = profile.proxy;
+ config.requiresInternetValidation = profile.requiresInternetValidation;
+ config.excludeLocalRoutes = profile.excludeLocalRoutes;
+ config.allowBypass = profile.isBypassable;
+ config.disallowedApplications = getAppExclusionList(mPackage);
+ mConfig = config;
switch (profile.type) {
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -4805,6 +4800,7 @@
mVpnRunner.start();
break;
default:
+ mConfig = null;
updateState(DetailedState.FAILED, "Invalid platform VPN type");
Log.d(TAG, "Unknown VPN profile type: " + profile.type);
break;
@@ -5216,7 +5212,7 @@
pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled"));
pw.println("Profile: " + runner.mProfile);
pw.println("Token: " + runner.mCurrentToken);
- if (mDataStallSuspected) pw.println("Data stall suspected");
+ pw.println("Validation failed retry count:" + runner.mValidationFailRetryCount);
if (runner.mScheduledHandleDataStallFuture != null) {
pw.println("Reset session scheduled");
}
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 6a0550b..aa99dab 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -291,6 +291,10 @@
reset(false /* reinitializing */);
}
+ void setInkWindowInitializer(Runnable inkWindowInitializer) {
+ mInkWindowInitRunnable = inkWindowInitializer;
+ }
+
private void reset(boolean reinitializing) {
if (mHandwritingEventReceiver != null) {
mHandwritingEventReceiver.dispose();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1ab83f7..02ee96a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2472,6 +2472,9 @@
curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
+ if (mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
+ mHwController.setInkWindowInitializer(new InkWindowInitializer());
+ }
return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
session.mSession, accessibilityInputMethodSessions,
(session.mChannel != null ? session.mChannel.dup() : null),
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9402fca..279a480 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -91,6 +91,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManagerInternal;
@@ -632,6 +633,8 @@
SettingsObserver mSettingsObserver;
ModifierShortcutManager mModifierShortcutManager;
+ /** Currently fully consumed key codes per device */
+ private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>();
PowerManager.WakeLock mBroadcastWakeLock;
PowerManager.WakeLock mPowerKeyWakeLock;
boolean mHavePendingMediaKeyRepeatWithWakeLock;
@@ -1817,7 +1820,7 @@
mDisplayId = displayId;
}
- int handleHomeButton(IBinder focusedToken, KeyEvent event) {
+ boolean handleHomeButton(IBinder focusedToken, KeyEvent event) {
final boolean keyguardOn = keyguardOn();
final int repeatCount = event.getRepeatCount();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
@@ -1838,12 +1841,12 @@
mHomePressed = false;
if (mHomeConsumed) {
mHomeConsumed = false;
- return -1;
+ return true;
}
if (canceled) {
Log.i(TAG, "Ignoring HOME; event canceled.");
- return -1;
+ return true;
}
// Delay handling home if a double-tap is possible.
@@ -1855,13 +1858,13 @@
mHomeDoubleTapPending = true;
mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
ViewConfiguration.getDoubleTapTimeout());
- return -1;
+ return true;
}
}
// Post to main thread to avoid blocking input pipeline.
mHandler.post(() -> handleShortPressOnHome(mDisplayId));
- return -1;
+ return true;
}
final KeyInterceptionInfo info =
@@ -1873,12 +1876,12 @@
|| (info.layoutParamsType == TYPE_NOTIFICATION_SHADE
&& isKeyguardShowing())) {
// the "app" is keyguard, so give it the key
- return 0;
+ return false;
}
for (int t : WINDOW_TYPES_WHERE_HOME_DOESNT_WORK) {
if (info.layoutParamsType == t) {
// don't do anything, but also don't pass it to the app
- return -1;
+ return true;
}
}
}
@@ -1903,7 +1906,7 @@
event.getEventTime()));
}
}
- return -1;
+ return true;
}
private void handleDoubleTapOnHome() {
@@ -2949,24 +2952,21 @@
@Override
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
int policyFlags) {
- final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();
- final int repeatCount = event.getRepeatCount();
- final int metaState = event.getMetaState();
final int flags = event.getFlags();
- final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
- final boolean canceled = event.isCanceled();
- final int displayId = event.getDisplayId();
- final long key_consumed = -1;
- final long key_not_consumed = 0;
+ final long keyConsumed = -1;
+ final long keyNotConsumed = 0;
+ final int deviceId = event.getDeviceId();
if (DEBUG_INPUT) {
- Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
- + repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled);
+ Log.d(TAG,
+ "interceptKeyTi keyCode=" + keyCode + " action=" + event.getAction()
+ + " repeatCount=" + event.getRepeatCount() + " keyguardOn="
+ + keyguardOn() + " canceled=" + event.isCanceled());
}
if (mKeyCombinationManager.isKeyConsumed(event)) {
- return key_consumed;
+ return keyConsumed;
}
if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
@@ -2977,8 +2977,54 @@
}
}
- // Cancel any pending meta actions if we see any other keys being pressed between the down
- // of the meta key and its corresponding up.
+ Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
+ if (consumedKeys == null) {
+ consumedKeys = new HashSet<>();
+ mConsumedKeysForDevice.put(deviceId, consumedKeys);
+ }
+
+ if (interceptSystemKeysAndShortcuts(focusedToken, event)
+ && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ consumedKeys.add(keyCode);
+ return keyConsumed;
+ }
+
+ boolean needToConsumeKey = consumedKeys.contains(keyCode);
+ if (event.getAction() == KeyEvent.ACTION_UP || event.isCanceled()) {
+ consumedKeys.remove(keyCode);
+ if (consumedKeys.isEmpty()) {
+ mConsumedKeysForDevice.remove(deviceId);
+ }
+ }
+
+ return needToConsumeKey ? keyConsumed : keyNotConsumed;
+ }
+
+ // You can only start consuming the key gesture if ACTION_DOWN and repeat count
+ // is 0. If you start intercepting the key halfway, then key will not be consumed
+ // and will be sent to apps for processing too.
+ // e.g. If a certain combination is only handled on ACTION_UP (i.e.
+ // interceptShortcut() returns true only for ACTION_UP), then since we already
+ // sent the ACTION_DOWN events to the application, we MUST also send the
+ // ACTION_UP to the application.
+ // So, to ensure that your intercept logic works properly, and we don't send any
+ // conflicting events to application, make sure to consume the event on
+ // ACTION_DOWN even if you want to do something on ACTION_UP. This is essential
+ // to maintain event parity and to not have incomplete key gestures.
+ @SuppressLint("MissingPermission")
+ private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
+ final boolean keyguardOn = keyguardOn();
+ final int keyCode = event.getKeyCode();
+ final int repeatCount = event.getRepeatCount();
+ final int metaState = event.getMetaState();
+ final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+ final boolean canceled = event.isCanceled();
+ final int displayId = event.getDisplayId();
+ final int deviceId = event.getDeviceId();
+ final boolean firstDown = down && repeatCount == 0;
+
+ // Cancel any pending meta actions if we see any other keys being pressed between the
+ // down of the meta key and its corresponding up.
if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
mPendingMetaAction = false;
}
@@ -2992,50 +3038,49 @@
dismissKeyboardShortcutsMenu();
mPendingMetaAction = false;
mPendingCapsLockToggle = false;
- return key_consumed;
+ return true;
}
}
- switch(keyCode) {
+ switch (keyCode) {
case KeyEvent.KEYCODE_HOME:
- logKeyboardSystemsEvent(event, FrameworkStatsLog
- .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME);
+ logKeyboardSystemsEvent(event,
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME);
return handleHomeShortcuts(displayId, focusedToken, event);
case KeyEvent.KEYCODE_MENU:
// Hijack modified menu keys for debugging features
final int chordBug = KeyEvent.META_SHIFT_ON;
- if (down && repeatCount == 0) {
- if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) {
- Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
- mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
- null, null, null, 0, null, null);
- return key_consumed;
- }
+ if (mEnableShiftMenuBugReports && firstDown
+ && (metaState & chordBug) == chordBug) {
+ Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
+ null, null, null, 0, null, null);
+ return true;
}
break;
case KeyEvent.KEYCODE_RECENT_APPS:
- if (down && repeatCount == 0) {
+ if (firstDown) {
showRecentApps(false /* triggeredFromAltTab */);
- logKeyboardSystemsEvent(event, FrameworkStatsLog
- .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS);
+ logKeyboardSystemsEvent(event,
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS);
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_APP_SWITCH:
if (!keyguardOn) {
- if (down && repeatCount == 0) {
+ if (firstDown) {
preloadRecentApps();
} else if (!down) {
toggleRecentApps();
}
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_A:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
- event.getDeviceId(),
- event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN);
- return key_consumed;
+ deviceId, event.getEventTime(),
+ AssistUtils.INVOCATION_TYPE_UNKNOWN);
+ return true;
}
break;
case KeyEvent.KEYCODE_H:
@@ -3045,73 +3090,73 @@
}
break;
case KeyEvent.KEYCODE_I:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
showSystemSettings();
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_L:
- if (down && event.isMetaPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed()) {
lockNow(null /* options */);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_N:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
if (event.isCtrlPressed()) {
sendSystemKeyToStatusBarAsync(event);
} else {
toggleNotificationPanel();
}
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_S:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_T:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
toggleTaskbar();
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_DPAD_UP:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
if (statusbar != null) {
statusbar.goToFullscreenFromSplit();
+ return true;
}
- return key_consumed;
}
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
enterStageSplitFromRunningApp(true /* leftOrTop */);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
enterStageSplitFromRunningApp(false /* leftOrTop */);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_SLASH:
- if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
+ if (firstDown && event.isMetaPressed() && !keyguardOn) {
toggleKeyboardShortcutsMenu(event.getDeviceId());
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_ASSIST:
Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_VOICE_ASSIST:
Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in"
+ " interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_VIDEO_APP_1:
case KeyEvent.KEYCODE_VIDEO_APP_2:
case KeyEvent.KEYCODE_VIDEO_APP_3:
@@ -3129,7 +3174,7 @@
case KeyEvent.KEYCODE_DEMO_APP_3:
case KeyEvent.KEYCODE_DEMO_APP_4:
Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
if (down) {
@@ -3169,20 +3214,20 @@
startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
UserHandle.CURRENT_OR_SELF);
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
if (down) {
mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
if (down) {
mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
// TODO: Add logic
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE:
@@ -3190,7 +3235,7 @@
// On TVs or when the configuration is enabled, volume keys never
// go to the foreground app.
dispatchDirectAudioEvent(event);
- return key_consumed;
+ return true;
}
// If the device is in VR mode and keys are "internal" (e.g. on the side of the
@@ -3199,26 +3244,23 @@
if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) {
final InputDevice d = event.getDevice();
if (d != null && !d.isExternal()) {
- return key_consumed;
+ return true;
}
}
break;
case KeyEvent.KEYCODE_TAB:
- if (down && event.isMetaPressed()) {
- if (!keyguardOn && isUserSetupComplete()) {
+ if (firstDown && !keyguardOn && isUserSetupComplete()) {
+ if (event.isMetaPressed()) {
showRecentApps(false);
- return key_consumed;
- }
- } else if (down && repeatCount == 0) {
- // Display task switcher for ALT-TAB.
- if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
+ return true;
+ } else if (mRecentAppsHeldModifiers == 0) {
final int shiftlessModifiers =
event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
if (KeyEvent.metaStateHasModifiers(
shiftlessModifiers, KeyEvent.META_ALT_ON)) {
mRecentAppsHeldModifiers = shiftlessModifiers;
showRecentApps(true);
- return key_consumed;
+ return true;
}
}
}
@@ -3230,18 +3272,18 @@
msg.setAsynchronous(true);
msg.sendToTarget();
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_NOTIFICATION:
if (!down) {
toggleNotificationPanel();
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_SEARCH:
- if (down && repeatCount == 0 && !keyguardOn()) {
- switch(mSearchKeyBehavior) {
+ if (firstDown && !keyguardOn) {
+ switch (mSearchKeyBehavior) {
case SEARCH_BEHAVIOR_TARGET_ACTIVITY: {
launchTargetSearchActivity();
- return key_consumed;
+ return true;
}
case SEARCH_BEHAVIOR_DEFAULT_SEARCH:
default:
@@ -3250,21 +3292,18 @@
}
break;
case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
- if (down && repeatCount == 0) {
+ if (firstDown) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
sendSwitchKeyboardLayout(event, direction);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_SPACE:
// Handle keyboard layout switching. (META + SPACE)
- if ((metaState & KeyEvent.META_META_MASK) == 0) {
- return key_not_consumed;
- }
- if (down && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed()) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
sendSwitchKeyboardLayout(event, direction);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_META_LEFT:
@@ -3289,7 +3328,7 @@
mPendingMetaAction = false;
}
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_ALT_LEFT:
case KeyEvent.KEYCODE_ALT_RIGHT:
if (down) {
@@ -3305,14 +3344,14 @@
&& (metaState & mRecentAppsHeldModifiers) == 0) {
mRecentAppsHeldModifiers = 0;
hideRecentApps(true, false);
- return key_consumed;
+ return true;
}
// Toggle Caps Lock on META-ALT.
if (mPendingCapsLockToggle) {
mInputManagerInternal.toggleCapsLock(event.getDeviceId());
mPendingCapsLockToggle = false;
- return key_consumed;
+ return true;
}
}
break;
@@ -3322,24 +3361,18 @@
case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL:
Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
+ " interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
}
-
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
- return key_consumed;
+ return true;
}
// Reserve all the META modifier combos for system behavior
- if ((metaState & KeyEvent.META_META_ON) != 0) {
- return key_consumed;
- }
-
- // Let the application handle the key.
- return key_not_consumed;
+ return (metaState & KeyEvent.META_META_ON) != 0;
}
- private int handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
+ private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
// First we always handle the home key here, so applications
// can never break it, although if keyguard is on, we do let
// it handle it, because that gives us the correct 5 second
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1b27bb1..5b3bbd5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2270,7 +2270,7 @@
// transitions anyways).
return wc.getParent().asDisplayContent().getWindowingLayer();
}
- return wc.getParentSurfaceControl();
+ return wc.getParent().getSurfaceControl();
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 9806be8..31afcbf 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -388,23 +388,12 @@
@Override
SurfaceControl.Builder makeSurface() {
final SurfaceControl.Builder builder = super.makeSurface();
- // The overlay may use COLOR_MODE_A8 that needs to be at the top of the display to avoid
- // additional memory usage, see b/235601833. Note that getParentSurfaceControl() must use
- // the same parent.
if (mRoundedCornerOverlay) {
builder.setParent(null);
}
return builder;
}
- @Override
- public SurfaceControl getParentSurfaceControl() {
- if (mRoundedCornerOverlay) {
- return null;
- }
- return super.getParentSurfaceControl();
- }
-
boolean isClientVisible() {
return mClientVisible;
}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
index 6a1674b..63b8e17 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
@@ -38,13 +38,16 @@
import android.os.Process;
import android.os.RemoteException;
+import androidx.test.filters.FlakyTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.FakeLatencyTracker;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
@@ -52,8 +55,12 @@
import org.mockito.MockitoAnnotations;
@RunWith(JUnit4.class)
+@FlakyTest(bugId = 275746222)
public class SoundTriggerMiddlewareLoggingLatencyTest {
+ @Rule
+ public Timeout mGlobalTimeout = Timeout.seconds(30);
+
private FakeLatencyTracker mLatencyTracker;
@Mock
private BatteryStatsInternal mBatteryStatsInternal;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 24e231c..c975a50 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -250,10 +250,13 @@
waitConditions = arrayOf(ConditionsFactory.hasPipWindow())
)
+ val windowRegion = wmHelper.getWindowRegion(this)
+
wmHelper
.StateSyncBuilder()
.withWindowSurfaceAppeared(this)
.withPipShown()
+ .withSurfaceVisibleRegion(this, windowRegion)
.waitForAndVerify()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
index c336399..4e8a697 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -28,6 +28,7 @@
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -106,9 +107,9 @@
super.visibleLayersShownMoreThanOneConsecutiveEntry()
/** {@inheritDoc} */
- @FlakyTest(bugId = 209599395)
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+ @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+ override fun navBarLayerIsVisibleAtStartAndEnd() {}
/** {@inheritDoc} */
@Presubmit
diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp
index 90e61c5..0d2f2ef 100644
--- a/tests/UiBench/Android.bp
+++ b/tests/UiBench/Android.bp
@@ -24,6 +24,5 @@
"androidx.recyclerview_recyclerview",
"androidx.leanback_leanback",
],
- certificate: "platform",
test_suites: ["device-tests"],
}
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index 47211c5..4fc6ec7 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -18,7 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.test.uibench">
- <uses-permission android:name="android.permission.INJECT_EVENTS" />
<application android:allowBackup="false"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
diff --git a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
index 1b2c3c6..06b65a7 100644
--- a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
@@ -15,15 +15,11 @@
*/
package com.android.test.uibench;
-import android.app.Instrumentation;
+import android.content.Intent;
import android.os.Bundle;
-import android.os.Looper;
-import android.os.MessageQueue;
-import androidx.appcompat.app.AppCompatActivity;
-import android.view.KeyEvent;
import android.widget.EditText;
-import java.util.concurrent.Semaphore;
+import androidx.appcompat.app.AppCompatActivity;
/**
* Note: currently incomplete, complexity of input continuously grows, instead of looping
@@ -32,7 +28,13 @@
* Simulates typing continuously into an EditText.
*/
public class EditTextTypeActivity extends AppCompatActivity {
- Thread mThread;
+
+ /**
+ * Broadcast action: Used to notify UiBenchEditTextTypingMicrobenchmark test when the
+ * test activity was paused.
+ */
+ private static final String ACTION_CANCEL_TYPING_CALLBACK =
+ "com.android.uibench.action.CANCEL_TYPING_CALLBACK";
private static String sSeedText = "";
static {
@@ -46,9 +48,6 @@
sSeedText = builder.toString();
}
- final Object mLock = new Object();
- boolean mShouldStop = false;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -56,55 +55,13 @@
EditText editText = new EditText(this);
editText.setText(sSeedText);
setContentView(editText);
-
- final Instrumentation instrumentation = new Instrumentation();
- final Semaphore sem = new Semaphore(0);
- MessageQueue.IdleHandler handler = new MessageQueue.IdleHandler() {
- @Override
- public boolean queueIdle() {
- // TODO: consider other signaling approaches
- sem.release();
- return true;
- }
- };
- Looper.myQueue().addIdleHandler(handler);
- synchronized (mLock) {
- mShouldStop = false;
- }
- mThread = new Thread(new Runnable() {
- int codes[] = { KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_L,
- KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_SPACE };
- int i = 0;
- @Override
- public void run() {
- while (true) {
- try {
- sem.acquire();
- } catch (InterruptedException e) {
- // TODO, maybe
- }
- int code = codes[i % codes.length];
- if (i % 100 == 99) code = KeyEvent.KEYCODE_ENTER;
-
- synchronized (mLock) {
- if (mShouldStop) break;
- }
-
- // TODO: bit of a race here, since the event can arrive after pause/stop.
- // (Can't synchronize on key send, since it's synchronous.)
- instrumentation.sendKeyDownUpSync(code);
- i++;
- }
- }
- });
- mThread.start();
}
@Override
protected void onPause() {
- synchronized (mLock) {
- mShouldStop = true;
- }
+ // Cancel the typing when the test activity was paused.
+ sendBroadcast(new Intent(ACTION_CANCEL_TYPING_CALLBACK).addFlags(
+ Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY));
super.onPause();
}
}