diff options
174 files changed, 5166 insertions, 1159 deletions
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 9d5073e43957..7080133dc597 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -177,5 +177,5 @@ interface IFaceService { // Internal operation used to clear face biometric scheduler. // Ensures that the scheduler is not stuck. @EnforcePermission("USE_BIOMETRIC_INTERNAL") - void scheduleWatchdog(); + oneway void scheduleWatchdog(); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 99758525e5ff..e2840ec20ff9 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -213,5 +213,5 @@ interface IFingerprintService { // Internal operation used to clear fingerprint biometric scheduler. // Ensures that the scheduler is not stuck. @EnforcePermission("USE_BIOMETRIC_INTERNAL") - void scheduleWatchdog(); + oneway void scheduleWatchdog(); } diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 14e82539a6a5..3d4b55acee39 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -228,12 +228,12 @@ <dimen name="bubble_user_education_stack_padding">16dp</dimen> <!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. --> <dimen name="bubblebar_size">72dp</dimen> - <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. --> - <dimen name="bubble_bar_expanded_view_handle_size">40dp</dimen> - <!-- The width of the drag handle shown along with a bubble bar expanded view. --> - <dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen> - <!-- The height of the drag handle shown along with a bubble bar expanded view. --> - <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen> + <!-- The size of the caption bar inset at the top of bubble bar expanded view. --> + <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen> + <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. --> + <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen> + <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. --> + <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen> <!-- Minimum width of the bubble bar manage menu. --> <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen> <!-- Size of the dismiss icon in the bubble bar manage menu. --> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index f729d029a818..e97390d3a86e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -25,6 +25,8 @@ import android.graphics.PointF; import android.util.Log; import android.widget.FrameLayout; +import androidx.annotation.Nullable; + import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.animation.PhysicsAnimator; import com.android.wm.shell.bubbles.BubbleOverflow; @@ -111,7 +113,8 @@ public class BubbleBarAnimationHelper { /** * Animates the provided bubble's expanded view to the expanded state. */ - public void animateExpansion(BubbleViewProvider expandedBubble) { + public void animateExpansion(BubbleViewProvider expandedBubble, + @Nullable Runnable afterAnimation) { mExpandedBubble = expandedBubble; if (mExpandedBubble == null) { return; @@ -160,6 +163,9 @@ public class BubbleBarAnimationHelper { bev.setAnimationMatrix(null); updateExpandedView(); bev.setSurfaceZOrderedOnTop(false); + if (afterAnimation != null) { + afterAnimation.run(); + } }) .start(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index 396aa0e56cf6..6b6d6baa3d39 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -16,12 +16,12 @@ package com.android.wm.shell.bubbles.bar; -import android.annotation.ColorInt; import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; +import android.graphics.Insets; import android.graphics.Outline; import android.graphics.Rect; import android.util.AttributeSet; @@ -63,7 +63,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView private @Nullable TaskView mTaskView; private @Nullable BubbleOverflowContainerView mOverflowView; - private int mHandleHeight; + private int mCaptionHeight; + private int mBackgroundColor; private float mCornerRadius = 0f; @@ -97,8 +98,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView super.onFinishInflate(); Context context = getContext(); setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation)); - mHandleHeight = context.getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_handle_size); + mCaptionHeight = context.getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_height); addView(mHandleView); applyThemeAttrs(); setClipToOutline(true); @@ -136,6 +137,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView addView(mTaskView); mTaskView.setEnableSurfaceClipping(true); mTaskView.setCornerRadius(mCornerRadius); + + // Handle view needs to draw on top of task view. + bringChildToFront(mHandleView); } mMenuViewController = new BubbleBarMenuViewController(mContext, this); mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() { @@ -169,6 +173,10 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView }); } + public BubbleBarHandleView getHandleView() { + return mHandleView; + } + // TODO (b/275087636): call this when theme/config changes /** Updates the view based on the current theme. */ public void applyThemeAttrs() { @@ -183,12 +191,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView ta.recycle(); - mHandleHeight = getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_handle_size); + mCaptionHeight = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_height); if (mTaskView != null) { mTaskView.setCornerRadius(mCornerRadius); - updateHandleAndBackgroundColor(true /* animated */); + updateHandleColor(true /* animated */); } } @@ -196,13 +204,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); - int menuViewHeight = Math.min(mHandleHeight, height); + int menuViewHeight = Math.min(mCaptionHeight, height); measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, MeasureSpec.getMode(heightMeasureSpec))); if (mTaskView != null) { - int taskViewHeight = height - menuViewHeight; - measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight, + measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec))); } } @@ -210,19 +217,20 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - // Drag handle above - final int dragHandleBottom = t + mHandleView.getMeasuredHeight(); - mHandleView.layout(l, t, r, dragHandleBottom); + final int captionBottom = t + mCaptionHeight; if (mTaskView != null) { - mTaskView.layout(l, dragHandleBottom, r, - dragHandleBottom + mTaskView.getMeasuredHeight()); + mTaskView.layout(l, t, r, + t + mTaskView.getMeasuredHeight()); + mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0)); } + // Handle draws on top of task view in the caption area. + mHandleView.layout(l, t, r, captionBottom); } @Override public void onTaskCreated() { setContentVisibility(true); - updateHandleAndBackgroundColor(false /* animated */); + updateHandleColor(false /* animated */); } @Override @@ -298,33 +306,20 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } /** - * Updates the background color to match with task view status/bg color, and sets handle color - * to contrast with the background - */ - private void updateHandleAndBackgroundColor(boolean animated) { - if (mTaskView == null) return; - final int color = getTaskViewColor(); - final boolean isRegionDark = Color.luminance(color) <= 0.5; - mHandleView.updateHandleColor(isRegionDark, animated); - setBackgroundColor(color); - } - - /** - * Retrieves task view status/nav bar color or background if available - * - * TODO (b/283075226): Update with color sampling when - * RegionSamplingHelper or alternative is available + * Updates the handle color based on the task view status bar or background color; if those + * are transparent it defaults to the background color pulled from system theme attributes. */ - private @ColorInt int getTaskViewColor() { - if (mTaskView == null || mTaskView.getTaskInfo() == null) return mBackgroundColor; + private void updateHandleColor(boolean animated) { + if (mTaskView == null || mTaskView.getTaskInfo() == null) return; + int color = mBackgroundColor; ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription; if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) { - return taskDescription.getStatusBarColor(); + color = taskDescription.getStatusBarColor(); } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) { - return taskDescription.getBackgroundColor(); - } else { - return mBackgroundColor; + color = taskDescription.getBackgroundColor(); } + final boolean isRegionDark = Color.luminance(color) <= 0.5; + mHandleView.updateHandleColor(isRegionDark, animated); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java index ce26bc0322b3..2b7a0706b4de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java @@ -21,7 +21,8 @@ import android.animation.ObjectAnimator; import android.annotation.Nullable; import android.content.Context; import android.graphics.Outline; -import android.graphics.Rect; +import android.graphics.Path; +import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; import android.view.ViewOutlineProvider; @@ -37,8 +38,12 @@ import com.android.wm.shell.R; public class BubbleBarHandleView extends View { private static final long COLOR_CHANGE_DURATION = 120; - private int mHandleWidth; - private int mHandleHeight; + // The handle view is currently rendered as 3 evenly spaced dots. + private int mDotSize; + private int mDotSpacing; + // Path used to draw the dots + private final Path mPath = new Path(); + private @ColorInt int mHandleLightColor; private @ColorInt int mHandleDarkColor; private @Nullable ObjectAnimator mColorChangeAnim; @@ -58,11 +63,10 @@ public class BubbleBarHandleView extends View { public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - - mHandleWidth = getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_handle_width); - mHandleHeight = getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_handle_height); + mDotSize = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_dot_size); + mDotSpacing = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_dot_spacing); mHandleLightColor = ContextCompat.getColor(getContext(), R.color.bubble_bar_expanded_view_handle_light); mHandleDarkColor = ContextCompat.getColor(getContext(), @@ -74,13 +78,26 @@ public class BubbleBarHandleView extends View { public void getOutline(View view, Outline outline) { final int handleCenterX = view.getWidth() / 2; final int handleCenterY = view.getHeight() / 2; - final float handleRadius = mHandleHeight / 2f; - Rect handleBounds = new Rect( - handleCenterX - mHandleWidth / 2, - handleCenterY - mHandleHeight / 2, - handleCenterX + mHandleWidth / 2, - handleCenterY + mHandleHeight / 2); - outline.setRoundRect(handleBounds, handleRadius); + final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2; + final int handleLeft = handleCenterX - handleTotalWidth / 2; + final int handleTop = handleCenterY - mDotSize / 2; + final int handleBottom = handleTop + mDotSize; + RectF dot1 = new RectF( + handleLeft, handleTop, + handleLeft + mDotSize, handleBottom); + RectF dot2 = new RectF( + dot1.right + mDotSpacing, handleTop, + dot1.right + mDotSpacing + mDotSize, handleBottom + ); + RectF dot3 = new RectF( + dot2.right + mDotSpacing, handleTop, + dot2.right + mDotSpacing + mDotSize, handleBottom + ); + mPath.reset(); + mPath.addOval(dot1, Path.Direction.CW); + mPath.addOval(dot2, Path.Direction.CW); + mPath.addOval(dot3, Path.Direction.CW); + outline.setPath(mPath); } }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index d20b33edf2c9..8ead18b139de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -24,6 +24,7 @@ import android.content.Context; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; +import android.view.TouchDelegate; import android.view.View; import android.view.ViewTreeObserver; import android.widget.FrameLayout; @@ -68,6 +69,10 @@ public class BubbleBarLayerView extends FrameLayout private final Region mTouchableRegion = new Region(); private final Rect mTempRect = new Rect(); + // Used to ensure touch target size for the menu shown on a bubble expanded view + private TouchDelegate mHandleTouchDelegate; + private final Rect mHandleTouchBounds = new Rect(); + public BubbleBarLayerView(Context context, BubbleController controller) { super(context); mBubbleController = controller; @@ -164,7 +169,17 @@ public class BubbleBarLayerView extends FrameLayout mIsExpanded = true; mBubbleController.getSysuiProxy().onStackExpandChanged(true); - mAnimationHelper.animateExpansion(mExpandedBubble); + mAnimationHelper.animateExpansion(mExpandedBubble, () -> { + if (mExpandedView == null) return; + // Touch delegate for the menu + BubbleBarHandleView view = mExpandedView.getHandleView(); + view.getBoundsOnScreen(mHandleTouchBounds); + mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop(); + mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds, + mExpandedView.getHandleView()); + setTouchDelegate(mHandleTouchDelegate); + }); + showScrim(true); } @@ -175,6 +190,7 @@ public class BubbleBarLayerView extends FrameLayout mAnimationHelper.animateCollapse(() -> removeView(viewToRemove)); mBubbleController.getSysuiProxy().onStackExpandChanged(false); mExpandedView = null; + setTouchDelegate(null); showScrim(false); } 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 262d487b1d1b..2dbc4445d606 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 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { "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 a9ccdf6a156f..2b1037711249 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 @@ public class SplitDecorManager extends WindowlessWindowManager { } } 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 f70d3aec9ec8..e8fa638bea31 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 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange 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 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange 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 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange 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 0289da916937..d7ea1c0c620d 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 @@ public class SplitScreenUtils { 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 4fda4b7896c2..40ea2764d295 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 @@ class DesktopTasksController( 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 e294229038f2..3669bceebc54 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.SplitScreenConstants.SPLIT_POSIT 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.Bundle; 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } 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 bf70d48e5801..6961dabbb4b2 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.Display.DEFAULT_DISPLAY; 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_POSIT 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 3ef4f024a8ea..e2c55e4c63ea 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 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { void onRootTaskVanished(); - void onNoLongerSupportMultiWindow(); + void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo); } private final Context mContext; @@ -226,7 +226,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { 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 4faa92979733..0d77a2e4610c 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.Context; 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 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, 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 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, } /** + * 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 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, 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 163cf501734c..064af04cbc4e 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.os.Binder; 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 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { 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 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { 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 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { 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/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index 5baf2e320227..16f0e3987e24 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -202,15 +202,10 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { if (taskView == null) return null; // Opening types should all be initiated by shell if (!TransitionUtil.isClosingType(request.getType())) return null; - PendingTransition pending = findPendingCloseTransition(taskView); - if (pending == null) { - pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */); - } - if (pending.mClaimed != null) { - throw new IllegalStateException("Task is closing in 2 collecting transitions?" - + " This state doesn't make sense"); - } + PendingTransition pending = new PendingTransition(request.getType(), null, + taskView, null /* cookie */); pending.mClaimed = transition; + mPending.add(pending); return new WindowContainerTransaction(); } 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 336d95e7b62b..7c6fb99e9c8b 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 @@ -336,6 +336,7 @@ class DragResizeInputListener implements AutoCloseable { private final Runnable mConsumeBatchEventRunnable; private boolean mConsumeBatchEventScheduled; private boolean mShouldHandleEvents; + private int mLastCursorType = PointerIcon.TYPE_DEFAULT; private TaskResizeInputEventReceiver( InputChannel inputChannel, Handler handler, Choreographer choreographer) { @@ -437,7 +438,6 @@ class DragResizeInputListener implements AutoCloseable { break; } case MotionEvent.ACTION_HOVER_EXIT: - mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT); result = true; break; } @@ -477,7 +477,13 @@ class DragResizeInputListener implements AutoCloseable { if (y > mTaskHeight - mTaskCornerRadius) { ctrlType |= CTRL_TYPE_BOTTOM; } - return checkDistanceFromCenter(ctrlType, x, y); + // Check distances from the center if it's in one of four corners. + if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0 + && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) { + return checkDistanceFromCenter(ctrlType, x, y); + } + // Otherwise, we should make sure we don't resize tasks inside task bounds. + return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0; } // If corner input is not within appropriate distance of corner radius, do not use it. @@ -511,7 +517,8 @@ class DragResizeInputListener implements AutoCloseable { break; } default: { - return ctrlType; + throw new IllegalArgumentException("ctrlType should be complex, but it's 0x" + + Integer.toHexString(ctrlType)); } } double distanceFromCenter = Math.hypot(x - centerX, y - centerY); @@ -564,7 +571,19 @@ class DragResizeInputListener implements AutoCloseable { cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; break; } - mInputManager.setPointerIconType(cursorType); + // Only update the cursor type to default once so that views behind the decor container + // layer that aren't in the active resizing regions have chances to update the cursor + // type. We would like to enforce the cursor type by setting the cursor type multilple + // times in active regions because we shouldn't allow the views behind to change it, as + // we'll pilfer the gesture initiated in this area. This is necessary because 1) we + // should allow the views behind regions only for touches to set the cursor type; and 2) + // there is a small region out of each rounded corner that's inside the task bounds, + // where views in the task can receive input events because we can't set touch regions + // of input sinks to have rounded corners. + if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) { + mInputManager.setPointerIconType(cursorType); + mLastCursorType = cursorType; + } } } }
\ No newline at end of file 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 6fe88cacbbc7..d3f3c5b7c672 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 @@ package com.android.wm.shell.flicker.appcompat 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 @@ import org.junit.runners.Parameterized * 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 @@ open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : @Test fun appWindowBecomesAndStaysVisible() { flicker.assertWm { - this.isAppWindowInvisible(letterboxApp) - .then() - .isAppWindowVisible(letterboxApp) } + this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp) + } } /** @@ -245,7 +243,8 @@ open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isVisible(letterboxApp) - .isVisible(ComponentNameMatcher.LETTERBOX) } + .isVisible(ComponentNameMatcher.LETTERBOX) + } } /** {@inheritDoc} */ @@ -273,4 +272,4 @@ open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : ) } } -}
\ 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 8a85374d0712..8bd44c3a7fd0 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 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : 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 000000000000..566adec75615 --- /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 000000000000..92b62273d8cb --- /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 000000000000..e6d56b5c94d3 --- /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 000000000000..6752c58bd568 --- /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 000000000000..7c9ab9939dd0 --- /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 000000000000..4b795713cb23 --- /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 000000000000..04950799732e --- /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 000000000000..71ef48bea686 --- /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 000000000000..c78729c6dc92 --- /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 000000000000..30bce2f657b1 --- /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 000000000000..b33ea7c89158 --- /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 000000000000..07a86a57117b --- /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 000000000000..9a1d12787b9d --- /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 000000000000..266e268a3537 --- /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 000000000000..83fc30bceb7b --- /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 000000000000..b2f19299c7f0 --- /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 000000000000..dae92dddbfec --- /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 000000000000..732047ba38ad --- /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 000000000000..1de7efd7970a --- /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 000000000000..1a046aa5b09e --- /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 000000000000..6e88f0eddee8 --- /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 000000000000..d26a29c80583 --- /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 000000000000..4a552b0aed6a --- /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 000000000000..b7376eaea66d --- /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 000000000000..b2d05e4a2632 --- /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 000000000000..6de31b1315e4 --- /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 000000000000..aab18a6d27b9 --- /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 000000000000..b074f2c161c9 --- /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 000000000000..c402aa4444d8 --- /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 000000000000..840401c23a91 --- /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 964a78591d12..a5c512267508 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 @@ import org.junit.runner.RunWith @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 bc30d4157e84..092fb6720e57 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 @@ import org.junit.runner.RunWith @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 a43ad9b4dd39..1d4c4d2f7068 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 android.tools.device.flicker.legacy.LegacyFlickerTestFactory 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 @@ class CopyContentInSplit(override val flicker: LegacyFlickerTest) : 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 a118c08b35e2..0d967eb15e0f 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 android.tools.device.flicker.legacy.LegacyFlickerTestFactory 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 @@ class DragDividerToResize(override val flicker: LegacyFlickerTest) : 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 e0a47b394ba1..f236c2d11ecb 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 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.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 @@ class SwitchAppByDoubleTapDivider(override val flicker: LegacyFlickerTest) : 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 8f867df3fea1..8aaa98a5ca9f 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.ICommonAssertions 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 @@ class SwitchBetweenSplitPairs(override val flicker: LegacyFlickerTest) : 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 9c68aa488d65..d9d22def6992 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 @@ import org.junit.runners.Parameterized @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 @@ open class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) } } - 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 21ac7839c2f4..7e8d60b441bb 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 @@ import org.junit.runners.Parameterized @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 @@ open class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlic } } - 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 931bff6f97e5..770e0328c4f6 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 @@ import org.junit.runners.Parameterized @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 @@ open class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlick } } - 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 7fa2c0bba2be..46570fde1942 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 @@ import org.junit.runners.Parameterized @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 @@ open class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) 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 952051f62a92..5c3d4ffa8663 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 @@ import org.junit.runners.Parameterized @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 @@ open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: Lega } } - 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 1de1c0c5e91e..6b122c686c58 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 @@ import org.junit.runners.Parameterized @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 @@ open class EnterSplitScreenByDragFromNotificationBenchmark( 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 929c7eab3105..78f9bab402e9 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 @@ import org.junit.runners.Parameterized @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 @@ open class EnterSplitScreenByDragFromShortcutBenchmark(override val flicker: Leg } } - 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 9f829c9dc46e..78907f08edf3 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 @@ import org.junit.runners.Parameterized @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 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Lega } } - /** {@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 1d5518f319d8..2c91e84a01fe 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 @@ import org.junit.runners.Parameterized @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 @@ open class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFli } } - 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 a7fb93e9b645..fa09c2ee4e21 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.flicker.legacy.LegacyFlickerTestFactory 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 @@ import org.junit.runners.Parameterized @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 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlic } } - 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 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlic 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 8358aff00213..ff220069b88e 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 @@ import org.junit.runners.Parameterized @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 @@ open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: Legacy } } - 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 b63c7659c894..5787b02f8944 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 @@ import org.junit.runners.Parameterized @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 @@ open class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlicke } } - 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 ce5a409b2756..b2d50911f930 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 @@ import org.junit.runners.Parameterized @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 @@ open class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlic } } - 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 9821bfac7a74..f234e462e63e 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 @@ import org.junit.runners.Parameterized @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 @@ open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerT 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 4fc4627093db..61c367933159 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.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.runner.RunWith import org.junit.runners.MethodSorters @@ -33,7 +33,7 @@ import org.junit.runners.Parameterized @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 @@ open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlic } } - /** {@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 df1e2e16f485..946a7ef7d8c3 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 @@ public final class StageTaskListenerTests extends ShellTestCase { 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 1b389565c066..50435a02a44d 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.ActivityManager; 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 @@ public class TaskViewTest extends ShellTestCase { 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 917276b9ce65..ad18a9d64074 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 2530257d629a..b1009808cccc 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.util.Log; 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 @@ public class SlicePurchaseActivity extends Activity { 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 @@ public class SlicePurchaseActivity extends Activity { 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 5c55a435b463..c037c400948f 100644 --- a/packages/SettingsLib/tests/robotests/Android.bp +++ b/packages/SettingsLib/tests/robotests/Android.bp @@ -42,7 +42,10 @@ android_robolectric_test { 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 @@ android_robolectric_test { 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 @@ java_library { 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 fab7251d020b..2a9e50df62b0 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 000000000000..3e67156af0c4 --- /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 000000000000..393a02e8464c --- /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 000000000000..d9dcd84ded89 --- /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 000000000000..c688683e7f8a --- /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 000000000000..dd89441255c6 --- /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 000000000000..8493c0296c8b --- /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 000000000000..ef6305869b1f --- /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 000000000000..c074f30964db --- /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 000000000000..425b2bb6a0d9 --- /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 4a913c87bddf..bb72375499c1 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.spy; 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.annotation.Implements; 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 @@ public class UtilsTest { 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 @@ public class UtilsTest { 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 @@ public class UtilsTest { @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 class UtilsTest { 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 @@ public class UtilsTest { 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 @@ public class UtilsTest { @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 @@ public class UtilsTest { @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 @@ public class UtilsTest { 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 44fdaec49f73..3de84464af2e 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.content.Context; 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 @@ public class AccessibilityUtilsTest { @Test public void getEnabledServicesFromSettings_badFormat_emptyResult() { - Settings.Secure.putStringForUser( + ShadowSecure.putStringForUser( mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, ":", UserHandle.myUserId()); @@ -57,7 +61,7 @@ public class AccessibilityUtilsTest { @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 class AccessibilityUtilsTest { 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 cb62a735434d..f9505ddb7e2f 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.UserHandle; 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.mockito.MockitoAnnotations; 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 dd8d54a62ff4..a2e8c5956100 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.mockito.MockitoAnnotations; 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 @@ public class MetricsFeatureProviderTest { } @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 d67d44b9035d..25833b3ddbba 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 androidx.recyclerview.widget.RecyclerView; 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.Config; 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 class SettingsJankMonitorTest { 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 cf702b531a3c..471dac05e6b4 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.junit.runner.RunWith; 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 3475ff7d96f8..b009abd87b1c 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.content.Context; 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 8e33ca338eb1..0cabab241be4 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 static com.google.common.truth.Truth.assertThat; 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 @@ public class LicenseHtmlGeneratorFromXmlTest { } @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 @@ public class LicenseHtmlGeneratorFromXmlTest { } @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 000000000000..9e9725fc3710 --- /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 d41d5112e6b2..faec02f7a0d4 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.Test; 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 @@ public class AnimatedImageViewTest { } @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 0a48f19a3021..0d889139e8b4 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.annotation.ColorRes; 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 @@ public class BannerMessagePreferenceTest { 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 000000000000..4fcc5a1f7000 --- /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 924eb047c340..0b9ba8d044ce 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 @@ public class ShadowActivityManager { @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 2c0792fd57bd..bbfdb7f91c4b 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 @@ public class ShadowDefaultDialerManager { 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 000000000000..fae3aeafd5fb --- /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 000000000000..70ebc6791538 --- /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 381d072b6fe1..5ac0a87c1ae9 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 @@ public class ShadowSmsApplication { 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 ca1eefcad7de..60d7721242b8 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 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager 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 a443b5c4aa60..73fb0f03052b 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -65,6 +65,7 @@ systemui_compose_java_defaults { "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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 2913c169a0be..4fd47232a0df 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -865,7 +865,7 @@ <activity android:name=".settings.brightness.BrightnessDialog" android:label="@string/quick_settings_brightness_dialog_title" - android:theme="@style/Theme.SystemUI.QuickSettings.BrightnessDialog" + android:theme="@style/BrightnessDialog" android:finishOnCloseSystemDialogs="true" android:launchMode="singleInstance" android:excludeFromRecents="true" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 240bace21a1c..d83596eeb853 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -99,7 +99,10 @@ private fun BouncerScene( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(60.dp), modifier = - modifier.background(MaterialTheme.colorScheme.surface).fillMaxSize().padding(32.dp) + modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.surface) + .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp) ) { Crossfade( targetState = message, diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index f3a6bbeaaf0e..665c6127e06d 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -121,12 +121,10 @@ frame when animating QS <-> QQS transition <LinearLayout android:id="@+id/shade_header_system_icons" android:layout_width="wrap_content" - android:layout_height="@dimen/shade_header_system_icons_height" + app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height" + android:layout_height="@dimen/large_screen_shade_header_min_height" android:clickable="true" android:orientation="horizontal" - android:gravity="center_vertical" - android:paddingStart="@dimen/shade_header_system_icons_padding_start" - android:paddingEnd="@dimen/shade_header_system_icons_padding_end" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/privacy_container" app:layout_constraintTop_toTopOf="@id/clock"> @@ -134,13 +132,13 @@ frame when animating QS <-> QQS transition <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:paddingEnd="@dimen/signal_cluster_battery_padding" /> <com.android.systemui.battery.BatteryMeterView android:id="@+id/batteryRemainingIcon" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="match_parent" app:textAppearance="@style/TextAppearance.QS.Status" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index 50dcaf333ea1..bb32022a0b5f 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -57,7 +57,6 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" - android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_alarm" android:tint="@android:color/white" android:visibility="gone" @@ -68,7 +67,6 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" - android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_qs_dnd_on" android:tint="@android:color/white" android:visibility="gone" @@ -79,7 +77,6 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" - android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_signal_wifi_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_wifi_off" /> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 83702eac7fdb..9a2db6bfdef6 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -74,9 +74,6 @@ <dimen name="large_dialog_width">472dp</dimen> <dimen name="large_screen_shade_header_height">42dp</dimen> - <!-- start padding is smaller to account for status icon margins coming from drawable itself --> - <dimen name="shade_header_system_icons_padding_start">11dp</dimen> - <dimen name="shade_header_system_icons_padding_end">12dp</dimen> <!-- Lockscreen shade transition values --> <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen> diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml index ce545f44d9bd..d053a7a0d0bb 100644 --- a/packages/SystemUI/res/values-sw720dp/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp/dimens.xml @@ -27,9 +27,6 @@ <dimen name="large_screen_shade_header_height">56dp</dimen> - <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin --> - <dimen name="shade_header_system_icons_padding_start">10dp</dimen> - <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> <dimen name="biometric_auth_pattern_view_size">348dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b34b8f690fd5..d9466bf3b4c8 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -470,10 +470,6 @@ <dimen name="large_screen_shade_header_height">48dp</dimen> <dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen> <dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen> - <dimen name="shade_header_system_icons_height">@dimen/large_screen_shade_header_min_height</dimen> - <dimen name="shade_header_system_icons_height_large_screen">32dp</dimen> - <dimen name="shade_header_system_icons_padding_start">0dp</dimen> - <dimen name="shade_header_system_icons_padding_end">0dp</dimen> <!-- The top margin of the panel that holds the list of notifications. On phones it's always 0dp but it's overridden in Car UI @@ -1797,7 +1793,6 @@ <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen> <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen> <dimen name="dream_overlay_icon_inset_dimen">0dp</dimen> - <dimen name="dream_overlay_status_bar_marginTop">22dp</dimen> <!-- Default device corner radius, used for assist UI --> <dimen name="config_rounded_mask_size">0px</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index fd74c7eae361..6b8562105c74 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -398,7 +398,8 @@ <item name="android:itemTextAppearance">@style/Control.MenuItem</item> </style> - <style name="Theme.SystemUI.QuickSettings.BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog"> + <!-- Cannot double inherit. Use Theme.SystemUI.QuickSettings in code to match --> + <style name="BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog"> <item name="android:windowBackground">@android:color/transparent</item> </style> diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml index cb2c3a19a6d5..39f4c81b6dbe 100644 --- a/packages/SystemUI/res/xml/large_screen_shade_header.xml +++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml @@ -56,7 +56,7 @@ <Constraint android:id="@+id/shade_header_system_icons"> <Layout android:layout_width="wrap_content" - android:layout_height="@dimen/shade_header_system_icons_height_large_screen" + android:layout_height="@dimen/large_screen_shade_header_min_height" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/privacy_container" app:layout_constraintTop_toTopOf="parent" diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index e255f5c2ded6..8ef6c2e0d377 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.R; 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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme 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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme 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 de7a66900355..ff395da54d18 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.Color; 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 @@ public class ScreenDecorations implements CoreStartable, Dumpable { 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 @@ public class ScreenDecorations implements CoreStartable, Dumpable { 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 class ScreenDecorations implements CoreStartable, Dumpable { 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 @@ public class ScreenDecorations implements CoreStartable, Dumpable { 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 @@ public class ScreenDecorations implements CoreStartable, Dumpable { final ViewGroup overlayView = mOverlays[i].getRootView(); overlayView.getViewTreeObserver().addOnPreDrawListener( new RestartingPreDrawListener( - overlayView, i, newRotation, newDisplayMode)); + overlayView, i, newRotation, newSize)); } } } @@ -541,7 +544,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { 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 @@ public class ScreenDecorations implements CoreStartable, Dumpable { } } - 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 @@ public class ScreenDecorations implements CoreStartable, Dumpable { 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 @@ public class ScreenDecorations implements CoreStartable, Dumpable { 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 @@ public class ScreenDecorations implements CoreStartable, Dumpable { : 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 class ScreenDecorations implements CoreStartable, Dumpable { 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 e6aeb43d9d9e..802eea300bd4 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 @@ class UdfpsBpViewController( 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 40db63d609ba..b1f513d0945c 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.dialog.MediaOutputSwitcherDialogUI 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 @@ abstract class SystemUICoreStartableModule { @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 ead24ae71cc6..3b897394c515 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.LogModule; 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 @@ import javax.inject.Named; LockscreenLayoutModule.class, LogModule.class, MediaProjectionModule.class, + MediaProjectionTaskSwitcherModule.class, MotionToolModule.class, PeopleHubModule.class, PeopleModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index 051131433143..732102e0cf9e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -50,6 +50,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.R; import com.android.systemui.SystemUIAppComponentFactoryBase; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationMediaManager; @@ -151,6 +152,9 @@ public class KeyguardSliceProvider extends SliceProvider implements private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback; @Inject WakeLockLogger mWakeLockLogger; + @Inject + @Background + Handler mBgHandler; /** * Receiver responsible for time ticking and updating the date format. @@ -502,7 +506,7 @@ public class KeyguardSliceProvider extends SliceProvider implements } protected void notifyChange() { - mContentResolver.notifyChange(mSliceUri, null /* observer */); + mBgHandler.post(() -> mContentResolver.notifyChange(mSliceUri, null /* observer */)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt index 150de26c12c7..702a23ea5ebc 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 @@ constructor( ) } - 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 000000000000..3c501277ab8c --- /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 000000000000..22ad07ebc3b1 --- /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 000000000000..9938f11e5d4c --- /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 000000000000..492d482459d6 --- /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 000000000000..38d4e698f2d9 --- /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 000000000000..5bec6925babe --- /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 000000000000..544eb6b99d4f --- /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 000000000000..6a535e4ecc50 --- /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 000000000000..fc5cf7d75bdf --- /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 000000000000..cd1258ed6aa8 --- /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 000000000000..a4f407612fa8 --- /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 000000000000..21aee72d17ae --- /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 000000000000..d9754d4429d4 --- /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/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 8879501fa03d..5199bd43f982 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -84,6 +84,7 @@ public class BrightnessDialog extends Activity { window.getDecorView(); window.setLayout( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); + getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false); setContentView(R.layout.brightness_mirror_container); FrameLayout frame = findViewById(R.id.brightness_mirror_container); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index 4ac5cd8b18a7..8789a8b3b7f4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -131,7 +131,6 @@ constructor( private val date: TextView = header.findViewById(R.id.date) private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons) private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group) - private val systemIcons: View = header.findViewById(R.id.shade_header_system_icons) private var roundedCorners = 0 private var cutout: DisplayCutout? = null @@ -255,14 +254,6 @@ constructor( header.paddingRight, header.paddingBottom ) - systemIcons.setPaddingRelative( - resources.getDimensionPixelSize( - R.dimen.shade_header_system_icons_padding_start - ), - systemIcons.paddingTop, - resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_end), - systemIcons.paddingBottom - ) } override fun onDensityOrFontScaleChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 2fa070ca20b5..07eb8a00a178 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -28,12 +28,9 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.expansionChanges -import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope @@ -50,30 +47,29 @@ import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import java.io.PrintWriter import javax.inject.Inject -import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch import kotlinx.coroutines.yield /** * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section - * headers on the lockscreen. + * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the + * lockscreen. */ @CoordinatorScope class KeyguardCoordinator @@ -86,7 +82,6 @@ constructor( private val keyguardRepository: KeyguardRepository, private val keyguardTransitionRepository: KeyguardTransitionRepository, private val logger: KeyguardCoordinatorLogger, - private val notifPipelineFlags: NotifPipelineFlags, @Application private val scope: CoroutineScope, private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, private val secureSettings: SecureSettings, @@ -95,6 +90,8 @@ constructor( ) : Coordinator, Dumpable { private val unseenNotifications = mutableSetOf<NotificationEntry>() + private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) + private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) private var unseenFilterEnabled = false override fun attach(pipeline: NotifPipeline) { @@ -109,79 +106,130 @@ constructor( private fun attachUnseenFilter(pipeline: NotifPipeline) { pipeline.addFinalizeFilter(unseenNotifFilter) pipeline.addCollectionListener(collectionListener) - scope.launch { trackUnseenNotificationsWhileUnlocked() } - scope.launch { invalidateWhenUnseenSettingChanges() } + scope.launch { trackUnseenFilterSettingChanges() } dumpManager.registerDumpable(this) } - private suspend fun trackUnseenNotificationsWhileUnlocked() { - // Whether or not we're actively tracking unseen notifications to mark them as seen when - // appropriate. - val isTrackingUnseen: Flow<Boolean> = - keyguardRepository.isKeyguardShowing - // transformLatest so that we can cancel listening to keyguard transitions once - // isKeyguardShowing changes (after a successful transition to the keyguard). - .transformLatest { isShowing -> - if (isShowing) { - // If the keyguard is showing, we're not tracking unseen. - emit(false) - } else { - // If the keyguard stops showing, then start tracking unseen notifications. - emit(true) - // If the screen is turning off, stop tracking, but if that transition is - // cancelled, then start again. - emitAll( - keyguardTransitionRepository.transitions.map { step -> - !step.isScreenTurningOff - } - ) - } - } - // Prevent double emit of `false` caused by transition to AOD, followed by keyguard - // showing + private suspend fun trackSeenNotifications() { + // Whether or not keyguard is visible (or occluded). + val isKeyguardPresent: Flow<Boolean> = + keyguardTransitionRepository.transitions + .map { step -> step.to != KeyguardState.GONE } .distinctUntilChanged() .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) } - // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is - // showing again - var clearUnseenOnBeginTracking = false - isTrackingUnseen.collectLatest { trackingUnseen -> - if (!trackingUnseen) { - // Wait for the user to spend enough time on the lock screen before clearing unseen - // set when unlocked - awaitTimeSpentNotDozing(SEEN_TIMEOUT) - clearUnseenOnBeginTracking = true - logger.logSeenOnLockscreen() + // Separately track seen notifications while the device is locked, applying once the device + // is unlocked. + val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>() + + // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes. + isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean -> + if (isKeyguardPresent) { + // Keyguard is not gone, notifications need to be visible for a certain threshold + // before being marked as seen + trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked) } else { - if (clearUnseenOnBeginTracking) { - clearUnseenOnBeginTracking = false - logger.logAllMarkedSeenOnUnlock() - unseenNotifications.clear() + // Mark all seen-while-locked notifications as seen for real. + if (notificationsSeenWhileLocked.isNotEmpty()) { + unseenNotifications.removeAll(notificationsSeenWhileLocked) + logger.logAllMarkedSeenOnUnlock( + seenCount = notificationsSeenWhileLocked.size, + remainingUnseenCount = unseenNotifications.size + ) + notificationsSeenWhileLocked.clear() } unseenNotifFilter.invalidateList("keyguard no longer showing") - trackUnseenNotifications() + // Keyguard is gone, notifications can be immediately marked as seen when they + // become visible. + trackSeenNotificationsWhileUnlocked() } } } - private suspend fun awaitTimeSpentNotDozing(duration: Duration) { - keyguardRepository.isDozing - // Use transformLatest so that the timeout delay is cancelled if the device enters doze, - // and is restarted when doze ends. - .transformLatest { isDozing -> - if (!isDozing) { - delay(duration) - // Signal timeout has completed - emit(Unit) + /** + * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually + * been "seen" while the device is on the keyguard. + */ + private suspend fun trackSeenNotificationsWhileLocked( + notificationsSeenWhileLocked: MutableSet<NotificationEntry>, + ) = coroutineScope { + // Remove removed notifications from the set + launch { + unseenEntryRemoved.collect { entry -> + if (notificationsSeenWhileLocked.remove(entry)) { + logger.logRemoveSeenOnLockscreen(entry) + } + } + } + // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and + // is restarted when doze ends. + keyguardRepository.isDozing.collectLatest { isDozing -> + if (!isDozing) { + trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked) + } + } + } + + /** + * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually + * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen + * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration. + */ + private suspend fun trackSeenNotificationsWhileLockedAndNotDozing( + notificationsSeenWhileLocked: MutableSet<NotificationEntry> + ) = coroutineScope { + // All child tracking jobs will be cancelled automatically when this is cancelled. + val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>() + + /** + * Wait for the user to spend enough time on the lock screen before removing notification + * from unseen set upon unlock. + */ + suspend fun trackSeenDurationThreshold(entry: NotificationEntry) { + if (notificationsSeenWhileLocked.remove(entry)) { + logger.logResetSeenOnLockscreen(entry) + } + delay(SEEN_TIMEOUT) + notificationsSeenWhileLocked.add(entry) + trackingJobsByEntry.remove(entry) + logger.logSeenOnLockscreen(entry) + } + + /** Stop any unseen tracking when a notification is removed. */ + suspend fun stopTrackingRemovedNotifs(): Nothing = + unseenEntryRemoved.collect { entry -> + trackingJobsByEntry.remove(entry)?.let { + it.cancel() + logger.logStopTrackingLockscreenSeenDuration(entry) + } + } + + /** Start tracking new notifications when they are posted. */ + suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope { + unseenEntryAdded.collect { entry -> + logger.logTrackingLockscreenSeenDuration(entry) + // If this is an update, reset the tracking. + trackingJobsByEntry[entry]?.let { + it.cancel() + logger.logResetSeenOnLockscreen(entry) } + trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } } - // Suspend until the first emission - .first() + } + + // Start tracking for all notifications that are currently unseen. + logger.logTrackingLockscreenSeenDuration(unseenNotifications) + unseenNotifications.forEach { entry -> + trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } + } + + launch { trackNewUnseenNotifs() } + launch { stopTrackingRemovedNotifs() } } - // Track "unseen" notifications, marking them as seen when either shade is expanded or the + // Track "seen" notifications, marking them as such when either shade is expanded or the // notification becomes heads up. - private suspend fun trackUnseenNotifications() { + private suspend fun trackSeenNotificationsWhileUnlocked() { coroutineScope { launch { clearUnseenNotificationsWhenShadeIsExpanded() } launch { markHeadsUpNotificationsAsSeen() } @@ -212,7 +260,7 @@ constructor( } } - private suspend fun invalidateWhenUnseenSettingChanges() { + private suspend fun trackUnseenFilterSettingChanges() { secureSettings // emit whenever the setting has changed .observerFlow( @@ -228,17 +276,23 @@ constructor( UserHandle.USER_CURRENT, ) == 1 } + // don't emit anything if nothing has changed + .distinctUntilChanged() // perform lookups on the bg thread pool .flowOn(bgDispatcher) // only track the most recent emission, if events are happening faster than they can be // consumed .conflate() - // update local field and invalidate if necessary - .collect { setting -> + .collectLatest { setting -> + // update local field and invalidate if necessary if (setting != unseenFilterEnabled) { unseenFilterEnabled = setting unseenNotifFilter.invalidateList("unseen setting changed") } + // if the setting is enabled, then start tracking and filtering unseen notifications + if (setting) { + trackSeenNotifications() + } } } @@ -250,6 +304,7 @@ constructor( ) { logger.logUnseenAdded(entry.key) unseenNotifications.add(entry) + unseenEntryAdded.tryEmit(entry) } } @@ -259,12 +314,14 @@ constructor( ) { logger.logUnseenUpdated(entry.key) unseenNotifications.add(entry) + unseenEntryAdded.tryEmit(entry) } } override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { if (unseenNotifications.remove(entry)) { logger.logUnseenRemoved(entry.key) + unseenEntryRemoved.tryEmit(entry) } } } @@ -347,6 +404,3 @@ constructor( private val SEEN_TIMEOUT = 5.seconds } } - -private val TransitionStep.isScreenTurningOff: Boolean - get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt index 4c33524346eb..788659eb3ccc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.UnseenNotificationLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry import javax.inject.Inject private const val TAG = "KeyguardCoordinator" @@ -28,11 +29,14 @@ class KeyguardCoordinatorLogger constructor( @UnseenNotificationLog private val buffer: LogBuffer, ) { - fun logSeenOnLockscreen() = + fun logSeenOnLockscreen(entry: NotificationEntry) = buffer.log( TAG, LogLevel.DEBUG, - "Notifications on lockscreen will be marked as seen when unlocked." + messageInitializer = { str1 = entry.key }, + messagePrinter = { + "Notification [$str1] on lockscreen will be marked as seen when unlocked." + }, ) fun logTrackingUnseen(trackingUnseen: Boolean) = @@ -43,11 +47,21 @@ constructor( messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." }, ) - fun logAllMarkedSeenOnUnlock() = + fun logAllMarkedSeenOnUnlock( + seenCount: Int, + remainingUnseenCount: Int, + ) = buffer.log( TAG, LogLevel.DEBUG, - "Notifications have been marked as seen now that device is unlocked." + messageInitializer = { + int1 = seenCount + int2 = remainingUnseenCount + }, + messagePrinter = { + "$int1 Notifications have been marked as seen now that device is unlocked. " + + "$int2 notifications remain unseen." + }, ) fun logShadeExpanded() = @@ -96,4 +110,60 @@ constructor( messageInitializer = { str1 = key }, messagePrinter = { "Unseen notif has become heads up: $str1" }, ) + + fun logTrackingLockscreenSeenDuration(unseenNotifications: Set<NotificationEntry>) { + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { + str1 = unseenNotifications.joinToString { it.key } + int1 = unseenNotifications.size + }, + messagePrinter = { + "Tracking $int1 unseen notifications for lockscreen seen duration threshold: $str1" + }, + ) + } + + fun logTrackingLockscreenSeenDuration(entry: NotificationEntry) { + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = entry.key }, + messagePrinter = { + "Tracking new notification for lockscreen seen duration threshold: $str1" + }, + ) + } + + fun logStopTrackingLockscreenSeenDuration(entry: NotificationEntry) { + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = entry.key }, + messagePrinter = { + "Stop tracking removed notification for lockscreen seen duration threshold: $str1" + }, + ) + } + + fun logResetSeenOnLockscreen(entry: NotificationEntry) { + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = entry.key }, + messagePrinter = { + "Reset tracking updated notification for lockscreen seen duration threshold: $str1" + }, + ) + } + + fun logRemoveSeenOnLockscreen(entry: NotificationEntry) { + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = entry.key }, + messagePrinter = { "Notification marked as seen on lockscreen removed: $str1" }, + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index e7e1cc94b7c8..75106e75c1ab 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.R; 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 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { 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 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { 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 b62875988b2e..ed6a891a6094 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.hardware.biometrics.BiometricSourceType; 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 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { // 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 000000000000..7de78a60b73e --- /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/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt index 42106756b473..71d2ec152e5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt @@ -11,9 +11,9 @@ import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.CustomIconCache import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.flags.FakeFeatureFlags @@ -63,24 +63,19 @@ class ControlsEditingActivityTest : SysuiTestCase() { @JvmField var activityRule = ActivityTestRule( - object : - SingleActivityFactory<TestableControlsEditingActivity>( - TestableControlsEditingActivity::class.java - ) { - override fun create(intent: Intent?): TestableControlsEditingActivity { - return TestableControlsEditingActivity( - featureFlags, - uiExecutor, - controller, - userTracker, - customIconCache, - mockDispatcher, - latch - ) - } + /* activityFactory= */ SingleActivityFactory { + TestableControlsEditingActivity( + featureFlags, + uiExecutor, + controller, + userTracker, + customIconCache, + mockDispatcher, + latch + ) }, - false, - false + /* initialTouchMode= */ false, + /* launchActivity= */ false, ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt index f4cc8bcb09a5..f11c296ad572 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt @@ -13,9 +13,9 @@ import android.window.OnBackInvokedDispatcher import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController @@ -91,24 +91,19 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @JvmField var activityRule = ActivityTestRule( - object : - SingleActivityFactory<TestableControlsFavoritingActivity>( - TestableControlsFavoritingActivity::class.java - ) { - override fun create(intent: Intent?): TestableControlsFavoritingActivity { - return TestableControlsFavoritingActivity( - featureFlags, - executor, - controller, - listingController, - userTracker, - mockDispatcher, - latch - ) - } + /* activityFactory= */ SingleActivityFactory { + TestableControlsFavoritingActivity( + featureFlags, + executor, + controller, + listingController, + userTracker, + mockDispatcher, + latch + ) }, - false, - false + /* initialTouchMode= */ false, + /* launchActivity= */ false, ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt index 4ba67182c32c..d17495f21a68 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt @@ -29,8 +29,8 @@ import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.panels.AuthorizedPanelsRepository @@ -91,26 +91,21 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { @JvmField var activityRule = ActivityTestRule( - object : - SingleActivityFactory<TestableControlsProviderSelectorActivity>( - TestableControlsProviderSelectorActivity::class.java - ) { - override fun create(intent: Intent?): TestableControlsProviderSelectorActivity { - return TestableControlsProviderSelectorActivity( - executor, - backExecutor, - listingController, - controlsController, - userTracker, - authorizedPanelsRepository, - dialogFactory, - mockDispatcher, - latch - ) - } + /* activityFactory= */ SingleActivityFactory { + TestableControlsProviderSelectorActivity( + executor, + backExecutor, + listingController, + controlsController, + userTracker, + authorizedPanelsRepository, + dialogFactory, + mockDispatcher, + latch + ) }, - false, - false + /* initialTouchMode= */ false, + /* launchActivity= */ false, ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt index 314b17625f00..ca970bb41d56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt @@ -30,8 +30,8 @@ import android.testing.TestableLooper import androidx.lifecycle.Lifecycle import androidx.test.filters.MediumTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.settings.UserTracker @@ -81,19 +81,18 @@ class ControlsRequestDialogTest : SysuiTestCase() { @Rule @JvmField - var activityRule = ActivityTestRule<TestControlsRequestDialog>( - object : SingleActivityFactory<TestControlsRequestDialog>( - TestControlsRequestDialog::class.java - ) { - override fun create(intent: Intent?): TestControlsRequestDialog { - return TestControlsRequestDialog( - mainExecutor, - controller, - userTracker, - listingController - ) - } - }, false, false) + var activityRule = ActivityTestRule( + /* activityFactory= */ SingleActivityFactory { + TestControlsRequestDialog( + mainExecutor, + controller, + userTracker, + listingController + ) + }, + /* initialTouchMode= */ false, + /* launchActivity= */ false, + ) private lateinit var control: Control diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt index 2d3e10e6f7e0..e279d28de499 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt @@ -23,8 +23,8 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.settings.ControlsSettingsDialogManager import com.android.systemui.flags.FeatureFlags @@ -53,23 +53,18 @@ class ControlsActivityTest : SysuiTestCase() { @JvmField var activityRule = ActivityTestRule( - object : - SingleActivityFactory<TestableControlsActivity>( - TestableControlsActivity::class.java - ) { - override fun create(intent: Intent?): TestableControlsActivity { - return TestableControlsActivity( - uiController, - broadcastDispatcher, - dreamManager, - featureFlags, - controlsSettingsDialogManager, - keyguardStateController, - ) - } + /* activityFactory= */ SingleActivityFactory { + TestableControlsActivity( + uiController, + broadcastDispatcher, + dreamManager, + featureFlags, + controlsSettingsDialogManager, + keyguardStateController, + ) }, - false, - false + /* initialTouchMode= */ false, + /* launchActivity= */ false, ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java index 729a1ccd30f9..ce8028c8b37a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java @@ -19,6 +19,7 @@ package com.android.systemui.keyguard; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -31,6 +32,7 @@ import android.content.ContentResolver; import android.media.MediaMetadata; import android.media.session.PlaybackState; import android.net.Uri; +import android.os.Handler; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -166,6 +168,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @Test public void updatesClock() { + clearInvocations(mContentResolver); mProvider.mKeyguardUpdateMonitorCallback.onTimeChanged(); TestableLooper.get(this).processAllMessages(); verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null)); @@ -217,11 +220,13 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { reset(mContentResolver); mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class), PlaybackState.STATE_PLAYING); + TestableLooper.get(this).processAllMessages(); verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null)); // Hides after waking up reset(mContentResolver); mProvider.onDozingChanged(false); + TestableLooper.get(this).processAllMessages(); verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null)); } @@ -231,6 +236,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class), PlaybackState.STATE_PLAYING); reset(mContentResolver); + TestableLooper.get(this).processAllMessages(); // Show media when dozing mProvider.onDozingChanged(true); verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null)); @@ -272,6 +278,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager; mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor; mUserTracker = KeyguardSliceProviderTest.this.mUserTracker; + mBgHandler = + new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper()); } @Override 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 000000000000..bcbf666fd302 --- /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 000000000000..83932b0a6133 --- /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 000000000000..1c4870bc32b1 --- /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 000000000000..c59fd60cca9b --- /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 000000000000..593e3893fb2a --- /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 000000000000..2b074655bb02 --- /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 000000000000..112950b860e8 --- /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 000000000000..ea44fb3b1f6e --- /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/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt index 36b913fc3e7b..bdb095a3a209 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt @@ -22,9 +22,9 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.dx.mockito.inline.extended.ExtendedMockito.verify import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE import com.android.systemui.util.mockito.any @@ -47,13 +47,9 @@ class LaunchNotesRoleSettingsTrampolineActivityTest : SysuiTestCase() { @Rule @JvmField val activityRule = - ActivityTestRule<LaunchNotesRoleSettingsTrampolineActivity>( - /* activityFactory= */ object : - SingleActivityFactory<LaunchNotesRoleSettingsTrampolineActivity>( - LaunchNotesRoleSettingsTrampolineActivity::class.java - ) { - override fun create(intent: Intent?) = - LaunchNotesRoleSettingsTrampolineActivity(noteTaskController) + ActivityTestRule( + /* activityFactory= */ SingleActivityFactory { + LaunchNotesRoleSettingsTrampolineActivity(noteTaskController) }, /* initialTouchMode= */ false, /* launchActivity= */ false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt index 627c4a80e1ec..1f0f0d70a858 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt @@ -16,14 +16,13 @@ package com.android.systemui.notetask.shortcut -import android.content.Intent import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.dx.mockito.inline.extended.ExtendedMockito.verify import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEntryPoint import com.android.systemui.util.mockito.any @@ -47,12 +46,8 @@ class LaunchNoteTaskActivityTest : SysuiTestCase() { @JvmField val activityRule = ActivityTestRule<LaunchNoteTaskActivity>( - /* activityFactory= */ object : - SingleActivityFactory<LaunchNoteTaskActivity>(LaunchNoteTaskActivity::class.java) { - override fun create(intent: Intent?) = - LaunchNoteTaskActivity( - controller = noteTaskController, - ) + /* activityFactory= */ SingleActivityFactory { + LaunchNoteTaskActivity(controller = noteTaskController) }, /* initialTouchMode= */ false, /* launchActivity= */ false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt index 764619340592..16751c937f9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.settings.brightness -import android.content.Intent import android.graphics.Rect import android.os.Handler import android.testing.AndroidTestingRunner @@ -25,9 +24,9 @@ import android.view.View import android.view.ViewGroup import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any @@ -59,19 +58,17 @@ class BrightnessDialogTest : SysuiTestCase() { @JvmField var activityRule = ActivityTestRule( - object : SingleActivityFactory<TestDialog>(TestDialog::class.java) { - override fun create(intent: Intent?): TestDialog { - return TestDialog( - userTracker, - displayTracker, - brightnessSliderControllerFactory, - mainExecutor, - backgroundHandler - ) - } + /* activityFactory= */ SingleActivityFactory { + TestDialog( + userTracker, + displayTracker, + brightnessSliderControllerFactory, + mainExecutor, + backgroundHandler + ) }, - false, - false + /* initialTouchMode= */ false, + /* launchActivity= */ false, ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index 2fbe87158eba..ea70e9e44c66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -32,7 +32,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder @@ -46,11 +45,14 @@ import com.android.systemui.statusbar.notification.interruption.KeyguardNotifica import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat +import java.util.function.Consumer +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestCoroutineScheduler @@ -62,9 +64,8 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.same import org.mockito.Mockito.anyString import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never import org.mockito.Mockito.verify -import java.util.function.Consumer -import kotlin.time.Duration.Companion.seconds import org.mockito.Mockito.`when` as whenever @SmallTest @@ -75,7 +76,6 @@ class KeyguardCoordinatorTest : SysuiTestCase() { private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock() private val keyguardRepository = FakeKeyguardRepository() private val keyguardTransitionRepository = FakeKeyguardTransitionRepository() - private val notifPipelineFlags: NotifPipelineFlags = mock() private val notifPipeline: NotifPipeline = mock() private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock() private val statusBarStateController: StatusBarStateController = mock() @@ -136,13 +136,8 @@ class KeyguardCoordinatorTest : SysuiTestCase() { ) testScheduler.runCurrent() - // WHEN: The shade is expanded - whenever(statusBarStateController.isExpanded).thenReturn(true) - statusBarStateListener.onExpandedChanged(true) - testScheduler.runCurrent() - - // THEN: The notification is still treated as "unseen" and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + // THEN: We are no longer listening for shade expansions + verify(statusBarStateController, never()).addCallback(any()) } } @@ -152,6 +147,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() { keyguardRepository.setKeyguardShowing(false) whenever(statusBarStateController.isExpanded).thenReturn(false) runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) + // WHEN: A notification is posted val fakeEntry = NotificationEntryBuilder().build() collectionListener.onEntryAdded(fakeEntry) @@ -162,6 +161,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() { // WHEN: The keyguard is now showing keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD) + ) testScheduler.runCurrent() // THEN: The notification is recognized as "seen" and is filtered out. @@ -169,6 +171,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() { // WHEN: The keyguard goes away keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE) + ) testScheduler.runCurrent() // THEN: The notification is shown regardless @@ -182,9 +187,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() { keyguardRepository.setKeyguardShowing(false) whenever(statusBarStateController.isExpanded).thenReturn(true) runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder() + val fakeEntry = + NotificationEntryBuilder() .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build()) - .build() + .build() collectionListener.onEntryAdded(fakeEntry) // WHEN: The keyguard is now showing @@ -202,11 +208,13 @@ class KeyguardCoordinatorTest : SysuiTestCase() { keyguardRepository.setKeyguardShowing(false) whenever(statusBarStateController.isExpanded).thenReturn(true) runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder().build().apply { - row = mock<ExpandableNotificationRow>().apply { - whenever(isMediaRow).thenReturn(true) + val fakeEntry = + NotificationEntryBuilder().build().apply { + row = + mock<ExpandableNotificationRow>().apply { + whenever(isMediaRow).thenReturn(true) + } } - } collectionListener.onEntryAdded(fakeEntry) // WHEN: The keyguard is now showing @@ -299,14 +307,12 @@ class KeyguardCoordinatorTest : SysuiTestCase() { runKeyguardCoordinatorTest { // WHEN: A new notification is posted val fakeSummary = NotificationEntryBuilder().build() - val fakeChild = NotificationEntryBuilder() + val fakeChild = + NotificationEntryBuilder() .setGroup(context, "group") .setGroupSummary(context, false) .build() - GroupEntryBuilder() - .setSummary(fakeSummary) - .addChild(fakeChild) - .build() + GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build() collectionListener.onEntryAdded(fakeSummary) collectionListener.onEntryAdded(fakeChild) @@ -331,6 +337,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() { runKeyguardCoordinatorTest { val fakeEntry = NotificationEntryBuilder().build() collectionListener.onEntryAdded(fakeEntry) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() // WHEN: five seconds have passed testScheduler.advanceTimeBy(5.seconds) @@ -338,10 +348,16 @@ class KeyguardCoordinatorTest : SysuiTestCase() { // WHEN: Keyguard is no longer showing keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) testScheduler.runCurrent() // WHEN: Keyguard is shown again keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD) + ) testScheduler.runCurrent() // THEN: The notification is now recognized as "seen" and is filtered out. @@ -354,11 +370,17 @@ class KeyguardCoordinatorTest : SysuiTestCase() { // GIVEN: Keyguard is showing, unseen notification is present keyguardRepository.setKeyguardShowing(true) runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) val fakeEntry = NotificationEntryBuilder().build() collectionListener.onEntryAdded(fakeEntry) // WHEN: Keyguard is no longer showing keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) // WHEN: Keyguard is shown again keyguardRepository.setKeyguardShowing(true) @@ -369,14 +391,212 @@ class KeyguardCoordinatorTest : SysuiTestCase() { } } + @Test + fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() { + // GIVEN: Keyguard is showing, not dozing, unseen notification is present + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + val firstEntry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(firstEntry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: another unseen notification is posted + val secondEntry = NotificationEntryBuilder().setId(2).build() + collectionListener.onEntryAdded(secondEntry) + testScheduler.runCurrent() + + // WHEN: four more seconds have passed + testScheduler.advanceTimeBy(4.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // THEN: The first notification is considered seen and is filtered out. + assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue() + + // THEN: The second notification is still considered unseen and is not filtered out + assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: five more seconds have passed + testScheduler.advanceTimeBy(5.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is removed + collectionListener.onEntryRemoved(entry, 0) + testScheduler.runCurrent() + + // WHEN: the notification is re-posted + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one more second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is removed + collectionListener.onEntryRemoved(entry, 0) + testScheduler.runCurrent() + + // WHEN: the notification is re-posted + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one more second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is updated + collectionListener.onEntryUpdated(entry) + testScheduler.runCurrent() + + // WHEN: four more seconds have passed + testScheduler.advanceTimeBy(4.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + private fun runKeyguardCoordinatorTest( testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit ) { val testDispatcher = UnconfinedTestDispatcher() val testScope = TestScope(testDispatcher) - val fakeSettings = FakeSettings().apply { - putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1) - } + val fakeSettings = + FakeSettings().apply { + putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1) + } val seenNotificationsProvider = SeenNotificationsProviderImpl() val keyguardCoordinator = KeyguardCoordinator( @@ -387,7 +607,6 @@ class KeyguardCoordinatorTest : SysuiTestCase() { keyguardRepository, keyguardTransitionRepository, mock<KeyguardCoordinatorLogger>(), - notifPipelineFlags, testScope.backgroundScope, sectionHeaderVisibilityProvider, fakeSettings, @@ -397,11 +616,12 @@ class KeyguardCoordinatorTest : SysuiTestCase() { keyguardCoordinator.attach(notifPipeline) testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) { KeyguardCoordinatorTestScope( - keyguardCoordinator, - testScope, - seenNotificationsProvider, - fakeSettings, - ).testBlock() + keyguardCoordinator, + testScope, + seenNotificationsProvider, + fakeSettings, + ) + .testBlock() } } @@ -414,10 +634,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() { val testScheduler: TestCoroutineScheduler get() = scope.testScheduler - val onStateChangeListener: Consumer<String> = - withArgCaptor { - verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture()) - } + val onStateChangeListener: Consumer<String> = withArgCaptor { + verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture()) + } val unseenFilter: NotifFilter get() = keyguardCoordinator.unseenNotifFilter @@ -426,11 +645,11 @@ class KeyguardCoordinatorTest : SysuiTestCase() { verify(notifPipeline).addCollectionListener(capture()) } - val onHeadsUpChangedListener: OnHeadsUpChangedListener get() = - withArgCaptor { verify(headsUpManager).addListener(capture()) } + val onHeadsUpChangedListener: OnHeadsUpChangedListener + get() = withArgCaptor { verify(headsUpManager).addListener(capture()) } - val statusBarStateListener: StatusBarStateController.StateListener get() = - withArgCaptor { verify(statusBarStateController).addCallback(capture()) } + val statusBarStateListener: StatusBarStateController.StateListener + get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) } var showOnlyUnseenNotifsOnKeyguardSetting: Boolean get() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt index b30c20db642d..b04eb01201fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt @@ -25,8 +25,8 @@ import android.testing.TestableLooper import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.google.common.truth.Truth.assertThat import javax.inject.Inject @@ -49,19 +49,17 @@ class UsbPermissionActivityTest : SysuiTestCase() { open class UsbPermissionActivityTestable @Inject constructor ( val message: UsbAudioWarningDialogMessage - ) - : UsbPermissionActivity(UsbAudioWarningDialogMessage()) + ) : UsbPermissionActivity(UsbAudioWarningDialogMessage()) @Rule @JvmField - var activityRule = ActivityTestRule<UsbPermissionActivityTestable>( - object : SingleActivityFactory<UsbPermissionActivityTestable>( - UsbPermissionActivityTestable::class.java - ) { - override fun create(intent: Intent?): UsbPermissionActivityTestable { - return UsbPermissionActivityTestable(mMessage) - } - }, false, false) + var activityRule = ActivityTestRule( + /* activityFactory= */ SingleActivityFactory { + UsbPermissionActivityTestable(mMessage) + }, + /* initialTouchMode= */ false, + /* launchActivity= */ false, + ) private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java) .apply { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt new file mode 100644 index 000000000000..5a92fb1dbe7f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt @@ -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.systemui.activity + +import android.app.Activity +import android.content.Intent +import androidx.test.runner.intercepting.SingleActivityFactory + +/** + * Builds a new [SingleActivityFactory] which delegating any call of [SingleActivityFactory.create] + * to the [instantiate] parameter. + * + * For more details, see [SingleActivityFactory]. + */ +inline fun <reified T : Activity> SingleActivityFactory( + crossinline instantiate: (intent: Intent?) -> T, +): SingleActivityFactory<T> { + return object : SingleActivityFactory<T>(T::class.java) { + override fun create(intent: Intent?): T = instantiate(intent) + } +} diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index e85eee817d29..e3262cfbd30b 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.content.pm.PackageManager.PERMISSION_GRANTED; 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 @@ public class Vpn { 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 @@ public class Vpn { private final INetworkManagementService mNms; private final INetd mNetd; @VisibleForTesting + @GuardedBy("this") protected VpnConfig mConfig; private final NetworkProvider mNetworkProvider; @VisibleForTesting @@ -392,7 +399,6 @@ public class Vpn { private final UserManager mUserManager; private final VpnProfileStore mVpnProfileStore; - protected boolean mDataStallSuspected = false; @VisibleForTesting VpnProfileStore getVpnProfileStore() { @@ -685,14 +691,14 @@ public class Vpn { } /** - * 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 @@ public class Vpn { 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 @@ public class Vpn { * 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 class Vpn { */ 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 @@ public class Vpn { } }; + @GuardedBy("this") private void cleanupVpnStateLocked() { mStatusIntent = null; resetNetworkCapabilities(); @@ -2837,9 +2852,7 @@ public class Vpn { } 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 @@ public class Vpn { @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 @@ public class Vpn { * <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 @@ public class Vpn { } }; + // GuardedBy("Vpn.this") (annotation can't be applied to constructor) IkeV2VpnRunner( @NonNull Ikev2VpnProfile profile, @NonNull ScheduledThreadPoolExecutor executor) { super(TAG); @@ -3136,15 +3149,6 @@ public class Vpn { 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 class Vpn { } 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 @@ public class Vpn { } } - 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 @@ public class Vpn { // 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 @@ public class Vpn { // 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 @@ public class Vpn { * 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 @@ public class Vpn { mCarrierConfigManager.unregisterCarrierConfigChangeListener( mCarrierConfigChangeListener); mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); - mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback( - mDiagnosticsCallback); clearVpnNetworkPreference(mSessionKey); mExecutor.shutdown(); @@ -4293,6 +4286,7 @@ public class Vpn { } }; + // 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 @@ public class Vpn { } // Set the interface and the addresses in the config. - mConfig.interfaze = parameters[0].trim(); + 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]); - } + 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 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(" ")); + // 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); - } + // 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) { + // Here is the last step and it must be done synchronously. // Set the start time mConfig.startTime = SystemClock.elapsedRealtime(); @@ -4773,25 +4767,26 @@ public class Vpn { 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 @@ public class Vpn { 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 @@ public class Vpn { 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 6a0550b60c15..aa99dab8f007 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 @@ final class HandwritingModeController { 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 1ab83f7c5fe5..02ee96a04b1f 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 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub 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 9402fca9574f..279a48084252 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.AccessibilityService; 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { || (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 @@ public class PhoneWindowManager implements WindowManagerPolicy { event.getEventTime())); } } - return -1; + return true; } private void handleDoubleTapOnHome() { @@ -2949,24 +2952,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { @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 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - // 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { } 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { // 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { } 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingMetaAction = false; } } - return key_consumed; + return true; case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: if (down) { @@ -3305,14 +3344,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { && (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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java index bf511adf0bf9..2394da91684d 100644 --- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java +++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java @@ -433,7 +433,7 @@ class LaunchParamsPersister { final byte[] data = saveParamsToXml(); final File launchParamFolder = getLaunchParamFolder(mUserId); - if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdirs()) { + if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdir()) { Slog.w(TAG, "Failed to create folder for " + mUserId); return; } diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java index 29c192cc7c48..f882b9b18453 100644 --- a/services/core/java/com/android/server/wm/TaskPersister.java +++ b/services/core/java/com/android/server/wm/TaskPersister.java @@ -509,7 +509,7 @@ public class TaskPersister implements PersisterQueue.Listener { private static boolean createParentDirectory(String filePath) { File parentDir = new File(filePath).getParentFile(); - return parentDir.exists() || parentDir.mkdirs(); + return parentDir.isDirectory() || parentDir.mkdir(); } private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index e76cbe448f15..c065cb5f4ebe 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -971,7 +971,7 @@ void NativeInputManager::notifyDropWindow(const sp<IBinder>& token, float x, flo void NativeInputManager::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, const std::set<gui::Uid>& uids) { static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS = - sysprop::InputProperties::enable_input_device_usage_metrics().value_or(false); + sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true); if (!ENABLE_INPUT_DEVICE_USAGE_METRICS) return; mInputManager->getMetricsCollector().notifyDeviceInteraction(deviceId, timestamp, uids); 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 6a1674b7df8e..63b8e1710b50 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.BatteryStatsInternal; 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.Mock; 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/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index 7cb7c79d63a0..2b19ad97e90c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -107,6 +107,9 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(); mFolder = new File(cacheFolder, "launch_params_tests"); deleteRecursively(mFolder); + mFolder.mkdir(); + mUserFolderGetter.apply(TEST_USER_ID).mkdir(); + mUserFolderGetter.apply(ALTERNATIVE_USER_ID).mkdir(); mDisplayUniqueId = "test:" + sNextUniqueId++; mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500) diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp index 90e61c52da68..0d2f2ef46cab 100644 --- a/tests/UiBench/Android.bp +++ b/tests/UiBench/Android.bp @@ -24,6 +24,5 @@ android_test { "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 47211c5fbad1..4fc6ec71f29c 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 1b2c3c60ffd4..06b65a7f9bbf 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 @@ import java.util.concurrent.Semaphore; * 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 @@ public class EditTextTypeActivity extends AppCompatActivity { sSeedText = builder.toString(); } - final Object mLock = new Object(); - boolean mShouldStop = false; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -56,55 +55,13 @@ public class EditTextTypeActivity extends AppCompatActivity { 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(); } } |