diff options
104 files changed, 2235 insertions, 743 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b21187af1271..a1a1c45cdb6f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9233,6 +9233,14 @@ public final class Settings { public static final int DOCK_SETUP_PROMPTED = 3; /** + * Indicates that the user has started dock setup but never finished it. + * One of the possible states for {@link #DOCK_SETUP_STATE}. + * + * @hide + */ + public static final int DOCK_SETUP_INCOMPLETE = 4; + + /** * Indicates that the user has completed dock setup. * One of the possible states for {@link #DOCK_SETUP_STATE}. * @@ -9240,6 +9248,14 @@ public final class Settings { */ public static final int DOCK_SETUP_COMPLETED = 10; + /** + * Indicates that dock setup timed out before the user could complete it. + * One of the possible states for {@link #DOCK_SETUP_STATE}. + * + * @hide + */ + public static final int DOCK_SETUP_TIMED_OUT = 11; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -9247,7 +9263,9 @@ public final class Settings { DOCK_SETUP_STARTED, DOCK_SETUP_PAUSED, DOCK_SETUP_PROMPTED, - DOCK_SETUP_COMPLETED + DOCK_SETUP_INCOMPLETE, + DOCK_SETUP_COMPLETED, + DOCK_SETUP_TIMED_OUT }) public @interface DockSetupState { } diff --git a/core/java/android/service/appprediction/AppPredictionService.java b/core/java/android/service/appprediction/AppPredictionService.java index 4f37cd91b11f..a2ffa5d34219 100644 --- a/core/java/android/service/appprediction/AppPredictionService.java +++ b/core/java/android/service/appprediction/AppPredictionService.java @@ -328,7 +328,7 @@ public abstract class AppPredictionService extends Service { Slog.e(TAG, "Callback is null, likely the binder has died."); return false; } - return mCallback.equals(callback); + return mCallback.asBinder().equals(callback.asBinder()); } public void destroy() { diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java index 3a148dffe6d6..b13a069116af 100644 --- a/core/java/android/service/smartspace/SmartspaceService.java +++ b/core/java/android/service/smartspace/SmartspaceService.java @@ -302,7 +302,7 @@ public abstract class SmartspaceService extends Service { Slog.e(TAG, "Callback is null, likely the binder has died."); return false; } - return mCallback.equals(callback); + return mCallback.asBinder().equals(callback.asBinder()); } @Override diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8d52d001da2e..4988362f38db 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -8747,6 +8747,10 @@ public final class ViewRootImpl implements ViewParent, mAdded = false; AnimationHandler.removeRequestor(this); } + if (mSyncBufferCallback != null) { + mSyncBufferCallback.onBufferReady(null); + mSyncBufferCallback = null; + } WindowManagerGlobal.getInstance().doRemoveView(this); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index cd61dbb5b7d1..f6d67d858f98 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -47,7 +47,7 @@ public class PipBoundsAlgorithm { private final @NonNull PipBoundsState mPipBoundsState; private final PipSnapAlgorithm mSnapAlgorithm; - private final PipKeepClearAlgorithm mPipKeepClearAlgorithm; + private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm; private float mDefaultSizePercent; private float mMinAspectRatioForMinSize; @@ -62,7 +62,7 @@ public class PipBoundsAlgorithm { public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState, @NonNull PipSnapAlgorithm pipSnapAlgorithm, - @NonNull PipKeepClearAlgorithm pipKeepClearAlgorithm) { + @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm) { mPipBoundsState = pipBoundsState; mSnapAlgorithm = pipSnapAlgorithm; mPipKeepClearAlgorithm = pipKeepClearAlgorithm; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java index e3495e100c62..5045cf905ee6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java @@ -24,7 +24,7 @@ import java.util.Set; * Interface for interacting with keep clear algorithm used to move PiP window out of the way of * keep clear areas. */ -public interface PipKeepClearAlgorithm { +public interface PipKeepClearAlgorithmInterface { /** * Adjust the position of picture in picture window based on the registered keep clear areas. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java index 690505e03fce..ed8dc7ded654 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java @@ -26,14 +26,14 @@ import android.view.Gravity; import com.android.wm.shell.R; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipKeepClearAlgorithm; +import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; import java.util.Set; /** * Calculates the adjusted position that does not occlude keep clear areas. */ -public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm { +public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterface { private boolean mKeepClearAreaGravityEnabled = SystemProperties.getBoolean( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 3153313de42f..e83854e22fa2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -46,8 +46,6 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; import android.os.SystemProperties; -import android.os.UserHandle; -import android.os.UserManager; import android.util.Pair; import android.util.Size; import android.view.DisplayInfo; @@ -85,7 +83,7 @@ import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipAppOpsListener; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipKeepClearAlgorithm; +import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSnapAlgorithm; @@ -137,7 +135,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb private PipAppOpsListener mAppOpsListener; private PipMediaController mMediaController; private PipBoundsAlgorithm mPipBoundsAlgorithm; - private PipKeepClearAlgorithm mPipKeepClearAlgorithm; + private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm; private PipBoundsState mPipBoundsState; private PipMotionHelper mPipMotionHelper; private PipTouchHandler mTouchHandler; @@ -380,7 +378,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipAnimationController pipAnimationController, PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, - PipKeepClearAlgorithm pipKeepClearAlgorithm, + PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, PipBoundsState pipBoundsState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, @@ -419,7 +417,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipAnimationController pipAnimationController, PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, - PipKeepClearAlgorithm pipKeepClearAlgorithm, + PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, @NonNull PipBoundsState pipBoundsState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index ce34d2f9547d..1ff77f7d36dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -39,7 +39,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.pip.PipBoundsAlgorithm; -import com.android.wm.shell.pip.PipKeepClearAlgorithm; +import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -65,7 +65,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { @NonNull TvPipBoundsState tvPipBoundsState, @NonNull PipSnapAlgorithm pipSnapAlgorithm) { super(context, tvPipBoundsState, pipSnapAlgorithm, - new PipKeepClearAlgorithm() {}); + new PipKeepClearAlgorithmInterface() {}); this.mTvPipBoundsState = tvPipBoundsState; this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(); reloadResources(context); 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 1488469759cd..a6c4ac28c1fd 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 @@ -1072,6 +1072,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.removeAllTasks(wct, false /* toTop */); mMainStage.deactivate(wct, false /* toTop */); wct.reorder(mRootTaskInfo.token, false /* onTop */); + wct.setForceTranslucent(mRootTaskInfo.token, true); wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); onTransitionAnimationComplete(); } else { @@ -1103,6 +1104,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */); mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */); finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */); + finishedWCT.setForceTranslucent(mRootTaskInfo.token, true); finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); mSyncQueue.queue(finishedWCT); mSyncQueue.runInSync(at -> { @@ -1485,6 +1487,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } private void onStageVisibilityChanged(StageListenerImpl stageListener) { + // If split didn't active, just ignore this callback because we should already did these + // on #applyExitSplitScreen. + if (!isSplitActive()) { + return; + } + final boolean sideStageVisible = mSideStageListener.mVisible; final boolean mainStageVisible = mMainStageListener.mVisible; @@ -1493,20 +1501,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } + // Check if it needs to dismiss split screen when both stage invisible. + if (!mainStageVisible && mExitSplitScreenOnHide) { + exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME); + return; + } + final WindowContainerTransaction wct = new WindowContainerTransaction(); if (!mainStageVisible) { + // Split entering background. wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, true /* setReparentLeafTaskIfRelaunch */); wct.setForceTranslucent(mRootTaskInfo.token, true); - // Both stages are not visible, check if it needs to dismiss split screen. - if (mExitSplitScreenOnHide) { - exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME); - } } else { wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false /* setReparentLeafTaskIfRelaunch */); - wct.setForceTranslucent(mRootTaskInfo.token, false); } + mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { setDividerVisibility(mainStageVisible, t); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 1e72c565157a..129924ad5d05 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -136,7 +136,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) { - int statusBarColor = taskInfo.taskDescription.getStatusBarColor(); + final int statusBarColor = taskInfo.taskDescription.getStatusBarColor(); decoration.setCaptionColor(statusBarColor); } @@ -152,7 +152,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { SurfaceControl taskSurface, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId); + final CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId); if (oldDecoration != null) { // close the old decoration if it exists to avoid two window decorations being added oldDecoration.close(); @@ -169,9 +169,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); - TaskPositioner taskPositioner = + final TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration); - CaptionTouchEventListener touchEventListener = + final CaptionTouchEventListener touchEventListener = new CaptionTouchEventListener(taskInfo, taskPositioner); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); windowDecoration.setDragResizeCallback(taskPositioner); @@ -221,11 +221,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { if (e.getAction() != MotionEvent.ACTION_DOWN) { return false; } - RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (taskInfo.isFocused) { return false; } - WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reorder(mTaskToken, true /* onTop */); mSyncQueue.queue(wct); return true; @@ -236,7 +236,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { * @return {@code true} if a drag is happening; or {@code false} if it is not */ private void handleEventForMove(MotionEvent e) { - RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 8609c6b0302a..d26f1fc8ef1b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -48,15 +48,13 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL private View.OnClickListener mOnCaptionButtonClickListener; private View.OnTouchListener mOnCaptionTouchListener; private DragResizeCallback mDragResizeCallback; - private DragResizeInputListener mDragResizeListener; + private final DragDetector mDragDetector; private RelayoutParams mRelayoutParams = new RelayoutParams(); private final RelayoutResult<WindowDecorLinearLayout> mResult = new RelayoutResult<>(); - private DragDetector mDragDetector; - CaptionWindowDecoration( Context context, DisplayController displayController, @@ -104,14 +102,14 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM; final boolean isDragResizeable = isFreeform && taskInfo.isResizeable; - WindowDecorLinearLayout oldRootView = mResult.mRootView; + final WindowDecorLinearLayout oldRootView = mResult.mRootView; final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - int outsetLeftId = R.dimen.freeform_resize_handle; - int outsetTopId = R.dimen.freeform_resize_handle; - int outsetRightId = R.dimen.freeform_resize_handle; - int outsetBottomId = R.dimen.freeform_resize_handle; + final int outsetLeftId = R.dimen.freeform_resize_handle; + final int outsetTopId = R.dimen.freeform_resize_handle; + final int outsetRightId = R.dimen.freeform_resize_handle; + final int outsetBottomId = R.dimen.freeform_resize_handle; mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; @@ -123,7 +121,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL } relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); - taskInfo = null; // Clear it just in case we use it accidentally + // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo mTaskOrganizer.applyTransaction(wct); @@ -152,12 +150,13 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mDragResizeCallback); } - int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop(); + final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()) + .getScaledTouchSlop(); mDragDetector.setTouchSlop(touchSlop); - int resize_handle = mResult.mRootView.getResources() + final int resize_handle = mResult.mRootView.getResources() .getDimensionPixelSize(R.dimen.freeform_resize_handle); - int resize_corner = mResult.mRootView.getResources() + final int resize_corner = mResult.mRootView.getResources() .getDimensionPixelSize(R.dimen.freeform_resize_corner); mDragResizeListener.setGeometry( mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop); @@ -167,15 +166,15 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL * Sets up listeners when a new root view is created. */ private void setupRootView() { - View caption = mResult.mRootView.findViewById(R.id.caption); + final View caption = mResult.mRootView.findViewById(R.id.caption); caption.setOnTouchListener(mOnCaptionTouchListener); - View close = caption.findViewById(R.id.close_window); + final View close = caption.findViewById(R.id.close_window); close.setOnClickListener(mOnCaptionButtonClickListener); - View back = caption.findViewById(R.id.back_button); + final View back = caption.findViewById(R.id.back_button); back.setOnClickListener(mOnCaptionButtonClickListener); - View minimize = caption.findViewById(R.id.minimize_window); + final View minimize = caption.findViewById(R.id.minimize_window); minimize.setOnClickListener(mOnCaptionButtonClickListener); - View maximize = caption.findViewById(R.id.maximize_window); + final View maximize = caption.findViewById(R.id.maximize_window); maximize.setOnClickListener(mOnCaptionButtonClickListener); } @@ -184,31 +183,31 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL return; } - View caption = mResult.mRootView.findViewById(R.id.caption); - GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground(); + final View caption = mResult.mRootView.findViewById(R.id.caption); + final GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground(); captionDrawable.setColor(captionColor); - int buttonTintColorRes = + final int buttonTintColorRes = Color.valueOf(captionColor).luminance() < 0.5 ? R.color.decor_button_light_color : R.color.decor_button_dark_color; - ColorStateList buttonTintColor = + final ColorStateList buttonTintColor = caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */); - View back = caption.findViewById(R.id.back_button); - VectorDrawable backBackground = (VectorDrawable) back.getBackground(); + final View back = caption.findViewById(R.id.back_button); + final VectorDrawable backBackground = (VectorDrawable) back.getBackground(); backBackground.setTintList(buttonTintColor); - View minimize = caption.findViewById(R.id.minimize_window); - VectorDrawable minimizeBackground = (VectorDrawable) minimize.getBackground(); + final View minimize = caption.findViewById(R.id.minimize_window); + final VectorDrawable minimizeBackground = (VectorDrawable) minimize.getBackground(); minimizeBackground.setTintList(buttonTintColor); - View maximize = caption.findViewById(R.id.maximize_window); - VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground(); + final View maximize = caption.findViewById(R.id.maximize_window); + final VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground(); maximizeBackground.setTintList(buttonTintColor); - View close = caption.findViewById(R.id.close_window); - VectorDrawable closeBackground = (VectorDrawable) close.getBackground(); + final View close = caption.findViewById(R.id.close_window); + final VectorDrawable closeBackground = (VectorDrawable) close.getBackground(); closeBackground.setTintList(buttonTintColor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 13b4a956e4e5..2863adcc8f5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -68,9 +68,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final Choreographer mMainChoreographer; private final DisplayController mDisplayController; private final SyncTransactionQueue mSyncQueue; - private FreeformTaskTransitionStarter mTransitionStarter; - private Optional<DesktopModeController> mDesktopModeController; - private Optional<DesktopTasksController> mDesktopTasksController; + private final Optional<DesktopModeController> mDesktopModeController; + private final Optional<DesktopTasksController> mDesktopTasksController; private boolean mTransitionDragActive; private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>(); @@ -78,7 +77,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl(); - private InputMonitorFactory mInputMonitorFactory; + private final InputMonitorFactory mInputMonitorFactory; private TaskOperations mTaskOperations; public DesktopModeWindowDecorViewModel( @@ -199,7 +198,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (decoration == null) return; decoration.close(); - int displayId = taskInfo.displayId; + final int displayId = taskInfo.displayId; if (mEventReceiversByDisplay.contains(displayId)) { removeTaskFromEventReceiver(displayId); } @@ -227,7 +226,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public void onClick(View v) { - DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); final int id = v.getId(); if (id == R.id.close_window) { mTaskOperations.closeTask(mTaskToken); @@ -250,7 +249,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public boolean onTouch(View v, MotionEvent e) { boolean isDrag = false; - int id = v.getId(); + final int id = v.getId(); if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) { return false; } @@ -261,11 +260,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (e.getAction() != MotionEvent.ACTION_DOWN) { return isDrag; } - RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (taskInfo.isFocused) { return isDrag; } - WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reorder(mTaskToken, true /* onTop */); mSyncQueue.queue(wct); return true; @@ -276,7 +275,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { * @return {@code true} if a drag is happening; or {@code false} if it is not */ private void handleEventForMove(MotionEvent e) { - RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (DesktopModeStatus.isProto2Enabled() && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { return; @@ -295,16 +294,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { break; } case MotionEvent.ACTION_MOVE: { - int dragPointerIdx = e.findPointerIndex(mDragPointerId); + final int dragPointerIdx = e.findPointerIndex(mDragPointerId); mDragResizeCallback.onDragResizeMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); break; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { - int dragPointerIdx = e.findPointerIndex(mDragPointerId); - int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId) - .stableInsets().top; + final int dragPointerIdx = e.findPointerIndex(mDragPointerId); + final int statusBarHeight = mDisplayController + .getDisplayLayout(taskInfo.displayId).stableInsets().top; mDragResizeCallback.onDragResizeEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); if (e.getRawY(dragPointerIdx) <= statusBarHeight) { @@ -378,7 +377,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { */ private void incrementEventReceiverTasks(int displayId) { if (mEventReceiversByDisplay.contains(displayId)) { - EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId); + final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId); eventReceiver.incrementTaskNumber(); } else { createInputChannel(displayId); @@ -388,7 +387,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // If all tasks on this display are gone, we don't need to monitor its input. private void removeTaskFromEventReceiver(int displayId) { if (!mEventReceiversByDisplay.contains(displayId)) return; - EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId); + final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId); if (eventReceiver == null) return; eventReceiver.decrementTaskNumber(); if (eventReceiver.getTasksOnDisplay() == 0) { @@ -403,7 +402,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { */ private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) { if (DesktopModeStatus.isProto2Enabled()) { - DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); + final DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); if (focusedDecor == null || focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) { handleCaptionThroughStatusBar(ev); @@ -428,9 +427,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu private void handleEventOutsideFocusedCaption(MotionEvent ev) { - int action = ev.getActionMasked(); + final int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { - DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); + final DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); if (focusedDecor == null) { return; } @@ -450,7 +449,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: { // Begin drag through status bar if applicable. - DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); + final DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); if (focusedDecor != null) { boolean dragFromStatusBarAllowed = false; if (DesktopModeStatus.isProto2Enabled()) { @@ -469,14 +468,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { break; } case MotionEvent.ACTION_UP: { - DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); + final DesktopModeWindowDecoration focusedDecor = getFocusedDecor(); if (focusedDecor == null) { mTransitionDragActive = false; return; } if (mTransitionDragActive) { mTransitionDragActive = false; - int statusBarHeight = mDisplayController + final int statusBarHeight = mDisplayController .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top; if (ev.getY() > statusBarHeight) { if (DesktopModeStatus.isProto2Enabled()) { @@ -500,10 +499,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Nullable private DesktopModeWindowDecoration getFocusedDecor() { - int size = mWindowDecorByTaskId.size(); + final int size = mWindowDecorByTaskId.size(); DesktopModeWindowDecoration focusedDecor = null; for (int i = 0; i < size; i++) { - DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i); + final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i); if (decor != null && decor.isFocused()) { focusedDecor = decor; break; @@ -513,16 +512,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } private void createInputChannel(int displayId) { - InputManager inputManager = InputManager.getInstance(); - InputMonitor inputMonitor = + final InputManager inputManager = InputManager.getInstance(); + final InputMonitor inputMonitor = mInputMonitorFactory.create(inputManager, mContext); - EventReceiver eventReceiver = new EventReceiver(inputMonitor, + final EventReceiver eventReceiver = new EventReceiver(inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper()); mEventReceiversByDisplay.put(displayId, eventReceiver); } private void disposeInputChannel(int displayId) { - EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId); + final EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId); if (eventReceiver != null) { eventReceiver.dispose(); } @@ -541,7 +540,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { SurfaceControl taskSurface, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - DesktopModeWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId); + final DesktopModeWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId); if (oldDecoration != null) { // close the old decoration if it exists to avoid two window decorations being added oldDecoration.close(); @@ -558,9 +557,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); - TaskPositioner taskPositioner = + final TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener); - DesktopModeTouchEventListener touchEventListener = + final DesktopModeTouchEventListener touchEventListener = new DesktopModeTouchEventListener( taskInfo, taskPositioner, windowDecoration.getDragDetector()); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 9c2beb9c4b2b..1a38d24a4ab1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -56,17 +56,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private View.OnClickListener mOnCaptionButtonClickListener; private View.OnTouchListener mOnCaptionTouchListener; private DragResizeCallback mDragResizeCallback; - private DragResizeInputListener mDragResizeListener; + private final DragDetector mDragDetector; private RelayoutParams mRelayoutParams = new RelayoutParams(); private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult = new WindowDecoration.RelayoutResult<>(); private boolean mDesktopActive; - - private DragDetector mDragDetector; - private AdditionalWindow mHandleMenu; DesktopModeWindowDecoration( @@ -121,14 +118,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM; final boolean isDragResizeable = isFreeform && taskInfo.isResizeable; - WindowDecorLinearLayout oldRootView = mResult.mRootView; + final WindowDecorLinearLayout oldRootView = mResult.mRootView; final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - int outsetLeftId = R.dimen.freeform_resize_handle; - int outsetTopId = R.dimen.freeform_resize_handle; - int outsetRightId = R.dimen.freeform_resize_handle; - int outsetBottomId = R.dimen.freeform_resize_handle; + final int outsetLeftId = R.dimen.freeform_resize_handle; + final int outsetTopId = R.dimen.freeform_resize_handle; + final int outsetRightId = R.dimen.freeform_resize_handle; + final int outsetBottomId = R.dimen.freeform_resize_handle; mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; @@ -152,7 +149,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mRelayoutParams.setCaptionPosition(captionLeft, captionTop); relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); - taskInfo = null; // Clear it just in case we use it accidentally + // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo mTaskOrganizer.applyTransaction(wct); @@ -197,12 +194,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mDragResizeCallback); } - int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop(); + final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()) + .getScaledTouchSlop(); mDragDetector.setTouchSlop(touchSlop); - int resize_handle = mResult.mRootView.getResources() + final int resize_handle = mResult.mRootView.getResources() .getDimensionPixelSize(R.dimen.freeform_resize_handle); - int resize_corner = mResult.mRootView.getResources() + final int resize_corner = mResult.mRootView.getResources() .getDimensionPixelSize(R.dimen.freeform_resize_corner); mDragResizeListener.setGeometry( mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop); @@ -212,27 +210,27 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Sets up listeners when a new root view is created. */ private void setupRootView() { - View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); + final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); caption.setOnTouchListener(mOnCaptionTouchListener); - View close = caption.findViewById(R.id.close_window); + final View close = caption.findViewById(R.id.close_window); close.setOnClickListener(mOnCaptionButtonClickListener); - View back = caption.findViewById(R.id.back_button); + final View back = caption.findViewById(R.id.back_button); back.setOnClickListener(mOnCaptionButtonClickListener); - View handle = caption.findViewById(R.id.caption_handle); + final View handle = caption.findViewById(R.id.caption_handle); handle.setOnTouchListener(mOnCaptionTouchListener); handle.setOnClickListener(mOnCaptionButtonClickListener); updateButtonVisibility(); } private void setupHandleMenu() { - View menu = mHandleMenu.mWindowViewHost.getView(); - View fullscreen = menu.findViewById(R.id.fullscreen_button); + final View menu = mHandleMenu.mWindowViewHost.getView(); + final View fullscreen = menu.findViewById(R.id.fullscreen_button); fullscreen.setOnClickListener(mOnCaptionButtonClickListener); - View desktop = menu.findViewById(R.id.desktop_button); + final View desktop = menu.findViewById(R.id.desktop_button); desktop.setOnClickListener(mOnCaptionButtonClickListener); - View split = menu.findViewById(R.id.split_screen_button); + final View split = menu.findViewById(R.id.split_screen_button); split.setOnClickListener(mOnCaptionButtonClickListener); - View more = menu.findViewById(R.id.more_button); + final View more = menu.findViewById(R.id.more_button); more.setOnClickListener(mOnCaptionButtonClickListener); } @@ -242,8 +240,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * @param visible whether or not the caption should be visible */ private void setCaptionVisibility(boolean visible) { - int v = visible ? View.VISIBLE : View.GONE; - View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption); + final int v = visible ? View.VISIBLE : View.GONE; + final View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption); captionView.setVisibility(v); if (!visible) closeHandleMenu(); } @@ -264,19 +262,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Show or hide buttons */ void setButtonVisibility(boolean visible) { - int visibility = visible ? View.VISIBLE : View.GONE; - View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); - View back = caption.findViewById(R.id.back_button); - View close = caption.findViewById(R.id.close_window); + final int visibility = visible ? View.VISIBLE : View.GONE; + final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); + final View back = caption.findViewById(R.id.back_button); + final View close = caption.findViewById(R.id.close_window); back.setVisibility(visibility); close.setVisibility(visibility); - int buttonTintColorRes = + final int buttonTintColorRes = mDesktopActive ? R.color.decor_button_dark_color : R.color.decor_button_light_color; - ColorStateList buttonTintColor = + final ColorStateList buttonTintColor = caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */); - View handle = caption.findViewById(R.id.caption_handle); - VectorDrawable handleBackground = (VectorDrawable) handle.getBackground(); + final View handle = caption.findViewById(R.id.caption_handle); + final VectorDrawable handleBackground = (VectorDrawable) handle.getBackground(); handleBackground.setTintList(buttonTintColor); caption.getBackground().setTint(visible ? Color.WHITE : Color.TRANSPARENT); } @@ -297,12 +295,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Create and display handle menu window */ void createHandleMenu() { - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); final Resources resources = mDecorWindowContext.getResources(); - int x = mRelayoutParams.mCaptionX; - int y = mRelayoutParams.mCaptionY; - int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId); - int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId); + final int x = mRelayoutParams.mCaptionX; + final int y = mRelayoutParams.mCaptionY; + final int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId); + final int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId); String namePrefix = "Caption Menu"; mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY, @@ -353,8 +351,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * @return the point of the input in local space */ private PointF offsetCaptionLocation(MotionEvent ev) { - PointF result = new PointF(ev.getX(), ev.getY()); - Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId) + final PointF result = new PointF(ev.getX(), ev.getY()); + final Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId) .positionInParent; result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY); result.offset(-positionInParent.x, -positionInParent.y); @@ -370,8 +368,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) { if (mResult.mRootView == null) return false; - PointF inputPoint = offsetCaptionLocation(ev); - View view = mResult.mRootView.findViewById(layoutId); + final PointF inputPoint = offsetCaptionLocation(ev); + final View view = mResult.mRootView.findViewById(layoutId); return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0); } @@ -389,20 +387,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ void checkClickEvent(MotionEvent ev) { if (mResult.mRootView == null) return; - View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); - PointF inputPoint = offsetCaptionLocation(ev); + final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); + final PointF inputPoint = offsetCaptionLocation(ev); if (!isHandleMenuActive()) { - View handle = caption.findViewById(R.id.caption_handle); + final View handle = caption.findViewById(R.id.caption_handle); clickIfPointInView(inputPoint, handle); } else { - View menu = mHandleMenu.mWindowViewHost.getView(); - View fullscreen = menu.findViewById(R.id.fullscreen_button); + final View menu = mHandleMenu.mWindowViewHost.getView(); + final View fullscreen = menu.findViewById(R.id.fullscreen_button); if (clickIfPointInView(inputPoint, fullscreen)) return; - View desktop = menu.findViewById(R.id.desktop_button); + final View desktop = menu.findViewById(R.id.desktop_button); if (clickIfPointInView(inputPoint, desktop)) return; - View split = menu.findViewById(R.id.split_screen_button); + final View split = menu.findViewById(R.id.split_screen_button); if (clickIfPointInView(inputPoint, split)) return; - View more = menu.findViewById(R.id.more_button); + final View more = menu.findViewById(R.id.more_button); clickIfPointInView(inputPoint, more); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java index 262e4290ef44..298d0a624869 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java @@ -64,7 +64,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase { initializeMockResources(); mPipBoundsState = new PipBoundsState(mContext); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, - new PipSnapAlgorithm(), new PipKeepClearAlgorithm() {}); + new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}); mPipBoundsState.setDisplayLayout( new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 90880772b25d..17e7d74c57e0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -98,7 +98,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { mPipBoundsState = new PipBoundsState(mContext); mPipTransitionState = new PipTransitionState(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, - new PipSnapAlgorithm(), new PipKeepClearAlgorithm() {}); + new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}); mMainExecutor = new TestShellExecutor(); mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index 3bd2ae76ebfd..c1993b25030b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -37,7 +37,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipKeepClearAlgorithm; +import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; @@ -90,8 +90,8 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { MockitoAnnotations.initMocks(this); mPipBoundsState = new PipBoundsState(mContext); final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm(); - final PipKeepClearAlgorithm pipKeepClearAlgorithm = - new PipKeepClearAlgorithm() {}; + final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm = + new PipKeepClearAlgorithmInterface() {}; final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm); final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 474d6aaf4623..8ad2932b69e4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -34,7 +34,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipKeepClearAlgorithm; +import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; @@ -106,7 +106,7 @@ public class PipTouchHandlerTest extends ShellTestCase { mPipBoundsState = new PipBoundsState(mContext); mPipSnapAlgorithm = new PipSnapAlgorithm(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm, - new PipKeepClearAlgorithm() {}); + new PipKeepClearAlgorithmInterface() {}); PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator); diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index e148d4f547a4..252915703511 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -271,6 +271,7 @@ android_library { "LowLightDreamLib", "motion_tool_lib", "androidx.core_core-animation-testing-nodeps", + "androidx.compose.ui_ui", ], } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index a450d3af334b..9a9236be9c8a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -791,13 +791,13 @@ private class AnimatedDialog( // Move the drawing of the source in the overlay of this dialog, then animate. We trigger a // one-off synchronization to make sure that this is done in sync between the two different // windows. + controller.startDrawingInOverlayOf(decorView) synchronizeNextDraw( then = { isSourceDrawnInDialog = true maybeStartLaunchAnimation() } ) - controller.startDrawingInOverlayOf(decorView) } /** diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index 0028d13ffd5e..dfac02d99c4d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -195,14 +195,16 @@ open class GhostedViewLaunchAnimatorController @JvmOverloads constructor( backgroundDrawable = WrappedDrawable(background) backgroundView?.background = backgroundDrawable + // Delay the calls to `ghostedView.setVisibility()` during the animation. This must be + // called before `GhostView.addGhost()` is called because the latter will change the + // *transition* visibility, which won't be blocked and will affect the normal View + // visibility that is saved by `setShouldBlockVisibilityChanges()` for a later restoration. + (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true) + // Create a ghost of the view that will be moving and fading out. This allows to fade out // the content before fading out the background. ghostView = GhostView.addGhost(ghostedView, launchContainer) - // The ghost was just created, so ghostedView is currently invisible. We need to make sure - // that it stays invisible as long as we are animating. - (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true) - val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX matrix.getValues(initialGhostViewMatrixValues) @@ -297,14 +299,19 @@ open class GhostedViewLaunchAnimatorController @JvmOverloads constructor( backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha GhostView.removeGhost(ghostedView) - (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(false) launchContainerOverlay.remove(backgroundView) - // Make sure that the view is considered VISIBLE by accessibility by first making it - // INVISIBLE then VISIBLE (see b/204944038#comment17 for more info). - ghostedView.visibility = View.INVISIBLE - ghostedView.visibility = View.VISIBLE - ghostedView.invalidate() + if (ghostedView is LaunchableView) { + // Restore the ghosted view visibility. + ghostedView.setShouldBlockVisibilityChanges(false) + } else { + // Make the ghosted view visible. We ensure that the view is considered VISIBLE by + // accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17 + // for more info). + ghostedView.visibility = View.INVISIBLE + ghostedView.visibility = View.VISIBLE + ghostedView.invalidate() + } } companion object { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt index 67b59e0e9928..774255be4007 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt @@ -21,15 +21,19 @@ import android.view.View /** A view that can expand/launch into an app or a dialog. */ interface LaunchableView { /** - * Set whether this view should block/postpone all visibility changes. This ensures that this - * view: + * Set whether this view should block/postpone all calls to [View.setVisibility]. This ensures + * that this view: * - remains invisible during the launch animation given that it is ghosted and already drawn * somewhere else. * - remains invisible as long as a dialog expanded from it is shown. * - restores its expected visibility once the dialog expanded from it is dismissed. * - * Note that when this is set to true, both the [normal][android.view.View.setVisibility] and - * [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked. + * When `setShouldBlockVisibilityChanges(false)` is called, then visibility of the View should + * be restored to its expected value, i.e. it should have the visibility of the last call to + * `View.setVisibility()` that was made after `setShouldBlockVisibilityChanges(true)`, if any, + * or the original view visibility otherwise. + * + * Note that calls to [View.setTransitionVisibility] shouldn't be blocked. * * @param block whether we should block/postpone all calls to `setVisibility` and * `setTransitionVisibility`. @@ -46,27 +50,31 @@ class LaunchableViewDelegate( * super.setVisibility(visibility). */ private val superSetVisibility: (Int) -> Unit, - - /** - * The lambda that should set the actual transition visibility of [view], usually by calling - * super.setTransitionVisibility(visibility). - */ - private val superSetTransitionVisibility: (Int) -> Unit, -) { +) : LaunchableView { private var blockVisibilityChanges = false private var lastVisibility = view.visibility /** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */ - fun setShouldBlockVisibilityChanges(block: Boolean) { + override fun setShouldBlockVisibilityChanges(block: Boolean) { if (block == blockVisibilityChanges) { return } blockVisibilityChanges = block if (block) { + // Save the current visibility for later. lastVisibility = view.visibility } else { - superSetVisibility(lastVisibility) + // Restore the visibility. To avoid accessibility issues, we change the visibility twice + // which makes sure that we trigger a visibility flag change (see b/204944038#comment17 + // for more info). + if (lastVisibility == View.VISIBLE) { + superSetVisibility(View.INVISIBLE) + superSetVisibility(View.VISIBLE) + } else { + superSetVisibility(View.VISIBLE) + superSetVisibility(lastVisibility) + } } } @@ -79,16 +87,4 @@ class LaunchableViewDelegate( superSetVisibility(visibility) } - - /** Call this when [View.setTransitionVisibility] is called. */ - fun setTransitionVisibility(visibility: Int) { - if (blockVisibilityChanges) { - // View.setTransitionVisibility just sets the visibility flag, so we don't have to save - // the transition visibility separately from the normal visibility. - lastVisibility = visibility - return - } - - superSetTransitionVisibility(visibility) - } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt index 964ef8c88098..46d5a5c0af8c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt @@ -34,23 +34,29 @@ internal constructor( override val sourceIdentity: Any = source override fun startDrawingInOverlayOf(viewGroup: ViewGroup) { + // Delay the calls to `source.setVisibility()` during the animation. This must be called + // before `GhostView.addGhost()` is called because the latter will change the *transition* + // visibility, which won't be blocked and will affect the normal View visibility that is + // saved by `setShouldBlockVisibilityChanges()` for a later restoration. + (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true) + // Create a temporary ghost of the source (which will make it invisible) and add it // to the host dialog. GhostView.addGhost(source, viewGroup) - - // The ghost of the source was just created, so the source is currently invisible. - // We need to make sure that it stays invisible as long as the dialog is shown or - // animating. - (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true) } override fun stopDrawingInOverlay() { // Note: here we should remove the ghost from the overlay, but in practice this is - // already done by the launch controllers created below. - - // Make sure we allow the source to change its visibility again. - (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false) - source.visibility = View.VISIBLE + // already done by the launch controller created below. + + if (source is LaunchableView) { + // Make sure we allow the source to change its visibility again and restore its previous + // value. + source.setShouldBlockVisibilityChanges(false) + } else { + // We made the source invisible earlier, so let's make it visible again. + source.visibility = View.VISIBLE + } } override fun createLaunchController(): LaunchAnimator.Controller { @@ -67,10 +73,14 @@ internal constructor( override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { delegate.onLaunchAnimationEnd(isExpandingFullyAbove) - // We hide the source when the dialog is showing. We will make this view - // visible again when dismissing the dialog. This does nothing if the source - // implements [LaunchableView], as it's already INVISIBLE in that case. - source.visibility = View.INVISIBLE + // At this point the view visibility is restored by the delegate, so we delay the + // visibility changes again and make it invisible while the dialog is shown. + if (source is LaunchableView) { + source.setShouldBlockVisibilityChanges(true) + source.setTransitionVisibility(View.INVISIBLE) + } else { + source.visibility = View.INVISIBLE + } } } } @@ -90,13 +100,15 @@ internal constructor( } override fun onExitAnimationCancelled() { - // Make sure we allow the source to change its visibility again. - (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false) - - // If the view is invisible it's probably because of us, so we make it visible - // again. - if (source.visibility == View.INVISIBLE) { - source.visibility = View.VISIBLE + if (source is LaunchableView) { + // Make sure we allow the source to change its visibility again. + source.setShouldBlockVisibilityChanges(false) + } else { + // If the view is invisible it's probably because of us, so we make it visible + // again. + if (source.visibility == View.INVISIBLE) { + source.visibility = View.VISIBLE + } } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt index 67159512a701..79bc2f432ded 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt @@ -57,7 +57,7 @@ data class TurbulenceNoiseAnimationConfig( val onAnimationEnd: Runnable? = null ) { companion object { - const val DEFAULT_MAX_DURATION_IN_MILLIS = 7500f + const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec const val DEFAULT_EASING_DURATION_IN_MILLIS = 750f const val DEFAULT_LUMINOSITY_MULTIPLIER = 1f const val DEFAULT_NOISE_GRID_COUNT = 1.2f diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index 6e728ce7248f..e253fb925ceb 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -17,13 +17,21 @@ package com.android.systemui.compose +import android.content.Context +import android.view.View import androidx.activity.ComponentActivity +import androidx.lifecycle.LifecycleOwner import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel /** The Compose facade, when Compose is *not* available. */ object ComposeFacade : BaseComposeFacade { override fun isComposeAvailable(): Boolean = false + override fun composeInitializer(): ComposeInitializer { + throwComposeUnavailableError() + } + override fun setPeopleSpaceActivityContent( activity: ComponentActivity, viewModel: PeopleViewModel, @@ -32,7 +40,15 @@ object ComposeFacade : BaseComposeFacade { throwComposeUnavailableError() } - private fun throwComposeUnavailableError() { + override fun createFooterActionsView( + context: Context, + viewModel: FooterActionsViewModel, + qsVisibilityLifecycleOwner: LifecycleOwner + ): View { + throwComposeUnavailableError() + } + + private fun throwComposeUnavailableError(): Nothing { error( "Compose is not available. Make sure to check isComposeAvailable() before calling any" + " other function on ComposeFacade." diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index 6991ff82c2d1..1ea18fec4abe 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -16,16 +16,24 @@ package com.android.systemui.compose +import android.content.Context +import android.view.View import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.ui.platform.ComposeView +import androidx.lifecycle.LifecycleOwner import com.android.compose.theme.PlatformTheme import com.android.systemui.people.ui.compose.PeopleScreen import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import com.android.systemui.qs.footer.ui.compose.FooterActions +import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel /** The Compose facade, when Compose is available. */ object ComposeFacade : BaseComposeFacade { override fun isComposeAvailable(): Boolean = true + override fun composeInitializer(): ComposeInitializer = ComposeInitializerImpl + override fun setPeopleSpaceActivityContent( activity: ComponentActivity, viewModel: PeopleViewModel, @@ -33,4 +41,14 @@ object ComposeFacade : BaseComposeFacade { ) { activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } } } + + override fun createFooterActionsView( + context: Context, + viewModel: FooterActionsViewModel, + qsVisibilityLifecycleOwner: LifecycleOwner, + ): View { + return ComposeView(context).apply { + setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } } + } + } } diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt new file mode 100644 index 000000000000..772c8918fd2d --- /dev/null +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt @@ -0,0 +1,78 @@ +/* + * 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.compose + +import android.view.View +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.ViewTreeLifecycleOwner +import androidx.savedstate.SavedStateRegistry +import androidx.savedstate.SavedStateRegistryController +import androidx.savedstate.SavedStateRegistryOwner +import com.android.compose.animation.ViewTreeSavedStateRegistryOwner +import com.android.systemui.lifecycle.ViewLifecycleOwner + +internal object ComposeInitializerImpl : ComposeInitializer { + override fun onAttachedToWindow(root: View) { + if (ViewTreeLifecycleOwner.get(root) != null) { + error("root $root already has a LifecycleOwner") + } + + val parent = root.parent + if (parent is View && parent.id != android.R.id.content) { + error( + "ComposeInitializer.onAttachedToWindow(View) must be called on the content child." + + "Outside of activities and dialogs, this is usually the top-most View of a " + + "window." + ) + } + + // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is + // both visible and focused. + val lifecycleOwner = ViewLifecycleOwner(root) + + // We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save + // or restore because SystemUI process is always running and top-level windows using this + // initializer are created once, when the process is started. + val savedStateRegistryOwner = + object : SavedStateRegistryOwner { + private val savedStateRegistry = + SavedStateRegistryController.create(this).apply { performRestore(null) } + + override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle + + override fun getSavedStateRegistry(): SavedStateRegistry { + return savedStateRegistry.savedStateRegistry + } + } + + // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner] + // because `onCreate` might move the lifecycle state to STARTED which will make + // [SavedStateRegistryController.performRestore] throw. + lifecycleOwner.onCreate() + + // Set the owners on the root. They will be reused by any ComposeView inside the root + // hierarchy. + ViewTreeLifecycleOwner.set(root, lifecycleOwner) + ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner) + } + + override fun onDetachedFromWindow(root: View) { + (ViewTreeLifecycleOwner.get(root) as ViewLifecycleOwner).onDestroy() + ViewTreeLifecycleOwner.set(root, null) + ViewTreeSavedStateRegistryOwner.set(root, null) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt new file mode 100644 index 000000000000..9eb78e14ab4e --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.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.compose.modifiers + +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId + +/** + * Set a test tag on this node so that it is associated with [resId]. This node will then be + * accessible by integration tests using `sysuiResSelector(resId)`. + */ +@OptIn(ExperimentalComposeUiApi::class) +fun Modifier.sysuiResTag(resId: String): Modifier { + return this.semantics { testTagsAsResourceId = true }.testTag("com.android.systemui:id/$resId") +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt index 23dacf9946f3..3eeadae5385f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt @@ -51,6 +51,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.R +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel @@ -110,7 +111,9 @@ private fun PeopleScreenWithConversations( recentTiles: List<PeopleTileViewModel>, onTileClicked: (PeopleTileViewModel) -> Unit, ) { - Column { + Column( + Modifier.sysuiResTag("top_level_with_conversations"), + ) { Column( Modifier.fillMaxWidth().padding(PeopleSpacePadding), horizontalAlignment = Alignment.CenterHorizontally, @@ -132,7 +135,7 @@ private fun PeopleScreenWithConversations( } LazyColumn( - Modifier.fillMaxWidth(), + Modifier.fillMaxWidth().sysuiResTag("scroll_view"), contentPadding = PaddingValues( top = 16.dp, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 5c5ceefbd6fb..349f5c333116 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -73,6 +73,7 @@ import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel @@ -180,9 +181,9 @@ fun FooterActions( security?.let { SecurityButton(it, Modifier.weight(1f)) } foregroundServices?.let { ForegroundServicesButton(it) } - userSwitcher?.let { IconButton(it) } - IconButton(viewModel.settings) - viewModel.power?.let { IconButton(it) } + userSwitcher?.let { IconButton(it, Modifier.sysuiResTag("multi_user_switch")) } + IconButton(viewModel.settings, Modifier.sysuiResTag("settings_button_container")) + viewModel.power?.let { IconButton(it, Modifier.sysuiResTag("pm_lite")) } } } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt index 5bb37071b075..cd9fb886a4e6 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt @@ -20,12 +20,15 @@ package com.android.systemui.shared.customization.data.content import android.annotation.SuppressLint import android.content.ContentValues import android.content.Context +import android.content.Intent import android.database.ContentObserver import android.graphics.Color import android.graphics.drawable.Drawable import android.net.Uri +import android.util.Log import androidx.annotation.DrawableRes import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract +import java.net.URISyntaxException import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -169,6 +172,8 @@ interface CustomizationProviderClient { * If `null`, the button should not be shown. */ val enablementActionComponentName: String? = null, + /** Optional [Intent] to use to start an activity to configure this affordance. */ + val configureIntent: Intent? = null, ) /** Models a selection of a quick affordance on a slot. */ @@ -337,6 +342,11 @@ class CustomizationProviderClientImpl( Contract.LockScreenQuickAffordances.AffordanceTable.Columns .ENABLEMENT_COMPONENT_NAME ) + val configureIntentColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns + .CONFIGURE_INTENT + ) if ( idColumnIndex == -1 || nameColumnIndex == -1 || @@ -344,15 +354,17 @@ class CustomizationProviderClientImpl( isEnabledColumnIndex == -1 || enablementInstructionsColumnIndex == -1 || enablementActionTextColumnIndex == -1 || - enablementComponentNameColumnIndex == -1 + enablementComponentNameColumnIndex == -1 || + configureIntentColumnIndex == -1 ) { return@buildList } while (cursor.moveToNext()) { + val affordanceId = cursor.getString(idColumnIndex) add( CustomizationProviderClient.Affordance( - id = cursor.getString(idColumnIndex), + id = affordanceId, name = cursor.getString(nameColumnIndex), iconResourceId = cursor.getInt(iconColumnIndex), isEnabled = cursor.getInt(isEnabledColumnIndex) == 1, @@ -367,6 +379,10 @@ class CustomizationProviderClientImpl( cursor.getString(enablementActionTextColumnIndex), enablementActionComponentName = cursor.getString(enablementComponentNameColumnIndex), + configureIntent = + cursor + .getString(configureIntentColumnIndex) + ?.toIntent(affordanceId = affordanceId), ) ) } @@ -504,7 +520,19 @@ class CustomizationProviderClientImpl( .onStart { emit(Unit) } } + private fun String.toIntent( + affordanceId: String, + ): Intent? { + return try { + Intent.parseUri(this, 0) + } catch (e: URISyntaxException) { + Log.w(TAG, "Cannot parse Uri into Intent for affordance with ID \"$affordanceId\"!") + null + } + } + companion object { + private const val TAG = "CustomizationProviderClient" private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt index 1e2e7d2595ac..7f1c78fc47ff 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt @@ -113,6 +113,11 @@ object CustomizationProviderContract { * opens a destination where the user can re-enable the disabled affordance. */ const val ENABLEMENT_COMPONENT_NAME = "enablement_action_intent" + /** + * Byte array. Optional parcelled `Intent` to use to start an activity that can be + * used to configure the affordance. + */ + const val CONFIGURE_INTENT = "configure_intent" } } diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index 030eaa660c9f..5fc919330e92 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -21,12 +21,44 @@ # TODO(b/264686688): Handle these cases with more targeted annotations. -keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { private com.android.keyguard.KeyguardUpdateMonitorCallback *; + private com.android.systemui.privacy.PrivacyConfig$Callback *; private com.android.systemui.privacy.PrivacyItemController$Callback *; private com.android.systemui.settings.UserTracker$Callback *; private com.android.systemui.statusbar.phone.StatusBarWindowCallback *; private com.android.systemui.util.service.Observer$Callback *; private com.android.systemui.util.service.ObservableServiceConnection$Callback *; } +# Note that these rules are temporary companions to the above rules, required +# for cases like Kotlin where fields with anonymous types use the anonymous type +# rather than the supertype. +-if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback +-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { + <1> *; +} +-if class * extends com.android.systemui.privacy.PrivacyConfig$Callback +-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { + <1> *; +} +-if class * extends com.android.systemui.privacy.PrivacyItemController$Callback +-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { + <1> *; +} +-if class * extends com.android.systemui.settings.UserTracker$Callback +-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { + <1> *; +} +-if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback +-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { + <1> *; +} +-if class * extends com.android.systemui.util.service.Observer$Callback +-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { + <1> *; +} +-if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback +-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { + <1> *; +} -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index cea1779e39b8..3a9706da9090 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -222,6 +222,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("mSensorProps=(" + mSensorProps + ")"); + pw.println("Using new touch detection framework: " + mFeatureFlags.isEnabled( + Flags.UDFPS_NEW_TOUCH_DETECTION)); + pw.println("Using ellipse touch detection: " + mFeatureFlags.isEnabled( + Flags.UDFPS_ELLIPSE_DETECTION)); } public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt index 583ee3ac8e60..cee228252da9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt @@ -33,6 +33,7 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionListener @@ -40,7 +41,6 @@ import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.stack.StackStateAnimator -import com.android.systemui.statusbar.phone.KeyguardBouncer import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback @@ -112,10 +112,10 @@ constructor( } /** * Hidden amount of input (pin/pattern/password) bouncer. This is used - * [KeyguardBouncer.EXPANSION_VISIBLE] (0f) to [KeyguardBouncer.EXPANSION_HIDDEN] (1f). Only - * used for the non-modernBouncer. + * [KeyguardBouncerConstants.EXPANSION_VISIBLE] (0f) to + * [KeyguardBouncerConstants.EXPANSION_HIDDEN] (1f). Only used for the non-modernBouncer. */ - private var inputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN + private var inputBouncerHiddenAmount = KeyguardBouncerConstants.EXPANSION_HIDDEN private var inputBouncerExpansion = 0f // only used for modernBouncer private val stateListener: StatusBarStateController.StateListener = diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt index 62bedc627b07..48d48450e13e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt @@ -26,28 +26,28 @@ data class NormalizedTouchData( * Value obtained from [MotionEvent.getPointerId], or [MotionEvent.INVALID_POINTER_ID] if the ID * is not available. */ - val pointerId: Int, + val pointerId: Int = MotionEvent.INVALID_POINTER_ID, /** [MotionEvent.getRawX] mapped to natural orientation and native resolution. */ - val x: Float, + val x: Float = 0f, /** [MotionEvent.getRawY] mapped to natural orientation and native resolution. */ - val y: Float, + val y: Float = 0f, /** [MotionEvent.getTouchMinor] mapped to natural orientation and native resolution. */ - val minor: Float, + val minor: Float = 0f, /** [MotionEvent.getTouchMajor] mapped to natural orientation and native resolution. */ - val major: Float, + val major: Float = 0f, /** [MotionEvent.getOrientation] mapped to natural orientation. */ - val orientation: Float, + val orientation: Float = 0f, /** [MotionEvent.getEventTime]. */ - val time: Long, + val time: Long = 0, /** [MotionEvent.getDownTime]. */ - val gestureStart: Long, + val gestureStart: Long = 0, ) { /** diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt index 693f64a1f93d..3a01cd502929 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt @@ -43,74 +43,72 @@ class SinglePointerTouchProcessor @Inject constructor(val overlapDetector: Overl ): TouchProcessorResult { fun preprocess(): PreprocessedTouch { - // TODO(b/253085297): Add multitouch support. pointerIndex can be > 0 for ACTION_MOVE. - val pointerIndex = 0 - val touchData = event.normalize(pointerIndex, overlayParams) - val isGoodOverlap = - overlapDetector.isGoodOverlap(touchData, overlayParams.nativeSensorBounds) - return PreprocessedTouch(touchData, previousPointerOnSensorId, isGoodOverlap) + val touchData = List(event.pointerCount) { event.normalize(it, overlayParams) } + val pointersOnSensor = + touchData + .filter { overlapDetector.isGoodOverlap(it, overlayParams.nativeSensorBounds) } + .map { it.pointerId } + return PreprocessedTouch(touchData, previousPointerOnSensorId, pointersOnSensor) } return when (event.actionMasked) { - MotionEvent.ACTION_DOWN -> processActionDown(preprocess()) + MotionEvent.ACTION_DOWN, + MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_MOVE -> processActionMove(preprocess()) - MotionEvent.ACTION_UP -> processActionUp(preprocess()) - MotionEvent.ACTION_CANCEL -> - processActionCancel(event.normalize(pointerIndex = 0, overlayParams)) + MotionEvent.ACTION_UP, + MotionEvent.ACTION_POINTER_UP -> + processActionUp(preprocess(), event.getPointerId(event.actionIndex)) + MotionEvent.ACTION_CANCEL -> processActionCancel(NormalizedTouchData()) else -> Failure("Unsupported MotionEvent." + MotionEvent.actionToString(event.actionMasked)) } } } +/** + * [data] contains a list of NormalizedTouchData for pointers in the motionEvent ordered by + * pointerIndex + * + * [previousPointerOnSensorId] the pointerId of the previous pointer on the sensor, + * [MotionEvent.INVALID_POINTER_ID] if none + * + * [pointersOnSensor] contains a list of ids of pointers on the sensor + */ private data class PreprocessedTouch( - val data: NormalizedTouchData, + val data: List<NormalizedTouchData>, val previousPointerOnSensorId: Int, - val isGoodOverlap: Boolean, + val pointersOnSensor: List<Int>, ) -private fun processActionDown(touch: PreprocessedTouch): TouchProcessorResult { - return if (touch.isGoodOverlap) { - ProcessedTouch(InteractionEvent.DOWN, pointerOnSensorId = touch.data.pointerId, touch.data) - } else { - val event = - if (touch.data.pointerId == touch.previousPointerOnSensorId) { - InteractionEvent.UP - } else { - InteractionEvent.UNCHANGED - } - ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data) - } -} - private fun processActionMove(touch: PreprocessedTouch): TouchProcessorResult { val hadPointerOnSensor = touch.previousPointerOnSensorId != INVALID_POINTER_ID - val interactionEvent = - when { - touch.isGoodOverlap && !hadPointerOnSensor -> InteractionEvent.DOWN - !touch.isGoodOverlap && hadPointerOnSensor -> InteractionEvent.UP - else -> InteractionEvent.UNCHANGED - } - val pointerOnSensorId = - when (interactionEvent) { - InteractionEvent.UNCHANGED -> touch.previousPointerOnSensorId - InteractionEvent.DOWN -> touch.data.pointerId - else -> INVALID_POINTER_ID - } - return ProcessedTouch(interactionEvent, pointerOnSensorId, touch.data) + val hasPointerOnSensor = touch.pointersOnSensor.isNotEmpty() + val pointerOnSensorId = touch.pointersOnSensor.firstOrNull() ?: INVALID_POINTER_ID + + return if (!hadPointerOnSensor && hasPointerOnSensor) { + val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData() + ProcessedTouch(InteractionEvent.DOWN, data.pointerId, data) + } else if (hadPointerOnSensor && !hasPointerOnSensor) { + ProcessedTouch(InteractionEvent.UP, INVALID_POINTER_ID, NormalizedTouchData()) + } else { + val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData() + ProcessedTouch(InteractionEvent.UNCHANGED, pointerOnSensorId, data) + } } -private fun processActionUp(touch: PreprocessedTouch): TouchProcessorResult { - return if (touch.isGoodOverlap) { - ProcessedTouch(InteractionEvent.UP, pointerOnSensorId = INVALID_POINTER_ID, touch.data) +private fun processActionUp(touch: PreprocessedTouch, actionId: Int): TouchProcessorResult { + // Finger lifted and it was the only finger on the sensor + return if (touch.pointersOnSensor.size == 1 && touch.pointersOnSensor.contains(actionId)) { + ProcessedTouch( + InteractionEvent.UP, + pointerOnSensorId = INVALID_POINTER_ID, + NormalizedTouchData() + ) } else { - val event = - if (touch.previousPointerOnSensorId != INVALID_POINTER_ID) { - InteractionEvent.UP - } else { - InteractionEvent.UNCHANGED - } - ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data) + // Pick new pointerOnSensor that's not the finger that was lifted + val pointerOnSensorId = touch.pointersOnSensor.find { it != actionId } ?: INVALID_POINTER_ID + val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData() + ProcessedTouch(InteractionEvent.UNCHANGED, data.pointerId, data) } } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt index f95a8ee89a2c..7bbfec7df9d8 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt @@ -28,7 +28,6 @@ class LaunchableImageView : ImageView, LaunchableView { LaunchableViewDelegate( this, superSetVisibility = { super.setVisibility(it) }, - superSetTransitionVisibility = { super.setTransitionVisibility(it) }, ) constructor(context: Context?) : super(context) @@ -53,8 +52,4 @@ class LaunchableImageView : ImageView, LaunchableView { override fun setVisibility(visibility: Int) { delegate.setVisibility(visibility) } - - override fun setTransitionVisibility(visibility: Int) { - delegate.setTransitionVisibility(visibility) - } } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt index c27b82aeeb47..ddde6280f3a2 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt @@ -28,7 +28,6 @@ class LaunchableLinearLayout : LinearLayout, LaunchableView { LaunchableViewDelegate( this, superSetVisibility = { super.setVisibility(it) }, - superSetTransitionVisibility = { super.setTransitionVisibility(it) }, ) constructor(context: Context?) : super(context) @@ -53,8 +52,4 @@ class LaunchableLinearLayout : LinearLayout, LaunchableView { override fun setVisibility(visibility: Int) { delegate.setVisibility(visibility) } - - override fun setTransitionVisibility(visibility: Int) { - delegate.setTransitionVisibility(visibility) - } } diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index e5ec727f0437..c0f854958c41 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -17,8 +17,12 @@ package com.android.systemui.compose +import android.content.Context +import android.view.View import androidx.activity.ComponentActivity +import androidx.lifecycle.LifecycleOwner import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel /** * A facade to interact with Compose, when it is available. @@ -35,10 +39,22 @@ interface BaseComposeFacade { */ fun isComposeAvailable(): Boolean + /** + * Return the [ComposeInitializer] to make Compose usable in windows outside normal activities. + */ + fun composeInitializer(): ComposeInitializer + /** Bind the content of [activity] to [viewModel]. */ fun setPeopleSpaceActivityContent( activity: ComponentActivity, viewModel: PeopleViewModel, onResult: (PeopleViewModel.Result) -> Unit, ) + + /** Create a [View] to represent [viewModel] on screen. */ + fun createFooterActionsView( + context: Context, + viewModel: FooterActionsViewModel, + qsVisibilityLifecycleOwner: LifecycleOwner, + ): View } diff --git a/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt new file mode 100644 index 000000000000..90dc3a00daa2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 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.compose + +import android.view.View + +/** + * An initializer to use Compose outside of an Activity, e.g. inside a window added directly using + * [android.view.WindowManager.addView] (like the shade or status bar) or inside a dialog. + * + * Example: + * ``` + * windowManager.addView(MyWindowRootView(context), /* layoutParams */) + * + * class MyWindowRootView(context: Context) : FrameLayout(context) { + * override fun onAttachedToWindow() { + * super.onAttachedToWindow() + * ComposeInitializer.onAttachedToWindow(this) + * } + * + * override fun onDetachedFromWindow() { + * super.onDetachedFromWindow() + * ComposeInitializer.onDetachedFromWindow(this) + * } + * } + * ``` + */ +interface ComposeInitializer { + /** Function to be called on your window root view's [View.onAttachedToWindow] function. */ + fun onAttachedToWindow(root: View) + + /** Function to be called on your window root view's [View.onDetachedFromWindow] function. */ + fun onDetachedFromWindow(root: View) +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java index 92cdcf99f013..44207f4aecf5 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java @@ -36,10 +36,10 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.wm.shell.animation.FlingAnimationUtils; @@ -274,16 +274,18 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { (float) Math.hypot(horizontalVelocity, verticalVelocity); final float expansion = flingRevealsOverlay(verticalVelocity, velocityVector) - ? KeyguardBouncer.EXPANSION_HIDDEN : KeyguardBouncer.EXPANSION_VISIBLE; + ? KeyguardBouncerConstants.EXPANSION_HIDDEN + : KeyguardBouncerConstants.EXPANSION_VISIBLE; // Log the swiping up to show Bouncer event. - if (!mBouncerInitiallyShowing && expansion == KeyguardBouncer.EXPANSION_VISIBLE) { + if (!mBouncerInitiallyShowing + && expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) { mUiEventLogger.log(DreamEvent.DREAM_SWIPED); } flingToExpansion(verticalVelocity, expansion); - if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) { + if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) { mStatusBarKeyguardViewManager.reset(false); } break; @@ -302,7 +304,8 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { float dragDownAmount = expansionFraction * expansionHeight; setPanelExpansion(expansionFraction, dragDownAmount); }); - if (!mBouncerInitiallyShowing && targetExpansion == KeyguardBouncer.EXPANSION_VISIBLE) { + if (!mBouncerInitiallyShowing + && targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) { animator.addListener( new AnimatorListenerAdapter() { @Override @@ -335,7 +338,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { final float targetHeight = viewHeight * expansion; final float expansionHeight = targetHeight - currentHeight; final ValueAnimator animator = createExpansionAnimator(expansion, expansionHeight); - if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) { + if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) { // Hides the bouncer, i.e., fully expands the space above the bouncer. mFlingAnimationUtilsClosing.apply(animator, currentHeight, targetHeight, velocity, viewHeight); diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 04c8f3df3911..832ded43e93f 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -75,9 +75,7 @@ object Flags { unreleasedFlag(119, "notification_memory_logging_enabled", teamfood = true) // TODO(b/254512731): Tracking Bug - @JvmField - val NOTIFICATION_DISMISSAL_FADE = - unreleasedFlag(113, "notification_dismissal_fade", teamfood = true) + @JvmField val NOTIFICATION_DISMISSAL_FADE = releasedFlag(113, "notification_dismissal_fade") // TODO(b/259558771): Tracking Bug val STABILITY_INDEX_FIX = releasedFlag(114, "stability_index_fix") @@ -505,6 +503,7 @@ object Flags { @JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui") @JvmField val ENABLE_USI_BATTERY_NOTIFICATIONS = unreleasedFlag(2302, "enable_usi_battery_notifications") + @JvmField val ENABLE_STYLUS_EDUCATION = unreleasedFlag(2303, "enable_stylus_education") // 2400 - performance tools and debugging info // TODO(b/238923086): Tracking Bug diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt index eaf1081a374a..482138e6c277 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt @@ -282,6 +282,7 @@ class CustomizationProvider : .ENABLEMENT_ACTION_TEXT, Contract.LockScreenQuickAffordances.AffordanceTable.Columns .ENABLEMENT_COMPONENT_NAME, + Contract.LockScreenQuickAffordances.AffordanceTable.Columns.CONFIGURE_INTENT, ) ) .apply { @@ -298,6 +299,7 @@ class CustomizationProvider : ), representation.actionText, representation.actionComponentName, + representation.configureIntent?.toUri(0), ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt index 8efb36624831..ed1ff329004a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context +import android.content.Intent import android.net.Uri import android.provider.Settings import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS @@ -39,6 +40,7 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -48,10 +50,10 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart -import javax.inject.Inject @SysUISingleton -class DoNotDisturbQuickAffordanceConfig constructor( +class DoNotDisturbQuickAffordanceConfig +constructor( private val context: Context, private val controller: ZenModeController, private val secureSettings: SecureSettings, @@ -59,7 +61,7 @@ class DoNotDisturbQuickAffordanceConfig constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, private val testConditionId: Uri?, testDialog: EnableZenModeDialog?, -): KeyguardQuickAffordanceConfig { +) : KeyguardQuickAffordanceConfig { @Inject constructor( @@ -76,20 +78,23 @@ class DoNotDisturbQuickAffordanceConfig constructor( private val conditionUri: Uri get() = - testConditionId ?: ZenModeConfig.toTimeCondition( - context, - settingsValue, - userTracker.userId, - true, /* shortVersion */ - ).id + testConditionId + ?: ZenModeConfig.toTimeCondition( + context, + settingsValue, + userTracker.userId, + true, /* shortVersion */ + ) + .id private val dialog: EnableZenModeDialog by lazy { - testDialog ?: EnableZenModeDialog( - context, - R.style.Theme_SystemUI_Dialog, - true, /* cancelIsNeutral */ - ZenModeDialogMetricsLogger(context), - ) + testDialog + ?: EnableZenModeDialog( + context, + R.style.Theme_SystemUI_Dialog, + true, /* cancelIsNeutral */ + ZenModeDialogMetricsLogger(context), + ) } override val key: String = BuiltInKeyguardQuickAffordanceKeys.DO_NOT_DISTURB @@ -98,58 +103,62 @@ class DoNotDisturbQuickAffordanceConfig constructor( override val pickerIconResourceId: Int = R.drawable.ic_do_not_disturb - override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = combine( - conflatedCallbackFlow { - val callback = object: ZenModeController.Callback { - override fun onZenChanged(zen: Int) { - dndMode = zen - trySendWithFailureLogging(updateState(), TAG) - } - - override fun onZenAvailableChanged(available: Boolean) { - isAvailable = available - trySendWithFailureLogging(updateState(), TAG) - } - } - - dndMode = controller.zen - isAvailable = controller.isZenAvailable - trySendWithFailureLogging(updateState(), TAG) - - controller.addCallback(callback) - - awaitClose { controller.removeCallback(callback) } - }, - secureSettings - .observerFlow(Settings.Secure.ZEN_DURATION) - .onStart { emit(Unit) } - .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) } - .flowOn(backgroundDispatcher) - .distinctUntilChanged() - .onEach { settingsValue = it } - ) { callbackFlowValue, _ -> callbackFlowValue } + override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = + combine( + conflatedCallbackFlow { + val callback = + object : ZenModeController.Callback { + override fun onZenChanged(zen: Int) { + dndMode = zen + trySendWithFailureLogging(updateState(), TAG) + } + + override fun onZenAvailableChanged(available: Boolean) { + isAvailable = available + trySendWithFailureLogging(updateState(), TAG) + } + } + + dndMode = controller.zen + isAvailable = controller.isZenAvailable + trySendWithFailureLogging(updateState(), TAG) + + controller.addCallback(callback) + + awaitClose { controller.removeCallback(callback) } + }, + secureSettings + .observerFlow(Settings.Secure.ZEN_DURATION) + .onStart { emit(Unit) } + .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) } + .flowOn(backgroundDispatcher) + .distinctUntilChanged() + .onEach { settingsValue = it } + ) { callbackFlowValue, _ -> callbackFlowValue } override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { return if (controller.isZenAvailable) { - KeyguardQuickAffordanceConfig.PickerScreenState.Default + KeyguardQuickAffordanceConfig.PickerScreenState.Default( + configureIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS) + ) } else { KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice } } - override fun onTriggered(expandable: Expandable?): - KeyguardQuickAffordanceConfig.OnTriggeredResult { + override fun onTriggered( + expandable: Expandable? + ): KeyguardQuickAffordanceConfig.OnTriggeredResult { return when { - !isAvailable -> - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + !isAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled dndMode != ZEN_MODE_OFF -> { controller.setZen(ZEN_MODE_OFF, null, TAG) KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled } settingsValue == ZEN_DURATION_PROMPT -> KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog( - dialog.createDialog(), - expandable + dialog.createDialog(), + expandable ) settingsValue == ZEN_DURATION_FOREVER -> { controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG) @@ -187,4 +196,4 @@ class DoNotDisturbQuickAffordanceConfig constructor( companion object { const val TAG = "DoNotDisturbQuickAffordanceConfig" } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt index 62fe80a82908..3412f35669e3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt @@ -135,7 +135,7 @@ constructor( override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState = if (flashlightController.isAvailable) { - KeyguardQuickAffordanceConfig.PickerScreenState.Default + KeyguardQuickAffordanceConfig.PickerScreenState.Default() } else { KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt index 09e5ec0065f8..a1e9137d1764 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt @@ -90,7 +90,7 @@ constructor( ) } - return KeyguardQuickAffordanceConfig.PickerScreenState.Default + return KeyguardQuickAffordanceConfig.PickerScreenState.Default() } override fun onTriggered( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt index 20588e9ccdc1..e32edcb010e8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt @@ -46,7 +46,7 @@ interface KeyguardQuickAffordanceConfig { * Returns the [PickerScreenState] representing the affordance in the settings or selector * experience. */ - suspend fun getPickerScreenState(): PickerScreenState = PickerScreenState.Default + suspend fun getPickerScreenState(): PickerScreenState = PickerScreenState.Default() /** * Notifies that the affordance was clicked by the user. @@ -63,7 +63,10 @@ interface KeyguardQuickAffordanceConfig { sealed class PickerScreenState { /** The picker shows the item for selecting this affordance as it normally would. */ - object Default : PickerScreenState() + data class Default( + /** Optional [Intent] to use to start an activity to configure this affordance. */ + val configureIntent: Intent? = null, + ) : PickerScreenState() /** * The picker does not show an item for selecting this affordance as it is not supported on diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt index 4f7990ff0deb..ea6c107cd161 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt @@ -89,7 +89,7 @@ constructor( ), ), ) - else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default + else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt index 1928f40fa059..680c06bf2c64 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -128,7 +128,7 @@ constructor( actionComponentName = componentName, ) } - else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default + else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt index 2e34e9a93dff..41574d18e2c7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt @@ -20,6 +20,7 @@ import android.os.Build import com.android.keyguard.ViewMediatorCallback import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel import com.android.systemui.log.dagger.BouncerLog @@ -73,7 +74,7 @@ constructor( * 1f = panel fully showing = bouncer fully hidden * ``` */ - private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncer.EXPANSION_HIDDEN) + private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN) val panelExpansionAmount = _panelExpansionAmount.asStateFlow() private val _keyguardPosition = MutableStateFlow(0f) val keyguardPosition = _keyguardPosition.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt index e3f5e90b2300..2b2b9d0703fa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt @@ -187,6 +187,8 @@ constructor( pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice } .map { (config, pickerState) -> + val defaultPickerState = + pickerState as? KeyguardQuickAffordanceConfig.PickerScreenState.Default val disabledPickerState = pickerState as? KeyguardQuickAffordanceConfig.PickerScreenState.Disabled KeyguardQuickAffordancePickerRepresentation( @@ -198,6 +200,7 @@ constructor( instructions = disabledPickerState?.instructions, actionText = disabledPickerState?.actionText, actionComponentName = disabledPickerState?.actionComponentName, + configureIntent = defaultPickerState?.configureIntent, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt index 2cf5fb98d07e..a92540d733b5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt @@ -32,11 +32,11 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.BouncerView import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel import com.android.systemui.plugins.ActivityStarter import com.android.systemui.shared.system.SysUiStatsLog -import com.android.systemui.statusbar.phone.KeyguardBouncer import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject @@ -143,7 +143,7 @@ constructor( Trace.beginSection("KeyguardBouncer#show") repository.setPrimaryScrimmed(isScrimmed) if (isScrimmed) { - setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE) + setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) } if (resumeBouncer) { @@ -204,14 +204,14 @@ constructor( } if ( - expansion == KeyguardBouncer.EXPANSION_VISIBLE && - oldExpansion != KeyguardBouncer.EXPANSION_VISIBLE + expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE && + oldExpansion != KeyguardBouncerConstants.EXPANSION_VISIBLE ) { falsingCollector.onBouncerShown() primaryBouncerCallbackInteractor.dispatchFullyShown() } else if ( - expansion == KeyguardBouncer.EXPANSION_HIDDEN && - oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN + expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN && + oldExpansion != KeyguardBouncerConstants.EXPANSION_HIDDEN ) { /* * There are cases where #hide() was not invoked, such as when @@ -222,8 +222,8 @@ constructor( DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() } primaryBouncerCallbackInteractor.dispatchFullyHidden() } else if ( - expansion != KeyguardBouncer.EXPANSION_VISIBLE && - oldExpansion == KeyguardBouncer.EXPANSION_VISIBLE + expansion != KeyguardBouncerConstants.EXPANSION_VISIBLE && + oldExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE ) { primaryBouncerCallbackInteractor.dispatchStartingToHide() repository.setPrimaryStartingToHide(true) @@ -303,7 +303,7 @@ constructor( fun isFullyShowing(): Boolean { return (repository.primaryBouncerShowingSoon.value || repository.primaryBouncerVisible.value) && - repository.panelExpansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE && + repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE && repository.primaryBouncerStartingDisappearAnimation.value == null } @@ -315,8 +315,8 @@ constructor( /** If bouncer expansion is between 0f and 1f non-inclusive. */ fun isInTransit(): Boolean { return repository.primaryBouncerShowingSoon.value || - repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN && - repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE + repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_HIDDEN && + repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_VISIBLE } /** Return whether bouncer is animating away. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt new file mode 100644 index 000000000000..bb5ac84c6e54 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt @@ -0,0 +1,28 @@ +/* + * 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.keyguard.shared.constants + +object KeyguardBouncerConstants { + /** + * Values for the bouncer expansion represented as the panel expansion. Panel expansion 1f = + * panel fully showing = bouncer fully hidden Panel expansion 0f = panel fully hiding = bouncer + * fully showing + */ + const val EXPANSION_HIDDEN = 1f + const val EXPANSION_VISIBLE = 0f +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt index 7d133598e105..e7e915940290 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.shared.model +import android.content.Intent import androidx.annotation.DrawableRes /** @@ -45,4 +46,7 @@ data class KeyguardQuickAffordancePickerRepresentation( * user to a destination where they can re-enable it. */ val actionComponentName: String? = null, + + /** Optional [Intent] to use to start an activity to configure this affordance. */ + val configureIntent: Intent? = null, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index f772b17a7fb6..49d3748dcf3d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -27,10 +27,10 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.keyguard.data.BouncerViewDelegate +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt index e5d4e4971baa..c6002d6db91a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -20,9 +20,9 @@ import android.view.View import com.android.systemui.keyguard.data.BouncerView import com.android.systemui.keyguard.data.BouncerViewDelegate import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel -import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt index e3649187b0a7..d69ac7fe035d 100644 --- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt +++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt @@ -145,7 +145,7 @@ private fun createLifecycleOwnerAndRun( * └───────────────┴───────────────────┴──────────────┴─────────────────┘ * ``` */ -private class ViewLifecycleOwner( +class ViewLifecycleOwner( private val view: View, ) : LifecycleOwner { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 415ebeedfbde..a13279717d05 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -863,7 +863,7 @@ class MediaDataManager( notificationKey = key, hasCheckedForResume = hasCheckedForResume, isPlaying = isPlaying, - isClearable = sbn.isClearable(), + isClearable = !sbn.isOngoing, lastActive = lastActive, instanceId = instanceId, appUid = appUid, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index f58090b2d433..45d50f0e4976 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -111,6 +111,7 @@ import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimat import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController; import com.android.systemui.util.ColorUtilKt; import com.android.systemui.util.animation.TransitionLayout; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; import dagger.Lazy; @@ -168,10 +169,13 @@ public class MediaControlPanel { R.id.action1 ); + // Time in millis for playing turbulence noise that is played after a touch ripple. + @VisibleForTesting static final long TURBULENCE_NOISE_PLAY_DURATION = 7500L; + private final SeekBarViewModel mSeekBarViewModel; private SeekBarObserver mSeekBarObserver; protected final Executor mBackgroundExecutor; - private final Executor mMainExecutor; + private final DelayableExecutor mMainExecutor; private final ActivityStarter mActivityStarter; private final BroadcastSender mBroadcastSender; @@ -224,10 +228,10 @@ public class MediaControlPanel { private String mSwitchBroadcastApp; private MultiRippleController mMultiRippleController; private TurbulenceNoiseController mTurbulenceNoiseController; - private FeatureFlags mFeatureFlags; - private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig = null; + private final FeatureFlags mFeatureFlags; + private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig; @VisibleForTesting - MultiRippleController.Companion.RipplesFinishedListener mRipplesFinishedListener = null; + MultiRippleController.Companion.RipplesFinishedListener mRipplesFinishedListener; /** * Initialize a new control panel @@ -241,7 +245,7 @@ public class MediaControlPanel { public MediaControlPanel( Context context, @Background Executor backgroundExecutor, - @Main Executor mainExecutor, + @Main DelayableExecutor mainExecutor, ActivityStarter activityStarter, BroadcastSender broadcastSender, MediaViewController mediaViewController, @@ -412,10 +416,12 @@ public class MediaControlPanel { if (mFeatureFlags.isEnabled(Flags.UMO_TURBULENCE_NOISE)) { mRipplesFinishedListener = () -> { if (mTurbulenceNoiseAnimationConfig == null) { - mTurbulenceNoiseAnimationConfig = createLingeringNoiseAnimation(); + mTurbulenceNoiseAnimationConfig = createTurbulenceNoiseAnimation(); } // Color will be correctly updated in ColorSchemeTransition. mTurbulenceNoiseController.play(mTurbulenceNoiseAnimationConfig); + mMainExecutor.executeDelayed( + mTurbulenceNoiseController::finish, TURBULENCE_NOISE_PLAY_DURATION); }; mMultiRippleController.addRipplesFinishedListener(mRipplesFinishedListener); } @@ -1063,7 +1069,7 @@ public class MediaControlPanel { ); } - private TurbulenceNoiseAnimationConfig createLingeringNoiseAnimation() { + private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() { return new TurbulenceNoiseAnimationConfig( TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT, TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER, @@ -1078,7 +1084,9 @@ public class MediaControlPanel { /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(), /* height= */ mMediaViewHolder.getMultiRippleView().getHeight(), TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS, + /* easeInDuration= */ TurbulenceNoiseAnimationConfig.DEFAULT_EASING_DURATION_IN_MILLIS, + /* easeOutDuration= */ TurbulenceNoiseAnimationConfig.DEFAULT_EASING_DURATION_IN_MILLIS, this.getContext().getResources().getDisplayMetrics().density, BlendMode.PLUS, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 2647600e8684..61bb858ee7d7 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -1016,6 +1016,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements pw.println(" mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion); mView.dump(pw); mRegionSamplingHelper.dump(pw); + if (mAutoHideController != null) { + mAutoHideController.dump(pw); + } } // ----- CommandQueue Callbacks ----- diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index fba5f63ea9c7..7f0f89415280 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -68,8 +68,10 @@ public class PeopleSpaceActivity extends ComponentActivity { }; if (ComposeFacade.INSTANCE.isComposeAvailable()) { + Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity"); ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult); } else { + Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity"); ViewGroup view = PeopleViewBinder.create(this); PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, onResult); setContentView(view); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 774cb3442811..8ad102ece9b3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -49,6 +49,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.ShadeInterpolation; +import com.android.systemui.compose.ComposeFacade; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.controls.ui.MediaHost; @@ -228,9 +229,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */ this); - LinearLayout footerActionsView = view.findViewById(R.id.qs_footer_actions); - FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, - mListeningAndVisibilityLifecycleOwner); + bindFooterActionsView(view); mFooterActionsController.init(); mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view); @@ -291,6 +290,33 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca }); } + private void bindFooterActionsView(View root) { + LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions); + + if (!ComposeFacade.INSTANCE.isComposeAvailable()) { + Log.d(TAG, "Binding the View implementation of the QS footer actions"); + FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, + mListeningAndVisibilityLifecycleOwner); + return; + } + + // Compose is available, so let's use the Compose implementation of the footer actions. + Log.d(TAG, "Binding the Compose implementation of the QS footer actions"); + View composeView = ComposeFacade.INSTANCE.createFooterActionsView(root.getContext(), + mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner); + + // The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin + // to all views except for qs_footer_actions, so we set it to the Compose view. + composeView.setId(R.id.qs_footer_actions); + + // Replace the View by the Compose provided one. + ViewGroup parent = (ViewGroup) footerActionsView.getParent(); + ViewGroup.LayoutParams layoutParams = footerActionsView.getLayoutParams(); + int index = parent.indexOfChild(footerActionsView); + parent.removeViewAt(index); + parent.addView(composeView, index, layoutParams); + } + @Override public void setScrollListener(ScrollListener listener) { mScrollListener = listener; diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 3d48fd109e39..84a18d8dd365 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -132,7 +132,7 @@ public class TileServices extends IQSService.Stub { final String slot = tile.getComponent().getClassName(); // TileServices doesn't know how to add more than 1 icon per slot, so remove all mMainHandler.post(() -> mHost.getIconController() - .removeAllIconsForSlot(slot)); + .removeAllIconsForExternalSlot(slot)); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index b355d4bb67fe..29d7fb02e613 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -145,7 +145,6 @@ open class QSTileViewImpl @JvmOverloads constructor( private val launchableViewDelegate = LaunchableViewDelegate( this, superSetVisibility = { super.setVisibility(it) }, - superSetTransitionVisibility = { super.setTransitionVisibility(it) }, ) private var lastDisabledByPolicy = false @@ -362,10 +361,6 @@ open class QSTileViewImpl @JvmOverloads constructor( launchableViewDelegate.setVisibility(visibility) } - override fun setTransitionVisibility(visibility: Int) { - launchableViewDelegate.setTransitionVisibility(visibility) - } - // Accessibility override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index 6acf417f0ea6..1f0cbf9af51c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -58,6 +58,7 @@ import android.widget.FrameLayout; import com.android.internal.view.FloatingActionMode; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; import com.android.systemui.R; +import com.android.systemui.compose.ComposeFacade; /** * Combined keyguard and notification panel view. Also holding backdrop and scrims. @@ -149,6 +150,18 @@ public class NotificationShadeWindowView extends FrameLayout { protected void onAttachedToWindow() { super.onAttachedToWindow(); setWillNotDraw(!DEBUG); + + if (ComposeFacade.INSTANCE.isComposeAvailable()) { + ComposeFacade.INSTANCE.composeInitializer().onAttachedToWindow(this); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (ComposeFacade.INSTANCE.isComposeAvailable()) { + ComposeFacade.INSTANCE.composeInitializer().onDetachedFromWindow(this); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java index 662f70ef269e..438b0f625fc5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java @@ -36,10 +36,6 @@ public class AlphaOptimizedFrameLayout extends FrameLayout implements Launchable visibility -> { super.setVisibility(visibility); return Unit.INSTANCE; - }, - visibility -> { - super.setTransitionVisibility(visibility); - return Unit.INSTANCE; }); public AlphaOptimizedFrameLayout(Context context) { @@ -73,9 +69,4 @@ public class AlphaOptimizedFrameLayout extends FrameLayout implements Launchable public void setVisibility(int visibility) { mLaunchableViewDelegate.setVisibility(visibility); } - - @Override - public void setTransitionVisibility(int visibility) { - mLaunchableViewDelegate.setTransitionVisibility(visibility); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java index 3ccef9d6eb14..eb81c46027e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java @@ -16,25 +16,35 @@ package com.android.systemui.statusbar.phone; +import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS; + import android.content.Context; import android.os.Handler; import android.os.RemoteException; import android.util.Log; import android.view.IWindowManager; import android.view.MotionEvent; +import android.view.accessibility.AccessibilityManager; + +import androidx.annotation.NonNull; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.AutoHideUiElement; +import java.io.PrintWriter; + import javax.inject.Inject; /** A controller to control all auto-hide things. Also see {@link AutoHideUiElement}. */ @SysUISingleton public class AutoHideController { private static final String TAG = "AutoHideController"; - private static final long AUTO_HIDE_TIMEOUT_MS = 2250; + private static final int AUTO_HIDE_TIMEOUT_MS = 2250; + private static final int USER_AUTO_HIDE_TIMEOUT_MS = 350; + private final AccessibilityManager mAccessibilityManager; private final IWindowManager mWindowManagerService; private final Handler mHandler; @@ -52,11 +62,12 @@ public class AutoHideController { }; @Inject - public AutoHideController(Context context, @Main Handler handler, + public AutoHideController(Context context, + @Main Handler handler, IWindowManager iWindowManager) { + mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mHandler = handler; mWindowManagerService = iWindowManager; - mDisplayId = context.getDisplayId(); } @@ -138,7 +149,12 @@ public class AutoHideController { private void scheduleAutoHide() { cancelAutoHide(); - mHandler.postDelayed(mAutoHide, AUTO_HIDE_TIMEOUT_MS); + mHandler.postDelayed(mAutoHide, getAutoHideTimeout()); + } + + private int getAutoHideTimeout() { + return mAccessibilityManager.getRecommendedTimeoutMillis(AUTO_HIDE_TIMEOUT_MS, + FLAG_CONTENT_CONTROLS); } public void checkUserAutoHide(MotionEvent event) { @@ -160,7 +176,13 @@ public class AutoHideController { private void userAutoHide() { cancelAutoHide(); - mHandler.postDelayed(mAutoHide, 350); // longer than app gesture -> flag clear + // longer than app gesture -> flag clear + mHandler.postDelayed(mAutoHide, getUserAutoHideTimeout()); + } + + private int getUserAutoHideTimeout() { + return mAccessibilityManager.getRecommendedTimeoutMillis(USER_AUTO_HIDE_TIMEOUT_MS, + FLAG_CONTENT_CONTROLS); } private boolean isAnyTransientBarShown() { @@ -175,6 +197,15 @@ public class AutoHideController { return false; } + public void dump(@NonNull PrintWriter pw) { + pw.println("AutoHideController:"); + pw.println("\tmAutoHideSuspended=" + mAutoHideSuspended); + pw.println("\tisAnyTransientBarShown=" + isAnyTransientBarShown()); + pw.println("\thasPendingAutoHide=" + mHandler.hasCallbacks(mAutoHide)); + pw.println("\tgetAutoHideTimeout=" + getAutoHideTimeout()); + pw.println("\tgetUserAutoHideTimeout=" + getUserAutoHideTimeout()); + } + /** * Injectable factory for creating a {@link AutoHideController}. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 000fe140882c..f08de85ec02c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.phone; import static com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; +import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import android.content.Context; @@ -64,14 +66,6 @@ public class KeyguardBouncer { private static final String TAG = "PrimaryKeyguardBouncer"; static final long BOUNCER_FACE_DELAY = 1200; public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f; - /** - * Values for the bouncer expansion represented as the panel expansion. - * Panel expansion 1f = panel fully showing = bouncer fully hidden - * Panel expansion 0f = panel fully hiding = bouncer fully showing - */ - public static final float EXPANSION_HIDDEN = 1f; - public static final float EXPANSION_VISIBLE = 0f; - protected final Context mContext; protected final ViewMediatorCallback mCallback; protected final ViewGroup mContainer; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index b965ac97cc1c..ff1b31d8848f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -30,6 +30,9 @@ import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm +import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN +import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.tuner.TunerService import java.io.PrintWriter @@ -40,11 +43,19 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr private val mKeyguardStateController: KeyguardStateController private val statusBarStateController: StatusBarStateController + private val devicePostureController: DevicePostureController @BypassOverride private val bypassOverride: Int private var hasFaceFeature: Boolean + @DevicePostureInt private val configFaceAuthSupportedPosture: Int + @DevicePostureInt private var postureState: Int = DEVICE_POSTURE_UNKNOWN private var pendingUnlock: PendingUnlock? = null private val listeners = mutableListOf<OnBypassStateChangedListener>() - + private val postureCallback = DevicePostureController.Callback { posture -> + if (postureState != posture) { + postureState = posture + notifyListeners() + } + } private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback { override fun onFaceAuthEnabledChanged() = notifyListeners() } @@ -86,7 +97,8 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr FACE_UNLOCK_BYPASS_NEVER -> false else -> field } - return enabled && mKeyguardStateController.isFaceAuthEnabled + return enabled && mKeyguardStateController.isFaceAuthEnabled && + isPostureAllowedForFaceAuth() } private set(value) { field = value @@ -106,18 +118,31 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr lockscreenUserManager: NotificationLockscreenUserManager, keyguardStateController: KeyguardStateController, shadeExpansionStateManager: ShadeExpansionStateManager, + devicePostureController: DevicePostureController, dumpManager: DumpManager ) { this.mKeyguardStateController = keyguardStateController this.statusBarStateController = statusBarStateController + this.devicePostureController = devicePostureController bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override) + configFaceAuthSupportedPosture = + context.resources.getInteger(R.integer.config_face_auth_supported_posture) - hasFaceFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE) + hasFaceFeature = context.packageManager.hasSystemFeature(PackageManager.FEATURE_FACE) if (!hasFaceFeature) { return } + if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) { + devicePostureController.addCallback { posture -> + if (postureState != posture) { + postureState = posture + notifyListeners() + } + } + } + dumpManager.registerDumpable("KeyguardBypassController", this) statusBarStateController.addCallback(object : StatusBarStateController.StateListener { override fun onStateChanged(newState: Int) { @@ -203,6 +228,13 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr pendingUnlock = null } + fun isPostureAllowedForFaceAuth(): Boolean { + return when (configFaceAuthSupportedPosture) { + DEVICE_POSTURE_UNKNOWN -> true + else -> (postureState == configFaceAuthSupportedPosture) + } + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println("KeyguardBypassController:") if (pendingUnlock != null) { @@ -219,6 +251,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr pw.println(" launchingAffordance: $launchingAffordance") pw.println(" qSExpanded: $qsExpanded") pw.println(" hasFaceFeature: $hasFaceFeature") + pw.println(" postureState: $postureState") } /** Registers a listener for bypass state changes. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index ee8b86160f51..f78472386006 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -53,6 +53,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.notification.stack.ViewState; @@ -147,7 +148,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump * 0, the bouncer is visible. */ @FloatRange(from = 0, to = 1) - private float mBouncerHiddenFraction = KeyguardBouncer.EXPANSION_HIDDEN; + private float mBouncerHiddenFraction = KeyguardBouncerConstants.EXPANSION_HIDDEN; /** * Set whether an unocclusion animation is currently running on the notification panel. Used @@ -810,7 +811,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } if (mState == ScrimState.DREAMING - && mBouncerHiddenFraction != KeyguardBouncer.EXPANSION_HIDDEN) { + && mBouncerHiddenFraction != KeyguardBouncerConstants.EXPANSION_HIDDEN) { final float interpolatedFraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress( mBouncerHiddenFraction); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 1a14a0363763..24ad55d67bb0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -79,12 +79,30 @@ public interface StatusBarIconController { /** Refresh the state of an IconManager by recreating the views */ void refreshIconGroup(IconManager iconManager); - /** */ + + /** + * Adds or updates an icon for a given slot for a **tile service icon**. + * + * TODO(b/265307726): Merge with {@link #setIcon(String, StatusBarIcon)} or make this method + * much more clearly distinct from that method. + */ void setExternalIcon(String slot); - /** */ + + /** + * Adds or updates an icon for the given slot for **internal system icons**. + * + * TODO(b/265307726): Rename to `setInternalIcon`, or merge this appropriately with the + * {@link #setIcon(String, StatusBarIcon)} method. + */ void setIcon(String slot, int resourceId, CharSequence contentDescription); - /** */ + + /** + * Adds or updates an icon for the given slot for an **externally-provided icon**. + * + * TODO(b/265307726): Rename to `setExternalIcon` or something similar. + */ void setIcon(String slot, StatusBarIcon icon); + /** */ void setWifiIcon(String slot, WifiIconState state); @@ -133,9 +151,17 @@ public interface StatusBarIconController { * TAG_PRIMARY to refer to the first icon at a given slot. */ void removeIcon(String slot, int tag); + /** */ void removeAllIconsForSlot(String slot); + /** + * Removes all the icons for the given slot. + * + * Only use this for icons that have come from **an external process**. + */ + void removeAllIconsForExternalSlot(String slot); + // TODO: See if we can rename this tunable name. String ICON_HIDE_LIST = "icon_blacklist"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index 9fbe6cbc0e32..416bc7141eeb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -28,6 +28,8 @@ import android.util.ArraySet; import android.util.Log; import android.view.ViewGroup; +import androidx.annotation.VisibleForTesting; + import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.Dumpable; import com.android.systemui.R; @@ -63,6 +65,10 @@ public class StatusBarIconControllerImpl implements Tunable, ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode { private static final String TAG = "StatusBarIconController"; + // Use this suffix to prevent external icon slot names from unintentionally overriding our + // internal, system-level slot names. See b/255428281. + @VisibleForTesting + protected static final String EXTERNAL_SLOT_SUFFIX = "__external"; private final StatusBarIconList mStatusBarIconList; private final ArrayList<IconManager> mIconGroups = new ArrayList<>(); @@ -346,21 +352,26 @@ public class StatusBarIconControllerImpl implements Tunable, @Override public void setExternalIcon(String slot) { - int viewIndex = mStatusBarIconList.getViewIndex(slot, 0); + String slotName = createExternalSlotName(slot); + int viewIndex = mStatusBarIconList.getViewIndex(slotName, 0); int height = mContext.getResources().getDimensionPixelSize( R.dimen.status_bar_icon_drawing_size); mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height)); } - //TODO: remove this (used in command queue and for 3rd party tiles?) + // Override for *both* CommandQueue.Callbacks AND StatusBarIconController. + // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to + // differentiate between those callback methods and StatusBarIconController methods. + @Override public void setIcon(String slot, StatusBarIcon icon) { + String slotName = createExternalSlotName(slot); if (icon == null) { - removeAllIconsForSlot(slot); + removeAllIconsForSlot(slotName); return; } StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon); - setIcon(slot, holder); + setIcon(slotName, holder); } private void setIcon(String slot, @NonNull StatusBarIconHolder holder) { @@ -406,10 +417,12 @@ public class StatusBarIconControllerImpl implements Tunable, } } - /** */ + // CommandQueue.Callbacks override + // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to + // differentiate between those callback methods and StatusBarIconController methods. @Override public void removeIcon(String slot) { - removeAllIconsForSlot(slot); + removeAllIconsForExternalSlot(slot); } /** */ @@ -423,6 +436,11 @@ public class StatusBarIconControllerImpl implements Tunable, mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex)); } + @Override + public void removeAllIconsForExternalSlot(String slotName) { + removeAllIconsForSlot(createExternalSlotName(slotName)); + } + /** */ @Override public void removeAllIconsForSlot(String slotName) { @@ -506,4 +524,12 @@ public class StatusBarIconControllerImpl implements Tunable, public void onDensityOrFontScaleChanged() { refreshIconGroups(); } + + private String createExternalSlotName(String slot) { + if (slot.endsWith(EXTERNAL_SLOT_SUFFIX)) { + return slot; + } else { + return slot + EXTERNAL_SLOT_SUFFIX; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 7d917bd9cd1c..aef25e3f0932 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone; import static android.view.WindowInsets.Type.navigationBars; +import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; @@ -184,8 +185,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb isVisible && mDreamOverlayStateController.isOverlayActive()); if (!isVisible) { - mCentralSurfaces.setPrimaryBouncerHiddenFraction( - KeyguardBouncer.EXPANSION_HIDDEN); + mCentralSurfaces.setPrimaryBouncerHiddenFraction(EXPANSION_HIDDEN); } /* Register predictive back callback when keyguard becomes visible, and unregister @@ -485,7 +485,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb || mNotificationPanelViewController.isExpanding()); final boolean isUserTrackingStarted = - event.getFraction() != KeyguardBouncer.EXPANSION_HIDDEN && event.getTracking(); + event.getFraction() != EXPANSION_HIDDEN && event.getTracking(); return mKeyguardStateController.isShowing() && !primaryBouncerIsOrWillBeShowing() @@ -535,9 +535,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } else { if (mPrimaryBouncer != null) { - mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + mPrimaryBouncer.setExpansion(EXPANSION_HIDDEN); } else { - mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN); } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt index 7c1e384f8c30..cac4a0e5432c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -12,11 +12,13 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.WindowManager +import android.widget.FrameLayout import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.policy.DecorView import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertNotNull @@ -205,25 +207,74 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { verify(interactionJankMonitor).end(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN) } + @Test + fun testAnimationDoesNotChangeLaunchableViewVisibility_viewVisible() { + val touchSurface = createTouchSurface() + + // View is VISIBLE when starting the animation. + runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.VISIBLE } + + // View is invisible while the dialog is shown. + val dialog = showDialogFromView(touchSurface) + assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE) + + // View is visible again when the dialog is dismissed. + runOnMainThreadAndWaitForIdleSync { dialog.dismiss() } + assertThat(touchSurface.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun testAnimationDoesNotChangeLaunchableViewVisibility_viewInvisible() { + val touchSurface = createTouchSurface() + + // View is INVISIBLE when starting the animation. + runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.INVISIBLE } + + // View is INVISIBLE while the dialog is shown. + val dialog = showDialogFromView(touchSurface) + assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE) + + // View is invisible like it was before showing the dialog. + runOnMainThreadAndWaitForIdleSync { dialog.dismiss() } + assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + fun testAnimationDoesNotChangeLaunchableViewVisibility_viewVisibleThenGone() { + val touchSurface = createTouchSurface() + + // View is VISIBLE when starting the animation. + runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.VISIBLE } + + // View is INVISIBLE while the dialog is shown. + val dialog = showDialogFromView(touchSurface) + assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE) + + // Some external call makes the View GONE. It remains INVISIBLE while the dialog is shown, + // as all visibility changes should be blocked. + runOnMainThreadAndWaitForIdleSync { touchSurface.visibility = View.GONE } + assertThat(touchSurface.visibility).isEqualTo(View.INVISIBLE) + + // View is restored to GONE once the dialog is dismissed. + runOnMainThreadAndWaitForIdleSync { dialog.dismiss() } + assertThat(touchSurface.visibility).isEqualTo(View.GONE) + } + private fun createAndShowDialog( animator: DialogLaunchAnimator = dialogLaunchAnimator, ): TestDialog { val touchSurface = createTouchSurface() - return runOnMainThreadAndWaitForIdleSync { - val dialog = TestDialog(context) - animator.showFromView(dialog, touchSurface) - dialog - } + return showDialogFromView(touchSurface, animator) } private fun createTouchSurface(): View { return runOnMainThreadAndWaitForIdleSync { val touchSurfaceRoot = LinearLayout(context) - val touchSurface = View(context) + val touchSurface = TouchSurfaceView(context) touchSurfaceRoot.addView(touchSurface) // We need to attach the root to the window manager otherwise the exit animation will - // be skipped + // be skipped. ViewUtils.attachView(touchSurfaceRoot) attachedViews.add(touchSurfaceRoot) @@ -231,6 +282,17 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { } } + private fun showDialogFromView( + touchSurface: View, + animator: DialogLaunchAnimator = dialogLaunchAnimator, + ): TestDialog { + return runOnMainThreadAndWaitForIdleSync { + val dialog = TestDialog(context) + animator.showFromView(dialog, touchSurface) + dialog + } + } + private fun createDialogAndShowFromDialog(animateFrom: Dialog): TestDialog { return runOnMainThreadAndWaitForIdleSync { val dialog = TestDialog(context) @@ -248,6 +310,22 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { return result } + private class TouchSurfaceView(context: Context) : FrameLayout(context), LaunchableView { + private val delegate = + LaunchableViewDelegate( + this, + superSetVisibility = { super.setVisibility(it) }, + ) + + override fun setShouldBlockVisibilityChanges(block: Boolean) { + delegate.setShouldBlockVisibilityChanges(block) + } + + override fun setVisibility(visibility: Int) { + delegate.setVisibility(visibility) + } + } + private class TestDialog(context: Context) : Dialog(context) { companion object { const val DIALOG_WIDTH = 100 diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt index 3b4f7e10c806..9060922266c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt @@ -31,9 +31,9 @@ import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.phone.KeyguardBouncer import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.SystemClock @@ -133,7 +133,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle // WHEN the bouncer expansion is VISIBLE val job = mController.listenForBouncerExpansion(this) keyguardBouncerRepository.setPrimaryVisible(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE) + keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) yield() // THEN UDFPS shouldPauseAuth == true diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt index 56043e306c16..34ddf795c7e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt @@ -39,7 +39,8 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() @Test fun processTouch() { - overlapDetector.shouldReturn = testCase.isGoodOverlap + overlapDetector.shouldReturn = + testCase.currentPointers.associate { pointer -> pointer.id to pointer.onSensor } val actual = underTest.processTouch( @@ -56,7 +57,7 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() data class TestCase( val event: MotionEvent, - val isGoodOverlap: Boolean, + val currentPointers: List<TestPointer>, val previousPointerOnSensorId: Int, val overlayParams: UdfpsOverlayParams, val expected: TouchProcessorResult, @@ -91,28 +92,21 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() genPositiveTestCases( motionEventAction = MotionEvent.ACTION_DOWN, previousPointerOnSensorId = INVALID_POINTER_ID, - isGoodOverlap = true, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), expectedInteractionEvent = InteractionEvent.DOWN, - expectedPointerOnSensorId = POINTER_ID, - ), - genPositiveTestCases( - motionEventAction = MotionEvent.ACTION_DOWN, - previousPointerOnSensorId = POINTER_ID, - isGoodOverlap = true, - expectedInteractionEvent = InteractionEvent.DOWN, - expectedPointerOnSensorId = POINTER_ID, + expectedPointerOnSensorId = POINTER_ID_1, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_DOWN, previousPointerOnSensorId = INVALID_POINTER_ID, - isGoodOverlap = false, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), expectedInteractionEvent = InteractionEvent.UNCHANGED, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_DOWN, - previousPointerOnSensorId = POINTER_ID, - isGoodOverlap = false, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), expectedInteractionEvent = InteractionEvent.UP, expectedPointerOnSensorId = INVALID_POINTER_ID, ), @@ -120,109 +114,226 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() genPositiveTestCases( motionEventAction = MotionEvent.ACTION_MOVE, previousPointerOnSensorId = INVALID_POINTER_ID, - isGoodOverlap = true, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), expectedInteractionEvent = InteractionEvent.DOWN, - expectedPointerOnSensorId = POINTER_ID, + expectedPointerOnSensorId = POINTER_ID_1, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_MOVE, - previousPointerOnSensorId = POINTER_ID, - isGoodOverlap = true, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), expectedInteractionEvent = InteractionEvent.UNCHANGED, - expectedPointerOnSensorId = POINTER_ID, + expectedPointerOnSensorId = POINTER_ID_1, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_MOVE, previousPointerOnSensorId = INVALID_POINTER_ID, - isGoodOverlap = false, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), expectedInteractionEvent = InteractionEvent.UNCHANGED, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_MOVE, - previousPointerOnSensorId = POINTER_ID, - isGoodOverlap = false, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), expectedInteractionEvent = InteractionEvent.UP, expectedPointerOnSensorId = INVALID_POINTER_ID, ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_MOVE, + previousPointerOnSensorId = INVALID_POINTER_ID, + currentPointers = + listOf( + TestPointer(id = POINTER_ID_1, onSensor = false), + TestPointer(id = POINTER_ID_2, onSensor = true) + ), + expectedInteractionEvent = InteractionEvent.DOWN, + expectedPointerOnSensorId = POINTER_ID_2, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_MOVE, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = + listOf( + TestPointer(id = POINTER_ID_1, onSensor = false), + TestPointer(id = POINTER_ID_2, onSensor = true) + ), + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = POINTER_ID_2, + ), // MotionEvent.ACTION_UP genPositiveTestCases( motionEventAction = MotionEvent.ACTION_UP, previousPointerOnSensorId = INVALID_POINTER_ID, - isGoodOverlap = true, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), expectedInteractionEvent = InteractionEvent.UP, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_UP, - previousPointerOnSensorId = POINTER_ID, - isGoodOverlap = true, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), expectedInteractionEvent = InteractionEvent.UP, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_UP, previousPointerOnSensorId = INVALID_POINTER_ID, - isGoodOverlap = false, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), expectedInteractionEvent = InteractionEvent.UNCHANGED, expectedPointerOnSensorId = INVALID_POINTER_ID, ), - genPositiveTestCases( - motionEventAction = MotionEvent.ACTION_UP, - previousPointerOnSensorId = POINTER_ID, - isGoodOverlap = false, - expectedInteractionEvent = InteractionEvent.UP, - expectedPointerOnSensorId = INVALID_POINTER_ID, - ), // MotionEvent.ACTION_CANCEL genPositiveTestCases( motionEventAction = MotionEvent.ACTION_CANCEL, previousPointerOnSensorId = INVALID_POINTER_ID, - isGoodOverlap = true, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), expectedInteractionEvent = InteractionEvent.CANCEL, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_CANCEL, - previousPointerOnSensorId = POINTER_ID, - isGoodOverlap = true, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)), expectedInteractionEvent = InteractionEvent.CANCEL, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_CANCEL, previousPointerOnSensorId = INVALID_POINTER_ID, - isGoodOverlap = false, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), expectedInteractionEvent = InteractionEvent.CANCEL, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_CANCEL, - previousPointerOnSensorId = POINTER_ID, - isGoodOverlap = false, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)), expectedInteractionEvent = InteractionEvent.CANCEL, expectedPointerOnSensorId = INVALID_POINTER_ID, ), + // MotionEvent.ACTION_POINTER_DOWN + genPositiveTestCases( + motionEventAction = + MotionEvent.ACTION_POINTER_DOWN + + (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT), + previousPointerOnSensorId = INVALID_POINTER_ID, + currentPointers = + listOf( + TestPointer(id = POINTER_ID_1, onSensor = true), + TestPointer(id = POINTER_ID_2, onSensor = false) + ), + expectedInteractionEvent = InteractionEvent.DOWN, + expectedPointerOnSensorId = POINTER_ID_1, + ), + genPositiveTestCases( + motionEventAction = + MotionEvent.ACTION_POINTER_DOWN + + (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT), + previousPointerOnSensorId = INVALID_POINTER_ID, + currentPointers = + listOf( + TestPointer(id = POINTER_ID_1, onSensor = false), + TestPointer(id = POINTER_ID_2, onSensor = true) + ), + expectedInteractionEvent = InteractionEvent.DOWN, + expectedPointerOnSensorId = POINTER_ID_2 + ), + genPositiveTestCases( + motionEventAction = + MotionEvent.ACTION_POINTER_DOWN + + (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT), + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = + listOf( + TestPointer(id = POINTER_ID_1, onSensor = true), + TestPointer(id = POINTER_ID_2, onSensor = false) + ), + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = POINTER_ID_1, + ), + // MotionEvent.ACTION_POINTER_UP + genPositiveTestCases( + motionEventAction = + MotionEvent.ACTION_POINTER_UP + + (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT), + previousPointerOnSensorId = INVALID_POINTER_ID, + currentPointers = + listOf( + TestPointer(id = POINTER_ID_1, onSensor = false), + TestPointer(id = POINTER_ID_2, onSensor = false) + ), + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = INVALID_POINTER_ID + ), + genPositiveTestCases( + motionEventAction = + MotionEvent.ACTION_POINTER_UP + + (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT), + previousPointerOnSensorId = POINTER_ID_2, + currentPointers = + listOf( + TestPointer(id = POINTER_ID_1, onSensor = false), + TestPointer(id = POINTER_ID_2, onSensor = true) + ), + expectedInteractionEvent = InteractionEvent.UP, + expectedPointerOnSensorId = INVALID_POINTER_ID + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_POINTER_UP, + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = + listOf( + TestPointer(id = POINTER_ID_1, onSensor = true), + TestPointer(id = POINTER_ID_2, onSensor = false) + ), + expectedInteractionEvent = InteractionEvent.UP, + expectedPointerOnSensorId = INVALID_POINTER_ID + ), + genPositiveTestCases( + motionEventAction = + MotionEvent.ACTION_POINTER_UP + + (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT), + previousPointerOnSensorId = POINTER_ID_1, + currentPointers = + listOf( + TestPointer(id = POINTER_ID_1, onSensor = true), + TestPointer(id = POINTER_ID_2, onSensor = false) + ), + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = POINTER_ID_1 + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_POINTER_UP, + previousPointerOnSensorId = POINTER_ID_2, + currentPointers = + listOf( + TestPointer(id = POINTER_ID_1, onSensor = false), + TestPointer(id = POINTER_ID_2, onSensor = true) + ), + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = POINTER_ID_2 + ) ) .flatten() + listOf( - // Unsupported MotionEvent actions. - genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_DOWN), - genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_UP), genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_ENTER), genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_MOVE), - genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT), + genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT) ) .flatten() } } +data class TestPointer(val id: Int, val onSensor: Boolean) + /* Display dimensions in native resolution and natural orientation. */ private const val ROTATION_0_NATIVE_DISPLAY_WIDTH = 400 private const val ROTATION_0_NATIVE_DISPLAY_HEIGHT = 600 /* Placeholder touch parameters. */ -private const val POINTER_ID = 42 +private const val POINTER_ID_1 = 42 +private const val POINTER_ID_2 = 43 private const val NATIVE_MINOR = 2.71828f private const val NATIVE_MAJOR = 3.14f private const val ORIENTATION = 1.2345f @@ -325,7 +436,7 @@ private val ROTATION_270_INPUTS = private val MOTION_EVENT = obtainMotionEvent( action = 0, - pointerId = POINTER_ID, + pointerId = POINTER_ID_1, x = 0f, y = 0f, minor = 0f, @@ -338,7 +449,7 @@ private val MOTION_EVENT = /* Template [NormalizedTouchData]. */ private val NORMALIZED_TOUCH_DATA = NormalizedTouchData( - POINTER_ID, + POINTER_ID_1, x = 0f, y = 0f, NATIVE_MINOR, @@ -384,7 +495,7 @@ private data class OrientationBasedInputs( private fun genPositiveTestCases( motionEventAction: Int, previousPointerOnSensorId: Int, - isGoodOverlap: Boolean, + currentPointers: List<TestPointer>, expectedInteractionEvent: InteractionEvent, expectedPointerOnSensorId: Int ): List<SinglePointerTouchProcessorTest.TestCase> { @@ -399,22 +510,47 @@ private fun genPositiveTestCases( return scaleFactors.flatMap { scaleFactor -> orientations.map { orientation -> val overlayParams = orientation.toOverlayParams(scaleFactor) - val nativeX = orientation.getNativeX(isGoodOverlap) - val nativeY = orientation.getNativeY(isGoodOverlap) + + val pointerProperties = + currentPointers + .map { pointer -> + val pp = MotionEvent.PointerProperties() + pp.id = pointer.id + pp + } + .toList() + + val pointerCoords = + currentPointers + .map { pointer -> + val pc = MotionEvent.PointerCoords() + pc.x = orientation.getNativeX(pointer.onSensor) * scaleFactor + pc.y = orientation.getNativeY(pointer.onSensor) * scaleFactor + pc.touchMinor = NATIVE_MINOR * scaleFactor + pc.touchMajor = NATIVE_MAJOR * scaleFactor + pc.orientation = orientation.nativeOrientation + pc + } + .toList() + val event = MOTION_EVENT.copy( action = motionEventAction, - x = nativeX * scaleFactor, - y = nativeY * scaleFactor, - minor = NATIVE_MINOR * scaleFactor, - major = NATIVE_MAJOR * scaleFactor, - orientation = orientation.nativeOrientation + pointerProperties = pointerProperties, + pointerCoords = pointerCoords ) + val expectedTouchData = - NORMALIZED_TOUCH_DATA.copy( - x = ROTATION_0_INPUTS.getNativeX(isGoodOverlap), - y = ROTATION_0_INPUTS.getNativeY(isGoodOverlap), - ) + if (expectedPointerOnSensorId != INVALID_POINTER_ID) { + NORMALIZED_TOUCH_DATA.copy( + pointerId = expectedPointerOnSensorId, + x = ROTATION_0_INPUTS.getNativeX(isWithinSensor = true), + y = ROTATION_0_INPUTS.getNativeY(isWithinSensor = true) + ) + } else { + NormalizedTouchData() + } + val expected = TouchProcessorResult.ProcessedTouch( event = expectedInteractionEvent, @@ -423,7 +559,7 @@ private fun genPositiveTestCases( ) SinglePointerTouchProcessorTest.TestCase( event = event, - isGoodOverlap = isGoodOverlap, + currentPointers = currentPointers, previousPointerOnSensorId = previousPointerOnSensorId, overlayParams = overlayParams, expected = expected, @@ -436,7 +572,7 @@ private fun genTestCasesForUnsupportedAction( motionEventAction: Int ): List<SinglePointerTouchProcessorTest.TestCase> { val isGoodOverlap = true - val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID) + val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID_1) return previousPointerOnSensorIds.map { previousPointerOnSensorId -> val overlayParams = ROTATION_0_INPUTS.toOverlayParams(scaleFactor = 1f) val nativeX = ROTATION_0_INPUTS.getNativeX(isGoodOverlap) @@ -451,7 +587,7 @@ private fun genTestCasesForUnsupportedAction( ) SinglePointerTouchProcessorTest.TestCase( event = event, - isGoodOverlap = isGoodOverlap, + currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = isGoodOverlap)), previousPointerOnSensorId = previousPointerOnSensorId, overlayParams = overlayParams, expected = TouchProcessorResult.Failure(), @@ -478,13 +614,23 @@ private fun obtainMotionEvent( pc.touchMinor = minor pc.touchMajor = major pc.orientation = orientation + return obtainMotionEvent(action, arrayOf(pp), arrayOf(pc), time, gestureStart) +} + +private fun obtainMotionEvent( + action: Int, + pointerProperties: Array<MotionEvent.PointerProperties>, + pointerCoords: Array<MotionEvent.PointerCoords>, + time: Long, + gestureStart: Long, +): MotionEvent { return MotionEvent.obtain( gestureStart /* downTime */, time /* eventTime */, action /* action */, - 1 /* pointerCount */, - arrayOf(pp) /* pointerProperties */, - arrayOf(pc) /* pointerCoords */, + pointerCoords.size /* pointerCount */, + pointerProperties /* pointerProperties */, + pointerCoords /* pointerCoords */, 0 /* metaState */, 0 /* buttonState */, 1f /* xPrecision */, @@ -508,4 +654,19 @@ private fun MotionEvent.copy( gestureStart: Long = this.downTime, ) = obtainMotionEvent(action, pointerId, x, y, minor, major, orientation, time, gestureStart) +private fun MotionEvent.copy( + action: Int = this.action, + pointerProperties: List<MotionEvent.PointerProperties>, + pointerCoords: List<MotionEvent.PointerCoords>, + time: Long = this.eventTime, + gestureStart: Long = this.downTime +) = + obtainMotionEvent( + action, + pointerProperties.toTypedArray(), + pointerCoords.toTypedArray(), + time, + gestureStart + ) + private fun Rect.scaled(scaleFactor: Float) = Rect(this).apply { scale(scaleFactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt new file mode 100644 index 000000000000..3e6cc3bb4f6b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 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.compose + +import android.content.Context +import android.testing.AndroidTestingRunner +import android.testing.ViewUtils +import android.widget.FrameLayout +import androidx.compose.ui.platform.ComposeView +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ComposeInitializerTest : SysuiTestCase() { + @Test + fun testCanAddComposeViewInInitializedWindow() { + if (!ComposeFacade.isComposeAvailable()) { + return + } + + val root = TestWindowRoot(context) + try { + runOnMainThreadAndWaitForIdleSync { ViewUtils.attachView(root) } + assertThat(root.isAttachedToWindow).isTrue() + + runOnMainThreadAndWaitForIdleSync { root.addView(ComposeView(context)) } + } finally { + runOnMainThreadAndWaitForIdleSync { ViewUtils.detachView(root) } + } + } + + private fun runOnMainThreadAndWaitForIdleSync(f: () -> Unit) { + mContext.mainExecutor.execute(f) + waitForIdleSync() + } + + class TestWindowRoot(context: Context) : FrameLayout(context) { + override fun onAttachedToWindow() { + super.onAttachedToWindow() + ComposeFacade.composeInitializer().onAttachedToWindow(this) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + ComposeFacade.composeInitializer().onDetachedFromWindow(this) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java index 4bd53c00327f..f64179deec35 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java @@ -41,11 +41,11 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.wm.shell.animation.FlingAnimationUtils; @@ -302,12 +302,13 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { final float velocityY = -1; swipeToPosition(swipeUpPercentage, Direction.UP, velocityY); - verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncer.EXPANSION_HIDDEN)); + verify(mValueAnimatorCreator).create(eq(expansion), + eq(KeyguardBouncerConstants.EXPANSION_HIDDEN)); verify(mValueAnimator, never()).addListener(any()); verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * expansion), - eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_HIDDEN), + eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_HIDDEN), eq(velocityY), eq((float) SCREEN_HEIGHT_PX)); verify(mValueAnimator).start(); verify(mUiEventLogger, never()).log(any()); @@ -324,7 +325,8 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { final float velocityY = 1; swipeToPosition(swipeUpPercentage, Direction.UP, velocityY); - verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncer.EXPANSION_VISIBLE)); + verify(mValueAnimatorCreator).create(eq(expansion), + eq(KeyguardBouncerConstants.EXPANSION_VISIBLE)); ArgumentCaptor<AnimatorListenerAdapter> endAnimationListenerCaptor = ArgumentCaptor.forClass(AnimatorListenerAdapter.class); @@ -332,7 +334,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { AnimatorListenerAdapter endAnimationListener = endAnimationListenerCaptor.getValue(); verify(mFlingAnimationUtils).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * expansion), - eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_VISIBLE), + eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_VISIBLE), eq(velocityY), eq((float) SCREEN_HEIGHT_PX)); verify(mValueAnimator).start(); verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_SWIPED); @@ -355,12 +357,12 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { swipeToPosition(swipeDownPercentage, Direction.DOWN, velocityY); verify(mValueAnimatorCreator).create(eq(swipeDownPercentage), - eq(KeyguardBouncer.EXPANSION_VISIBLE)); + eq(KeyguardBouncerConstants.EXPANSION_VISIBLE)); verify(mValueAnimator, never()).addListener(any()); verify(mFlingAnimationUtils).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * swipeDownPercentage), - eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_VISIBLE), + eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_VISIBLE), eq(velocityY), eq((float) SCREEN_HEIGHT_PX)); verify(mValueAnimator).start(); verify(mUiEventLogger, never()).log(any()); @@ -381,12 +383,12 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { swipeToPosition(swipeDownPercentage, Direction.DOWN, velocityY); verify(mValueAnimatorCreator).create(eq(swipeDownPercentage), - eq(KeyguardBouncer.EXPANSION_HIDDEN)); + eq(KeyguardBouncerConstants.EXPANSION_HIDDEN)); verify(mValueAnimator, never()).addListener(any()); verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * swipeDownPercentage), - eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_HIDDEN), + eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_HIDDEN), eq(velocityY), eq((float) SCREEN_HEIGHT_PX)); verify(mValueAnimator).start(); verify(mUiEventLogger, never()).log(any()); @@ -405,7 +407,8 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { final float velocityY = -1; swipeToPosition(swipeUpPercentage, Direction.UP, velocityY); - verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncer.EXPANSION_VISIBLE)); + verify(mValueAnimatorCreator).create(eq(expansion), + eq(KeyguardBouncerConstants.EXPANSION_VISIBLE)); ArgumentCaptor<AnimatorListenerAdapter> endAnimationListenerCaptor = ArgumentCaptor.forClass(AnimatorListenerAdapter.class); @@ -413,7 +416,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { AnimatorListenerAdapter endAnimationListener = endAnimationListenerCaptor.getValue(); verify(mFlingAnimationUtils).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * expansion), - eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_VISIBLE), + eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_VISIBLE), eq(velocityY), eq((float) SCREEN_HEIGHT_PX)); verify(mValueAnimator).start(); verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_SWIPED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt index 7c10108d5b45..15b85ded5fd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestDispatcher @@ -83,169 +84,205 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { settings = FakeSettings() - underTest = DoNotDisturbQuickAffordanceConfig( - context, - zenModeController, - settings, - userTracker, - testDispatcher, - conditionUri, - enableZenModeDialog, - ) + underTest = + DoNotDisturbQuickAffordanceConfig( + context, + zenModeController, + settings, + userTracker, + testDispatcher, + conditionUri, + enableZenModeDialog, + ) } @Test - fun `dnd not available - picker state hidden`() = testScope.runTest { - //given - whenever(zenModeController.isZenAvailable).thenReturn(false) + fun `dnd not available - picker state hidden`() = + testScope.runTest { + // given + whenever(zenModeController.isZenAvailable).thenReturn(false) - //when - val result = underTest.getPickerScreenState() + // when + val result = underTest.getPickerScreenState() - //then - assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, result) - } + // then + assertEquals( + KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, + result + ) + } @Test - fun `dnd available - picker state visible`() = testScope.runTest { - //given - whenever(zenModeController.isZenAvailable).thenReturn(true) - - //when - val result = underTest.getPickerScreenState() - - //then - assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.Default, result) - } + fun `dnd available - picker state visible`() = + testScope.runTest { + // given + whenever(zenModeController.isZenAvailable).thenReturn(true) + + // when + val result = underTest.getPickerScreenState() + + // then + assertThat(result) + .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java) + val defaultPickerState = + result as KeyguardQuickAffordanceConfig.PickerScreenState.Default + assertThat(defaultPickerState.configureIntent).isNotNull() + assertThat(defaultPickerState.configureIntent?.action) + .isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS) + } @Test - fun `onTriggered - dnd mode is not ZEN_MODE_OFF - set to ZEN_MODE_OFF`() = testScope.runTest { - //given - whenever(zenModeController.isZenAvailable).thenReturn(true) - whenever(zenModeController.zen).thenReturn(-1) - settings.putInt(Settings.Secure.ZEN_DURATION, -2) - collectLastValue(underTest.lockScreenState) - runCurrent() - - //when - val result = underTest.onTriggered(null) - verify(zenModeController).setZen(spyZenMode.capture(), spyConditionId.capture(), eq(DoNotDisturbQuickAffordanceConfig.TAG)) - - //then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) - assertEquals(ZEN_MODE_OFF, spyZenMode.value) - assertNull(spyConditionId.value) - } + fun `onTriggered - dnd mode is not ZEN_MODE_OFF - set to ZEN_MODE_OFF`() = + testScope.runTest { + // given + whenever(zenModeController.isZenAvailable).thenReturn(true) + whenever(zenModeController.zen).thenReturn(-1) + settings.putInt(Settings.Secure.ZEN_DURATION, -2) + collectLastValue(underTest.lockScreenState) + runCurrent() + + // when + val result = underTest.onTriggered(null) + verify(zenModeController) + .setZen( + spyZenMode.capture(), + spyConditionId.capture(), + eq(DoNotDisturbQuickAffordanceConfig.TAG) + ) + + // then + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(ZEN_MODE_OFF, spyZenMode.value) + assertNull(spyConditionId.value) + } @Test - fun `onTriggered - dnd mode is ZEN_MODE_OFF - setting is FOREVER - set zen with no condition`() = testScope.runTest { - //given - whenever(zenModeController.isZenAvailable).thenReturn(true) - whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) - settings.putInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_FOREVER) - collectLastValue(underTest.lockScreenState) - runCurrent() - - //when - val result = underTest.onTriggered(null) - verify(zenModeController).setZen(spyZenMode.capture(), spyConditionId.capture(), eq(DoNotDisturbQuickAffordanceConfig.TAG)) - - //then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) - assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, spyZenMode.value) - assertNull(spyConditionId.value) - } + fun `onTriggered - dnd mode is ZEN_MODE_OFF - setting FOREVER - set zen without condition`() = + testScope.runTest { + // given + whenever(zenModeController.isZenAvailable).thenReturn(true) + whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) + settings.putInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_FOREVER) + collectLastValue(underTest.lockScreenState) + runCurrent() + + // when + val result = underTest.onTriggered(null) + verify(zenModeController) + .setZen( + spyZenMode.capture(), + spyConditionId.capture(), + eq(DoNotDisturbQuickAffordanceConfig.TAG) + ) + + // then + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, spyZenMode.value) + assertNull(spyConditionId.value) + } @Test - fun `onTriggered - dnd mode is ZEN_MODE_OFF - setting is not FOREVER or PROMPT - set zen with condition`() = testScope.runTest { - //given - whenever(zenModeController.isZenAvailable).thenReturn(true) - whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) - settings.putInt(Settings.Secure.ZEN_DURATION, -900) - collectLastValue(underTest.lockScreenState) - runCurrent() - - //when - val result = underTest.onTriggered(null) - verify(zenModeController).setZen(spyZenMode.capture(), spyConditionId.capture(), eq(DoNotDisturbQuickAffordanceConfig.TAG)) - - //then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) - assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, spyZenMode.value) - assertEquals(conditionUri, spyConditionId.value) - } + fun `onTriggered - dnd ZEN_MODE_OFF - setting not FOREVER or PROMPT - zen with condition`() = + testScope.runTest { + // given + whenever(zenModeController.isZenAvailable).thenReturn(true) + whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) + settings.putInt(Settings.Secure.ZEN_DURATION, -900) + collectLastValue(underTest.lockScreenState) + runCurrent() + + // when + val result = underTest.onTriggered(null) + verify(zenModeController) + .setZen( + spyZenMode.capture(), + spyConditionId.capture(), + eq(DoNotDisturbQuickAffordanceConfig.TAG) + ) + + // then + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, spyZenMode.value) + assertEquals(conditionUri, spyConditionId.value) + } @Test - fun `onTriggered - dnd mode is ZEN_MODE_OFF - setting is PROMPT - show dialog`() = testScope.runTest { - //given - val expandable: Expandable = mock() - whenever(zenModeController.isZenAvailable).thenReturn(true) - whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) - settings.putInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT) - whenever(enableZenModeDialog.createDialog()).thenReturn(mock()) - collectLastValue(underTest.lockScreenState) - runCurrent() - - //when - val result = underTest.onTriggered(expandable) - - //then - assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog) - assertEquals(expandable, (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable) - } + fun `onTriggered - dnd mode is ZEN_MODE_OFF - setting is PROMPT - show dialog`() = + testScope.runTest { + // given + val expandable: Expandable = mock() + whenever(zenModeController.isZenAvailable).thenReturn(true) + whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) + settings.putInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT) + whenever(enableZenModeDialog.createDialog()).thenReturn(mock()) + collectLastValue(underTest.lockScreenState) + runCurrent() + + // when + val result = underTest.onTriggered(expandable) + + // then + assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog) + assertEquals( + expandable, + (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable + ) + } @Test - fun `lockScreenState - dndAvailable starts as true - changes to false - State moves to Hidden`() = testScope.runTest { - //given - whenever(zenModeController.isZenAvailable).thenReturn(true) - val callbackCaptor: ArgumentCaptor<ZenModeController.Callback> = argumentCaptor() - val valueSnapshot = collectLastValue(underTest.lockScreenState) - val secondLastValue = valueSnapshot() - verify(zenModeController).addCallback(callbackCaptor.capture()) - - //when - callbackCaptor.value.onZenAvailableChanged(false) - val lastValue = valueSnapshot() - - //then - assertTrue(secondLastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) - assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden) - } + fun `lockScreenState - dndAvailable starts as true - change to false - State is Hidden`() = + testScope.runTest { + // given + whenever(zenModeController.isZenAvailable).thenReturn(true) + val callbackCaptor: ArgumentCaptor<ZenModeController.Callback> = argumentCaptor() + val valueSnapshot = collectLastValue(underTest.lockScreenState) + val secondLastValue = valueSnapshot() + verify(zenModeController).addCallback(callbackCaptor.capture()) + + // when + callbackCaptor.value.onZenAvailableChanged(false) + val lastValue = valueSnapshot() + + // then + assertTrue(secondLastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } @Test - fun `lockScreenState - dndMode starts as ZEN_MODE_OFF - changes to not OFF - State moves to Visible`() = testScope.runTest { - //given - whenever(zenModeController.isZenAvailable).thenReturn(true) - whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) - val valueSnapshot = collectLastValue(underTest.lockScreenState) - val secondLastValue = valueSnapshot() - val callbackCaptor: ArgumentCaptor<ZenModeController.Callback> = argumentCaptor() - verify(zenModeController).addCallback(callbackCaptor.capture()) - - //when - callbackCaptor.value.onZenChanged(ZEN_MODE_IMPORTANT_INTERRUPTIONS) - val lastValue = valueSnapshot() - - //then - assertEquals( - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - Icon.Resource( - R.drawable.qs_dnd_icon_off, - ContentDescription.Resource(R.string.dnd_is_off) + fun `lockScreenState - dndMode starts as ZEN_MODE_OFF - change to not OFF - State Visible`() = + testScope.runTest { + // given + whenever(zenModeController.isZenAvailable).thenReturn(true) + whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) + val valueSnapshot = collectLastValue(underTest.lockScreenState) + val secondLastValue = valueSnapshot() + val callbackCaptor: ArgumentCaptor<ZenModeController.Callback> = argumentCaptor() + verify(zenModeController).addCallback(callbackCaptor.capture()) + + // when + callbackCaptor.value.onZenChanged(ZEN_MODE_IMPORTANT_INTERRUPTIONS) + val lastValue = valueSnapshot() + + // then + assertEquals( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.qs_dnd_icon_off, + ContentDescription.Resource(R.string.dnd_is_off) + ), + ActivationState.Inactive ), - ActivationState.Inactive - ), - secondLastValue, - ) - assertEquals( - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - Icon.Resource( - R.drawable.qs_dnd_icon_on, - ContentDescription.Resource(R.string.dnd_is_on) + secondLastValue, + ) + assertEquals( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.qs_dnd_icon_on, + ContentDescription.Resource(R.string.dnd_is_on) + ), + ActivationState.Active ), - ActivationState.Active - ), - lastValue, - ) - } -}
\ No newline at end of file + lastValue, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt index 6255980601ac..9d2ddffddb5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt @@ -141,7 +141,7 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() { whenever(controller.isAbleToOpenCameraApp).thenReturn(true) assertThat(underTest.getPickerScreenState()) - .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default) + .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt index d875dd94da3e..ca44fa18f6c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt @@ -159,7 +159,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { setUpState() assertThat(underTest.getPickerScreenState()) - .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default) + .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt index a6fc13bcb011..7f48ea19c91a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -30,11 +30,11 @@ import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.BouncerView import com.android.systemui.keyguard.data.BouncerViewDelegate import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN -import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index c24c8c7f7cf6..1687fdc9f76c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.media.controls.pipeline import android.app.Notification +import android.app.Notification.FLAG_NO_CLEAR import android.app.Notification.MediaStyle import android.app.PendingIntent import android.app.smartspace.SmartspaceAction @@ -1451,6 +1452,39 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(mediaDataCaptor.value.semanticActions).isNull() } + @Test + fun testNoClearNotOngoing_canDismiss() { + mediaNotification = + SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + it.setOngoing(false) + it.setFlag(FLAG_NO_CLEAR, true) + } + build() + } + addNotificationAndLoad() + assertThat(mediaDataCaptor.value.isClearable).isTrue() + } + + @Test + fun testOngoing_cannotDismiss() { + mediaNotification = + SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + it.setOngoing(true) + } + build() + } + addNotificationAndLoad() + assertThat(mediaDataCaptor.value.isClearable).isFalse() + } + /** Helper function to add a media notification and capture the resulting MediaData */ private fun addNotificationAndLoad() { mediaDataManager.onNotificationAdded(KEY, mediaNotification) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt index cfb19fc32bec..b35dd266e422 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt @@ -82,6 +82,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.surfaceeffects.ripple.MultiRippleView +import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.concurrency.FakeExecutor @@ -225,8 +226,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Before fun setUp() { - bgExecutor = FakeExecutor(FakeSystemClock()) - mainExecutor = FakeExecutor(FakeSystemClock()) + bgExecutor = FakeExecutor(clock) + mainExecutor = FakeExecutor(clock) whenever(mediaViewController.expandedLayout).thenReturn(expandedSet) whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet) @@ -2121,6 +2122,27 @@ public class MediaControlPanelTest : SysuiTestCase() { assertThat(player.mRipplesFinishedListener).isNull() } + @Test + fun playTurbulenceNoise_finishesAfterDuration() { + fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true) + fakeFeatureFlag.set(Flags.UMO_TURBULENCE_NOISE, true) + + player.attachPlayer(viewHolder) + + mainExecutor.execute { + player.mRipplesFinishedListener.onRipplesFinish() + + assertThat(turbulenceNoiseView.visibility).isEqualTo(View.VISIBLE) + + clock.advanceTime( + MediaControlPanel.TURBULENCE_NOISE_PLAY_DURATION + + TurbulenceNoiseAnimationConfig.DEFAULT_EASING_DURATION_IN_MILLIS.toLong() + ) + + assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE) + } + } + private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener = withArgCaptor { verify(seekBarViewModel).setScrubbingChangeListener(capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java index df7ee432e79e..7ad9cc27982a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN; -import static com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE; +import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; +import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; import static com.google.common.truth.Truth.assertThat; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt new file mode 100644 index 000000000000..3e90ed9811d0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt @@ -0,0 +1,255 @@ +/* + * 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.statusbar.phone + +import android.content.pm.PackageManager +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import com.android.systemui.R +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.NotificationLockscreenUserManager +import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_FLIPPED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.tuner.TunerService +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class KeyguardBypassControllerTest : SysuiTestCase() { + + private lateinit var keyguardBypassController: KeyguardBypassController + private lateinit var postureControllerCallback: DevicePostureController.Callback + @Mock private lateinit var tunerService: TunerService + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager + @Mock private lateinit var devicePostureController: DevicePostureController + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var packageManager: PackageManager + @Captor + private val postureCallbackCaptor = + ArgumentCaptor.forClass(DevicePostureController.Callback::class.java) + @JvmField @Rule val mockito = MockitoJUnit.rule() + + @Before + fun setUp() { + context.setMockPackageManager(packageManager) + whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true) + whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true) + } + + @After + fun tearDown() { + reset(devicePostureController) + reset(keyguardStateController) + } + + private fun defaultConfigPostureClosed() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_auth_supported_posture, + DEVICE_POSTURE_CLOSED + ) + initKeyguardBypassController() + verify(devicePostureController).addCallback(postureCallbackCaptor.capture()) + postureControllerCallback = postureCallbackCaptor.value + } + + private fun defaultConfigPostureOpened() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_auth_supported_posture, + DEVICE_POSTURE_OPENED + ) + initKeyguardBypassController() + verify(devicePostureController).addCallback(postureCallbackCaptor.capture()) + postureControllerCallback = postureCallbackCaptor.value + } + + private fun defaultConfigPostureFlipped() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_auth_supported_posture, + DEVICE_POSTURE_FLIPPED + ) + initKeyguardBypassController() + verify(devicePostureController).addCallback(postureCallbackCaptor.capture()) + postureControllerCallback = postureCallbackCaptor.value + } + + private fun defaultConfigPostureUnknown() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_auth_supported_posture, + DEVICE_POSTURE_UNKNOWN + ) + initKeyguardBypassController() + verify(devicePostureController, never()).addCallback(postureCallbackCaptor.capture()) + } + + private fun initKeyguardBypassController() { + keyguardBypassController = + KeyguardBypassController( + context, + tunerService, + statusBarStateController, + lockscreenUserManager, + keyguardStateController, + shadeExpansionStateManager, + devicePostureController, + dumpManager + ) + } + + @Test + fun configDevicePostureClosed_matchState_isPostureAllowedForFaceAuth_returnTrue() { + defaultConfigPostureClosed() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue() + } + + @Test + fun configDevicePostureOpen_matchState_isPostureAllowedForFaceAuth_returnTrue() { + defaultConfigPostureOpened() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue() + } + + @Test + fun configDevicePostureFlipped_matchState_isPostureAllowedForFaceAuth_returnTrue() { + defaultConfigPostureFlipped() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue() + } + + @Test + fun configDevicePostureClosed_changeOpened_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureClosed() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun configDevicePostureClosed_changeFlipped_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureClosed() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun configDevicePostureOpened_changeClosed_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureOpened() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun configDevicePostureOpened_changeFlipped_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureOpened() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun configDevicePostureFlipped_changeClosed_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureFlipped() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun configDevicePostureFlipped_changeOpened_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureFlipped() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun defaultConfigPostureClosed_canOverrideByPassAlways_shouldReturnFalse() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_unlock_bypass_override, + 1 /* FACE_UNLOCK_BYPASS_ALWAYS */ + ) + + defaultConfigPostureClosed() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + assertThat(keyguardBypassController.bypassEnabled).isFalse() + } + + @Test + fun defaultConfigPostureUnknown_canNotOverrideByPassAlways_shouldReturnTrue() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_unlock_bypass_override, + 1 /* FACE_UNLOCK_BYPASS_ALWAYS */ + ) + + defaultConfigPostureUnknown() + + assertThat(keyguardBypassController.bypassEnabled).isTrue() + } + + @Test + fun defaultConfigPostureUnknown_canNotOverrideByPassNever_shouldReturnFalse() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_unlock_bypass_override, + 2 /* FACE_UNLOCK_BYPASS_NEVER */ + ) + + defaultConfigPostureUnknown() + + assertThat(keyguardBypassController.bypassEnabled).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index e4759057a59c..c7a0582f0007 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -58,6 +58,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; import com.android.systemui.scrim.ScrimView; import com.android.systemui.statusbar.policy.FakeConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -1562,7 +1563,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToDreaming() { mScrimController.setRawPanelExpansionFraction(0f); - mScrimController.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN); + mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN); mScrimController.transitionTo(ScrimState.DREAMING); finishAnimationsImmediately(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt new file mode 100644 index 000000000000..3bc288a2f823 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt @@ -0,0 +1,309 @@ +/* + * 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.statusbar.phone + +import android.os.UserHandle +import androidx.test.filters.SmallTest +import com.android.internal.statusbar.StatusBarIcon +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY +import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl.EXTERNAL_SLOT_SUFFIX +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.verify + +@SmallTest +class StatusBarIconControllerImplTest : SysuiTestCase() { + + private lateinit var underTest: StatusBarIconControllerImpl + + private lateinit var iconList: StatusBarIconList + private val iconGroup: StatusBarIconController.IconManager = mock() + + @Before + fun setUp() { + iconList = StatusBarIconList(arrayOf()) + underTest = + StatusBarIconControllerImpl( + context, + mock(), + mock(), + mock(), + mock(), + mock(), + iconList, + mock(), + ) + underTest.addIconGroup(iconGroup) + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_bothDisplayed() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + val externalIcon = + StatusBarIcon( + "external.package", + UserHandle.ALL, + /* iconId= */ 2, + /* iconLevel= */ 0, + /* number= */ 0, + "contentDescription", + ) + underTest.setIcon(slotName, externalIcon) + + assertThat(iconList.slots).hasSize(2) + // Whichever was added last comes first + assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(iconList.slots[1].name).isEqualTo(slotName) + assertThat(iconList.slots[0].hasIconsInSlot()).isTrue() + assertThat(iconList.slots[1].hasIconsInSlot()).isTrue() + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveIcon_internalStays() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + underTest.setIcon(slotName, createExternalIcon()) + + // WHEN the external icon is removed via #removeIcon + underTest.removeIcon(slotName) + + // THEN the external icon is removed but the internal icon remains + // Note: [StatusBarIconList] never removes slots from its list, it just sets the holder for + // the slot to null when an icon is removed. + assertThat(iconList.slots).hasSize(2) + assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(iconList.slots[1].name).isEqualTo(slotName) + assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal + assertThat(iconList.slots[1].hasIconsInSlot()).isTrue() + + verify(iconGroup).onRemoveIcon(0) + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveAll_internalStays() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + underTest.setIcon(slotName, createExternalIcon()) + + // WHEN the external icon is removed via #removeAllIconsForExternalSlot + underTest.removeAllIconsForExternalSlot(slotName) + + // THEN the external icon is removed but the internal icon remains + assertThat(iconList.slots).hasSize(2) + assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(iconList.slots[1].name).isEqualTo(slotName) + assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal + assertThat(iconList.slots[1].hasIconsInSlot()).isTrue() + + verify(iconGroup).onRemoveIcon(0) + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_externalRemoved_viaSetNull_internalStays() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + underTest.setIcon(slotName, createExternalIcon()) + + // WHEN the external icon is removed via a #setIcon(null) + underTest.setIcon(slotName, /* icon= */ null) + + // THEN the external icon is removed but the internal icon remains + assertThat(iconList.slots).hasSize(2) + assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(iconList.slots[1].name).isEqualTo(slotName) + assertThat(iconList.slots[0].hasIconsInSlot()).isFalse() // Indicates removal + assertThat(iconList.slots[1].hasIconsInSlot()).isTrue() + + verify(iconGroup).onRemoveIcon(0) + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_internalRemoved_viaRemove_externalStays() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + underTest.setIcon(slotName, createExternalIcon()) + + // WHEN the internal icon is removed via #removeIcon + underTest.removeIcon(slotName, /* tag= */ 0) + + // THEN the external icon is removed but the internal icon remains + assertThat(iconList.slots).hasSize(2) + assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(iconList.slots[1].name).isEqualTo(slotName) + assertThat(iconList.slots[0].hasIconsInSlot()).isTrue() + assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal + + verify(iconGroup).onRemoveIcon(1) + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_internalRemoved_viaRemoveAll_externalStays() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + underTest.setIcon(slotName, createExternalIcon()) + + // WHEN the internal icon is removed via #removeAllIconsForSlot + underTest.removeAllIconsForSlot(slotName) + + // THEN the external icon is removed but the internal icon remains + assertThat(iconList.slots).hasSize(2) + assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(iconList.slots[1].name).isEqualTo(slotName) + assertThat(iconList.slots[0].hasIconsInSlot()).isTrue() + assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal + + verify(iconGroup).onRemoveIcon(1) + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_internalUpdatedIndependently() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + val startingExternalIcon = + StatusBarIcon( + "external.package", + UserHandle.ALL, + /* iconId= */ 20, + /* iconLevel= */ 0, + /* number= */ 0, + "externalDescription", + ) + underTest.setIcon(slotName, startingExternalIcon) + + // WHEN the internal icon is updated + underTest.setIcon(slotName, /* resourceId= */ 11, "newContentDescription") + + // THEN only the internal slot gets the updates + val internalSlot = iconList.slots[1] + val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!! + assertThat(internalSlot.name).isEqualTo(slotName) + assertThat(internalHolder.icon!!.contentDescription).isEqualTo("newContentDescription") + assertThat(internalHolder.icon!!.icon.resId).isEqualTo(11) + + // And the external slot has its own values + val externalSlot = iconList.slots[0] + val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!! + assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(externalHolder.icon!!.contentDescription).isEqualTo("externalDescription") + assertThat(externalHolder.icon!!.icon.resId).isEqualTo(20) + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_externalUpdatedIndependently() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + val startingExternalIcon = + StatusBarIcon( + "external.package", + UserHandle.ALL, + /* iconId= */ 20, + /* iconLevel= */ 0, + /* number= */ 0, + "externalDescription", + ) + underTest.setIcon(slotName, startingExternalIcon) + + // WHEN the external icon is updated + val newExternalIcon = + StatusBarIcon( + "external.package", + UserHandle.ALL, + /* iconId= */ 21, + /* iconLevel= */ 0, + /* number= */ 0, + "newExternalDescription", + ) + underTest.setIcon(slotName, newExternalIcon) + + // THEN only the external slot gets the updates + val externalSlot = iconList.slots[0] + val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!! + assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(externalHolder.icon!!.contentDescription).isEqualTo("newExternalDescription") + assertThat(externalHolder.icon!!.icon.resId).isEqualTo(21) + + // And the internal slot has its own values + val internalSlot = iconList.slots[1] + val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!! + assertThat(internalSlot.name).isEqualTo(slotName) + assertThat(internalHolder.icon!!.contentDescription).isEqualTo("contentDescription") + assertThat(internalHolder.icon!!.icon.resId).isEqualTo(10) + } + + @Test + fun externalSlot_alreadyEndsWithSuffix_suffixNotAddedTwice() { + underTest.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon()) + + assertThat(iconList.slots).hasSize(1) + assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX") + } + + private fun createExternalIcon(): StatusBarIcon { + return StatusBarIcon( + "external.package", + UserHandle.ALL, + /* iconId= */ 2, + /* iconLevel= */ 0, + /* number= */ 0, + "contentDescription", + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 04a67006d686..e3e648a8f716 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.flags.Flags.MODERN_BOUNCER; +import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; +import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -255,7 +257,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true); mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); - verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN)); + verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(EXPANSION_HIDDEN)); } @Test @@ -283,7 +285,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK); mStatusBarKeyguardViewManager.onPanelExpansionChanged( expansionEvent( - /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* fraction= */ EXPANSION_VISIBLE, /* expanded= */ true, /* tracking= */ false)); verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat()); @@ -300,7 +302,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .thenReturn(BiometricUnlockController.MODE_DISMISS_BOUNCER); mStatusBarKeyguardViewManager.onPanelExpansionChanged( expansionEvent( - /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* fraction= */ EXPANSION_VISIBLE, /* expanded= */ true, /* tracking= */ false)); verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat()); @@ -311,7 +313,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { when(mKeyguardStateController.isOccluded()).thenReturn(true); mStatusBarKeyguardViewManager.onPanelExpansionChanged( expansionEvent( - /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* fraction= */ EXPANSION_VISIBLE, /* expanded= */ true, /* tracking= */ false)); verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat()); @@ -328,7 +330,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER); mStatusBarKeyguardViewManager.onPanelExpansionChanged( expansionEvent( - /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* fraction= */ EXPANSION_VISIBLE, /* expanded= */ true, /* tracking= */ false)); verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat()); @@ -339,7 +341,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); mStatusBarKeyguardViewManager.onPanelExpansionChanged( expansionEvent( - /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* fraction= */ EXPANSION_VISIBLE, /* expanded= */ true, /* tracking= */ false)); verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java index a9c55fa06cc5..0605b8d43f65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.flags.Flags.MODERN_BOUNCER; +import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; +import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -261,7 +263,7 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { when(mPrimaryBouncer.inTransit()).thenReturn(true); mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); - verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN)); + verify(mPrimaryBouncer).setExpansion(eq(EXPANSION_HIDDEN)); } @Test @@ -289,7 +291,7 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK); mStatusBarKeyguardViewManager.onPanelExpansionChanged( expansionEvent( - /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* fraction= */ EXPANSION_VISIBLE, /* expanded= */ true, /* tracking= */ false)); verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); @@ -306,7 +308,7 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { .thenReturn(BiometricUnlockController.MODE_DISMISS_BOUNCER); mStatusBarKeyguardViewManager.onPanelExpansionChanged( expansionEvent( - /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* fraction= */ EXPANSION_VISIBLE, /* expanded= */ true, /* tracking= */ false)); verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); @@ -317,7 +319,7 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { when(mKeyguardStateController.isOccluded()).thenReturn(true); mStatusBarKeyguardViewManager.onPanelExpansionChanged( expansionEvent( - /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* fraction= */ EXPANSION_VISIBLE, /* expanded= */ true, /* tracking= */ false)); verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); @@ -334,7 +336,7 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER); mStatusBarKeyguardViewManager.onPanelExpansionChanged( expansionEvent( - /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* fraction= */ EXPANSION_VISIBLE, /* expanded= */ true, /* tracking= */ false)); verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); @@ -345,7 +347,7 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); mStatusBarKeyguardViewManager.onPanelExpansionChanged( expansionEvent( - /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* fraction= */ EXPANSION_VISIBLE, /* expanded= */ true, /* tracking= */ false)); verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt index 8176dd07b84a..1bdee3667d04 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt @@ -19,9 +19,10 @@ package com.android.systemui.biometrics.udfps import android.graphics.Rect class FakeOverlapDetector : OverlapDetector { - var shouldReturn: Boolean = false + var shouldReturn: Map<Int, Boolean> = mapOf() override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean { - return shouldReturn + return shouldReturn[touchData.pointerId] + ?: error("Unexpected PointerId not declared in TestCase currentPointers") } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java index 2d6d29a50a74..926c6c56a862 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java @@ -98,6 +98,10 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> } @Override + public void removeAllIconsForExternalSlot(String slot) { + } + + @Override public void setIconAccessibilityLiveRegion(String slot, int mode) { } diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 17cd5d158f2d..6562ae70bc19 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -97,7 +97,6 @@ public class SettingsToPropertiesMapper { DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT, DeviceConfig.NAMESPACE_SWCODEC_NATIVE, - DeviceConfig.NAMESPACE_TETHERING, DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE, DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT, DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE, diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 75ba2146267d..9c43c1d62ab8 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1146,10 +1146,9 @@ final class LetterboxUiController { final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */, true /* traverseTopToBottom */); - if (firstOpaqueActivityBeneath == null - || mActivityRecord.launchedFromUid != firstOpaqueActivityBeneath.getUid()) { + if (firstOpaqueActivityBeneath == null) { // We skip letterboxing if the translucent activity doesn't have any opaque - // activities beneath of if it's launched from a different user (e.g. notification) + // activities beneath return; } inheritConfiguration(firstOpaqueActivityBeneath); diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 90a0dffa25f2..49b2a4ef51a7 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -540,9 +540,12 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr synchronized (mGlobalLock) { final TaskFragmentOrganizerState organizerState = mTaskFragmentOrganizerState.get(organizer.asBinder()); - return organizerState != null - ? organizerState.mRemoteAnimationDefinition - : null; + if (organizerState == null) { + Slog.e(TAG, "TaskFragmentOrganizer has been unregistered or died when trying" + + " to play animation on its organized windows."); + return null; + } + return organizerState.mRemoteAnimationDefinition; } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 38613a655d70..fd477532e984 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1962,6 +1962,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub ownerTask.addChild(taskFragment, position); taskFragment.setWindowingMode(creationParams.getWindowingMode()); taskFragment.setBounds(creationParams.getInitialBounds()); + // Record the initial relative embedded bounds. + taskFragment.updateRelativeEmbeddedBounds(); mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment); if (transition != null) transition.collectExistenceChange(taskFragment); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 63607ad19f5a..ae03fbbe1cff 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -396,14 +396,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP int mPrepareSyncSeqId = 0; /** - * {@code true} when the client was still drawing for sync when the sync-set was finished or - * cancelled. This can happen if the window goes away during a sync. In this situation we need - * to make sure to still apply the postDrawTransaction when it finishes to prevent the client - * from getting stuck in a bad state. - */ - boolean mClientWasDrawingForSync = false; - - /** * Special mode that is intended only for the rounded corner overlay: during rotation * transition, we un-rotate the window token such that the window appears as it did before the * rotation. @@ -6024,9 +6016,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override void finishSync(Transaction outMergedTransaction, boolean cancel) { - if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) { - mClientWasDrawingForSync = true; - } mPrepareSyncSeqId = 0; if (cancel) { // This is leaving sync so any buffers left in the sync have a chance of @@ -6094,9 +6083,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP layoutNeeded = onSyncFinishedDrawing(); } - layoutNeeded |= - mWinAnimator.finishDrawingLocked(postDrawTransaction, mClientWasDrawingForSync); - mClientWasDrawingForSync = false; + layoutNeeded |= mWinAnimator.finishDrawingLocked(postDrawTransaction); // We always want to force a traversal after a finish draw for blast sync. return !skipLayout && (hasSyncHandlers || layoutNeeded); } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index a0ba8fda906f..f3642480da58 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -151,17 +151,6 @@ class WindowStateAnimator { int mAttrType; - /** - * Handles surface changes synchronized to after the client has drawn the surface. This - * transaction is currently used to reparent the old surface children to the new surface once - * the client has completed drawing to the new surface. - * This transaction is also used to merge transactions parceled in by the client. The client - * uses the transaction to update the relative z of its children from the old parent surface - * to the new parent surface once window manager reparents its children. - */ - private final SurfaceControl.Transaction mPostDrawTransaction = - new SurfaceControl.Transaction(); - WindowStateAnimator(final WindowState win) { final WindowManagerService service = win.mWmService; @@ -217,8 +206,7 @@ class WindowStateAnimator { } } - boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction, - boolean forceApplyNow) { + boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) { final boolean startingWindow = mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; if (startingWindow) { @@ -240,14 +228,7 @@ class WindowStateAnimator { } if (postDrawTransaction != null) { - // If there is no surface, the last draw was for the previous surface. We don't want to - // wait until the new surface is shown and instead just apply the transaction right - // away. - if (mLastHidden && mDrawState != NO_SURFACE && !forceApplyNow) { - mPostDrawTransaction.merge(postDrawTransaction); - } else { - mWin.getSyncTransaction().merge(postDrawTransaction); - } + mWin.getSyncTransaction().merge(postDrawTransaction); layoutNeeded = true; } @@ -547,7 +528,6 @@ class WindowStateAnimator { if (!shown) return false; - t.merge(mPostDrawTransaction); return true; } @@ -714,10 +694,6 @@ class WindowStateAnimator { } void destroySurface(SurfaceControl.Transaction t) { - // Since the SurfaceControl is getting torn down, it's safe to just clean up any - // pending transactions that were in mPostDrawTransaction, as well. - t.merge(mPostDrawTransaction); - try { if (mSurfaceController != null) { mSurfaceController.destroy(t); diff --git a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java index 8a3a44ae9f35..0993295e162f 100644 --- a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java +++ b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.annotation.WorkerThread; import android.content.Context; import android.database.Cursor; +import android.database.sqlite.SQLiteException; import android.net.Uri; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; @@ -149,6 +150,8 @@ class ContactsQueryHelper { found = true; } + } catch (SQLiteException exception) { + Slog.w("SQLite exception when querying contacts.", exception); } if (found && lookupKey != null && hasPhoneNumber) { return queryPhoneNumber(lookupKey); diff --git a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java index 96302b954e75..299f15344dfa 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.when; import android.database.Cursor; import android.database.MatrixCursor; +import android.database.sqlite.SQLiteException; import android.net.Uri; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; @@ -63,6 +64,7 @@ public final class ContactsQueryHelperTest { private MatrixCursor mContactsLookupCursor; private MatrixCursor mPhoneCursor; private ContactsQueryHelper mHelper; + private ContactsContentProvider contentProvider; @Before public void setUp() { @@ -73,7 +75,7 @@ public final class ContactsQueryHelperTest { mPhoneCursor = new MatrixCursor(PHONE_COLUMNS); MockContentResolver contentResolver = new MockContentResolver(); - ContactsContentProvider contentProvider = new ContactsContentProvider(); + contentProvider = new ContactsContentProvider(); contentProvider.registerCursor(Contacts.CONTENT_URI, mContactsCursor); contentProvider.registerCursor( ContactsContract.PhoneLookup.CONTENT_FILTER_URI, mContactsLookupCursor); @@ -89,6 +91,14 @@ public final class ContactsQueryHelperTest { } @Test + public void testQueryException_returnsFalse() { + contentProvider.setThrowException(true); + + Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY); + assertFalse(mHelper.query(contactUri.toString())); + } + + @Test public void testQueryWithUri() { mContactsCursor.addRow(new Object[] { /* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 1, /* hasPhoneNumber= */ 1, @@ -168,10 +178,15 @@ public final class ContactsQueryHelperTest { private class ContactsContentProvider extends MockContentProvider { private Map<Uri, Cursor> mUriPrefixToCursorMap = new ArrayMap<>(); + private boolean throwException = false; @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + if (throwException) { + throw new SQLiteException(); + } + for (Uri prefixUri : mUriPrefixToCursorMap.keySet()) { if (uri.isPathPrefixMatch(prefixUri)) { return mUriPrefixToCursorMap.get(prefixUri); @@ -180,6 +195,10 @@ public final class ContactsQueryHelperTest { return mUriPrefixToCursorMap.get(uri); } + public void setThrowException(boolean throwException) { + this.throwException = throwException; + } + private void registerCursor(Uri uriPrefix, Cursor cursor) { mUriPrefixToCursorMap.put(uriPrefix, cursor); } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index a8e91980014e..995932c46201 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -197,27 +197,6 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testNotApplyStrategyToTranslucentActivitiesWithDifferentUid() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); - mActivity.info.setMinAspectRatio(1.2f); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) - .setMinAspectRatio(1.1f) - .setMaxAspectRatio(3f) - .build(); - doReturn(false).when(translucentActivity).fillsParent(); - mTask.addChild(translucentActivity); - // We check bounds - final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); - final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); - assertNotEquals(opaqueBounds, translucentRequestedBounds); - } - - @Test public void testApplyStrategyToMultipleTranslucentActivities() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); setUpDisplaySizeWithApp(2000, 1000); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 6b3425cf095c..8244f9419b80 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -796,6 +796,38 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test + public void testApplyTransaction_createTaskFragment_overrideBounds() { + final Task task = createTask(mDisplayContent); + final ActivityRecord activityAtBottom = createActivityRecord(task); + final int uid = Binder.getCallingUid(); + activityAtBottom.info.applicationInfo.uid = uid; + activityAtBottom.getTask().effectiveUid = uid; + mTaskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setFragmentToken(mFragmentToken) + .createActivityCount(1) + .build(); + mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); + final IBinder fragmentToken1 = new Binder(); + final Rect bounds = new Rect(100, 100, 500, 1000); + final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder( + mOrganizerToken, fragmentToken1, activityAtBottom.token) + .setPairedActivityToken(activityAtBottom.token) + .setInitialBounds(bounds) + .build(); + mTransaction.setTaskFragmentOrganizer(mIOrganizer); + mTransaction.createTaskFragment(params); + assertApplyTransactionAllowed(mTransaction); + + // Successfully created a TaskFragment. + final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment( + fragmentToken1); + assertNotNull(taskFragment); + // The relative embedded bounds is updated to the initial requested bounds. + assertEquals(bounds, taskFragment.getRelativeEmbeddedBounds()); + } + + @Test public void testApplyTransaction_createTaskFragment_withPairedActivityToken() { final Task task = createTask(mDisplayContent); final ActivityRecord activityAtBottom = createActivityRecord(task); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 3556ded23351..fd3776f828e5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -545,7 +545,12 @@ public class WindowStateTests extends WindowTestsBase { win.applyWithNextDraw(t -> handledT[0] = t); assertTrue(win.useBLASTSync()); final SurfaceControl.Transaction drawT = new StubTransaction(); + final SurfaceControl.Transaction currT = win.getSyncTransaction(); + clearInvocations(currT); + win.mWinAnimator.mLastHidden = true; assertTrue(win.finishDrawing(drawT, Integer.MAX_VALUE)); + // The draw transaction should be merged to current transaction even if the state is hidden. + verify(currT).merge(eq(drawT)); assertEquals(drawT, handledT[0]); assertFalse(win.useBLASTSync()); |