diff options
190 files changed, 3204 insertions, 1919 deletions
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 3a660812fd24..c872516014db 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -3535,7 +3535,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode. * They can be queried through * {@link android.hardware.camera2.CameraCharacteristics#get } with - * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }. + * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION }. * Unless reported by both * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and @@ -3550,13 +3550,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <ul> * <li> * <p>The mandatory stream combinations listed in - * {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations } - * would not apply.</p> + * {@link CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS android.scaler.mandatoryMaximumResolutionStreamCombinations} would not apply.</p> * </li> * <li> * <p>The bayer pattern of {@code RAW} streams when * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION } - * is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p> + * is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p> * </li> * <li> * <p>The following keys will always be present:</p> @@ -3576,9 +3575,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> * + * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see #SENSOR_PIXEL_MODE_DEFAULT diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 1536376e4eb5..57f7bca1f67e 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -4460,7 +4460,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode. * They can be queried through * {@link android.hardware.camera2.CameraCharacteristics#get } with - * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }. + * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION }. * Unless reported by both * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and @@ -4475,13 +4475,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <ul> * <li> * <p>The mandatory stream combinations listed in - * {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations } - * would not apply.</p> + * {@link CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS android.scaler.mandatoryMaximumResolutionStreamCombinations} would not apply.</p> * </li> * <li> * <p>The bayer pattern of {@code RAW} streams when * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION } - * is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p> + * is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p> * </li> * <li> * <p>The following keys will always be present:</p> @@ -4501,9 +4500,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> * + * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see #SENSOR_PIXEL_MODE_DEFAULT diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 7b5dd55f385b..795eb4a737ef 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1019,8 +1019,9 @@ public class InputMethodService extends AbstractInputMethodService { if (!mOnPreparedStylusHwCalled) { // prepare hasn't been called by Stylus HOVER. onPrepareStylusHandwriting(); - mOnPreparedStylusHwCalled = true; } + // reset flag as it's not relevant after onStartStylusHandwriting(). + mOnPreparedStylusHwCalled = false; if (onStartStylusHandwriting()) { cancelStylusWindowIdleTimeout(); mPrivOps.onStylusHandwritingReady(requestId, Process.myPid()); @@ -3089,7 +3090,8 @@ public class InputMethodService extends AbstractInputMethodService { mInputStarted = false; mStartedInputConnection = null; mCurCompletions = null; - if (mInkWindow != null) { + if (!mOnPreparedStylusHwCalled) { + // If IME didn't prepare to show InkWindow for current handwriting session. finishStylusHandwriting(); } // Back callback is typically unregistered in {@link #hideWindow()}, but it's possible diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 3c5757dd5615..5d6dfc760b02 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -63,6 +63,7 @@ import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.concurrent.Executor; @@ -1956,9 +1957,8 @@ public class CallLog { userManager.isUserUnlocked(user) ? CONTENT_URI : SHADOW_CONTENT_URI, user.getIdentifier()); - if (VERBOSE_LOG) { - Log.v(LOG_TAG, String.format("Inserting to %s", uri)); - } + Log.i(LOG_TAG, String.format(Locale.getDefault(), + "addEntryAndRemoveExpiredEntries: provider uri=%s", uri)); try { // When cleaning up the call log, try to delete older call long entries on a per diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 17afd5576973..4ecfc4044b1d 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -844,12 +844,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public boolean onStateChanged(InsetsState state) { boolean stateChanged = false; if (!CAPTION_ON_SHELL) { - stateChanged = !mState.equals(state, true /* excludingCaptionInsets */, - false /* excludeInvisibleIme */) + stateChanged = !mState.equals(state, true /* excludesCaptionBar */, + false /* excludesInvisibleIme */) || captionInsetsUnchanged(); } else { - stateChanged = !mState.equals(state, false /* excludingCaptionInsets */, - false /* excludeInvisibleIme */); + stateChanged = !mState.equals(state, false /* excludesCaptionBar */, + false /* excludesInvisibleIme */); } if (!stateChanged && mLastDispatchedState.equals(state)) { return false; @@ -862,8 +862,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation applyLocalVisibilityOverride(); updateCompatSysUiVisibility(); - if (!mState.equals(lastState, false /* excludingCaptionInsets */, - true /* excludeInvisibleIme */)) { + if (!mState.equals(lastState, false /* excludesCaptionBar */, + true /* excludesInvisibleIme */)) { if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged"); mHost.notifyInsetsChanged(); if (lastState.getDisplayFrame().equals(mState.getDisplayFrame())) { diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index c13b9ab0abd1..af24140086ed 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -380,11 +380,17 @@ public class InsetsState implements Parcelable { @InternalInsetsSide @Nullable SparseIntArray idSideMap, @Nullable boolean[] typeVisibilityMap, Insets insets, int type) { int index = indexOf(type); - Insets existing = typeInsetsMap[index]; - if (existing == null) { - typeInsetsMap[index] = insets; - } else { - typeInsetsMap[index] = Insets.max(existing, insets); + + // Don't put Insets.NONE into typeInsetsMap. Otherwise, two WindowInsets can be considered + // as non-equal while they provide the same insets of each type from WindowInsets#getInsets + // if one WindowInsets has Insets.NONE for a type and the other has null for the same type. + if (!Insets.NONE.equals(insets)) { + Insets existing = typeInsetsMap[index]; + if (existing == null) { + typeInsetsMap[index] = insets; + } else { + typeInsetsMap[index] = Insets.max(existing, insets); + } } if (typeVisibilityMap != null) { @@ -696,15 +702,14 @@ public class InsetsState implements Parcelable { * An equals method can exclude the caption insets. This is useful because we assemble the * caption insets information on the client side, and when we communicate with server, it's * excluded. - * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but - * ignore the caption insets source value. - * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored - * when IME is not visible. + * @param excludesCaptionBar If {@link Type#captionBar()}} should be ignored. + * @param excludesInvisibleIme If {@link WindowInsets.Type#ime()} should be ignored when IME is + * not visible. * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise. */ @VisibleForTesting - public boolean equals(@Nullable Object o, boolean excludingCaptionInsets, - boolean excludeInvisibleImeFrames) { + public boolean equals(@Nullable Object o, boolean excludesCaptionBar, + boolean excludesInvisibleIme) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } @@ -721,29 +726,35 @@ public class InsetsState implements Parcelable { final SparseArray<InsetsSource> thisSources = mSources; final SparseArray<InsetsSource> thatSources = state.mSources; - if (!excludingCaptionInsets && !excludeInvisibleImeFrames) { + if (!excludesCaptionBar && !excludesInvisibleIme) { return thisSources.contentEquals(thatSources); } else { final int thisSize = thisSources.size(); final int thatSize = thatSources.size(); int thisIndex = 0; int thatIndex = 0; - while (thisIndex < thisSize && thatIndex < thatSize) { + while (thisIndex < thisSize || thatIndex < thatSize) { + InsetsSource thisSource = thisIndex < thisSize + ? thisSources.valueAt(thisIndex) + : null; + // Seek to the next non-excluding source of ours. - InsetsSource thisSource = thisSources.valueAt(thisIndex); while (thisSource != null - && (excludingCaptionInsets && thisSource.getType() == captionBar() - || excludeInvisibleImeFrames && thisSource.getType() == ime() + && (excludesCaptionBar && thisSource.getType() == captionBar() + || excludesInvisibleIme && thisSource.getType() == ime() && !thisSource.isVisible())) { thisIndex++; thisSource = thisIndex < thisSize ? thisSources.valueAt(thisIndex) : null; } + InsetsSource thatSource = thatIndex < thatSize + ? thatSources.valueAt(thatIndex) + : null; + // Seek to the next non-excluding source of theirs. - InsetsSource thatSource = thatSources.valueAt(thatIndex); while (thatSource != null - && (excludingCaptionInsets && thatSource.getType() == captionBar() - || excludeInvisibleImeFrames && thatSource.getType() == ime() + && (excludesCaptionBar && thatSource.getType() == captionBar() + || excludesInvisibleIme && thatSource.getType() == ime() && !thatSource.isVisible())) { thatIndex++; thatSource = thatIndex < thatSize ? thatSources.valueAt(thatIndex) : null; @@ -756,7 +767,7 @@ public class InsetsState implements Parcelable { thisIndex++; thatIndex++; } - return thisIndex >= thisSize && thatIndex >= thatSize; + return true; } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 01a99b92a055..1b1098d9d57a 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2539,7 +2539,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int childrenCount = mChildrenCount; if (childrenCount != 0) { final float x = event.getXDispatchLocation(0); - final float y = event.getXDispatchLocation(0); + final float y = event.getYDispatchLocation(0); final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 3da9e96618f3..f39122a0a5c1 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -8207,7 +8207,8 @@ public class Editor { */ void beforeSetText() { // TextView#setText is called because our call to - // TextView#setTransformationMethodInternal in enterInsertMode() or exitInsertMode(). + // TextView#setTransformationMethodInternal in enterInsertMode(), exitInsertMode() or + // updateTransformationMethod(). // Do nothing in this case. if (mUpdatingTransformationMethod) { return; @@ -8218,22 +8219,28 @@ public class Editor { } /** - * Notify the {@link InsertModeController} before the TextView's - * {@link TransformationMethod} is updated. If it's not in the insert mode, - * the given method is directly returned. Otherwise, it will wrap the given transformation - * method with an {@link InsertModeTransformationMethod} and then return. + * Notify the {@link InsertModeController} that TextView#setTransformationMethod is called. + * If it's not in the insert mode, the given transformation method is directly set to the + * TextView. Otherwise, it will wrap the given transformation method with an + * {@link InsertModeTransformationMethod} and then set it on the TextView. * - * @param oldTransformationMethod the new {@link TransformationMethod} to be set on the + * @param transformationMethod the new {@link TransformationMethod} to be set on the * TextView. - * @return the updated {@link TransformationMethod} to be set on the Textview. */ - TransformationMethod updateTransformationMethod( - TransformationMethod oldTransformationMethod) { - if (!mIsInsertModeActive) return oldTransformationMethod; + void updateTransformationMethod(TransformationMethod transformationMethod) { + if (!mIsInsertModeActive) { + setTransformationMethod(transformationMethod, /* updateText */ true); + return; + } + // Changing TransformationMethod will reset selection range to [0, 0), we need to + // manually restore the old selection range. + final int selectionStart = mTextView.getSelectionStart(); + final int selectionEnd = mTextView.getSelectionEnd(); mInsertModeTransformationMethod = mInsertModeTransformationMethod.update( - oldTransformationMethod, mTextView.isSingleLine()); - return mInsertModeTransformationMethod; + transformationMethod, mTextView.isSingleLine()); + setTransformationMethod(mInsertModeTransformationMethod, /* updateText */ true); + Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd); } } @@ -8259,18 +8266,11 @@ public class Editor { * @param method the {@link TransformationMethod} to be set on the TextView. */ void setTransformationMethod(TransformationMethod method) { - if (mInsertModeController == null || !mInsertModeController.mIsInsertModeActive) { + if (mInsertModeController == null) { mTextView.setTransformationMethodInternal(method, /* updateText */ true); return; } - - // Changing TransformationMethod will reset selection range to [0, 0), we need to - // manually restore the old selection range. - final int selectionStart = mTextView.getSelectionStart(); - final int selectionEnd = mTextView.getSelectionEnd(); - method = mInsertModeController.updateTransformationMethod(method); - mTextView.setTransformationMethodInternal(method, /* updateText */ true); - Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd); + mInsertModeController.updateTransformationMethod(method); } /** diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 325ebbe885b4..8e619a8bda48 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -609,4 +609,6 @@ message BackNavigationProto { optional bool animation_in_progress = 1; optional int32 last_back_type = 2; optional bool show_wallpaper = 3; + optional string main_open_activity = 4; + optional bool animation_running = 5; }
\ No newline at end of file diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index fde1a6d7b04c..b06cd39d9236 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -43,6 +43,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -290,6 +291,18 @@ public class InsetsStateTest { } @Test + public void testCalculateInsets_emptyIme() { + WindowInsets insets1 = mState.calculateInsets(new Rect(), null, false, false, + SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); + mState.getOrCreateSource(ID_IME, ime()); + WindowInsets insets2 = mState.calculateInsets(new Rect(), null, false, false, + SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); + assertEquals(Insets.NONE, insets1.getInsets(ime())); + assertEquals(Insets.NONE, insets2.getInsets(ime())); + assertEquals(insets1, insets2); + } + + @Test public void testStripForDispatch() { mState.getOrCreateSource(ID_STATUS_BAR, statusBars()) .setFrame(new Rect(0, 0, 100, 100)) @@ -304,6 +317,73 @@ public class InsetsStateTest { } @Test + public void testEquals() { + final InsetsState state1 = new InsetsState(); + final InsetsState state2 = new InsetsState(); + assertTrue(state1.equals(state2)); + + state1.addSource(new InsetsSource(ID_STATUS_BAR, statusBars())); + assertFalse(state1.equals(state2)); + + state2.addSource(new InsetsSource(ID_STATUS_BAR, statusBars())); + assertTrue(state1.equals(state2)); + + state2.addSource(new InsetsSource(ID_NAVIGATION_BAR, navigationBars())); + assertFalse(state1.equals(state2)); + } + + @Test + public void testEquals_excludesCaptionBar() { + final InsetsState state1 = new InsetsState(); + final InsetsState state2 = new InsetsState(); + + state1.addSource(new InsetsSource(ID_CAPTION_BAR, captionBar()).setFrame(0, 0, 0, 5)); + assertFalse(state1.equals( + state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */)); + assertTrue(state1.equals( + state2, true /* excludesCaptionBar */, false /* excludesInvisibleIme */)); + + state2.addSource(new InsetsSource(ID_CAPTION_BAR, captionBar()).setFrame(0, 0, 0, 10)); + assertFalse(state1.equals( + state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */)); + assertTrue(state1.equals( + state2, true /* excludesCaptionBar */, false /* excludesInvisibleIme */)); + + state1.addSource(new InsetsSource(ID_STATUS_BAR, statusBars())); + state2.addSource(new InsetsSource(ID_STATUS_BAR, statusBars())); + assertFalse(state1.equals( + state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */)); + assertTrue(state1.equals( + state2, true /* excludesCaptionBar */, false /* excludesInvisibleIme */)); + } + + @Test + public void testEquals_excludesInvisibleIme() { + final InsetsState state1 = new InsetsState(); + final InsetsState state2 = new InsetsState(); + + final InsetsSource imeSource1 = new InsetsSource(ID_IME, ime()).setVisible(true); + state1.addSource(imeSource1); + assertFalse(state1.equals( + state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */)); + assertFalse(state1.equals( + state2, false /* excludesCaptionBar */, true /* excludesInvisibleIme */)); + + imeSource1.setVisible(false); + assertFalse(state1.equals( + state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */)); + assertTrue(state1.equals( + state2, false /* excludesCaptionBar */, true /* excludesInvisibleIme */)); + + final InsetsSource imeSource2 = new InsetsSource(ID_IME, ime()).setFrame(0, 0, 0, 10); + state2.addSource(imeSource2); + assertFalse(state1.equals( + state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */)); + assertTrue(state1.equals( + state2, false /* excludesCaptionBar */, true /* excludesInvisibleIme */)); + } + + @Test public void testEquals_differentRect() { mState.getOrCreateSource(ID_STATUS_BAR, statusBars()) .setFrame(new Rect(0, 0, 100, 100)); @@ -404,17 +484,6 @@ public class InsetsStateTest { } @Test - public void testEquals_excludeInvisibleIme() { - mState.getOrCreateSource(ID_IME, ime()) - .setFrame(new Rect(0, 0, 100, 100)) - .setVisible(false); - mState2.getOrCreateSource(ID_IME, ime()) - .setFrame(new Rect(0, 0, 100, 200)) - .setVisible(false); - assertTrue(mState2.equals(mState, true, true /* excludeInvisibleIme */)); - } - - @Test public void testParcelUnparcel() { mState.getOrCreateSource(ID_IME, ime()) .setFrame(new Rect(0, 0, 100, 100)) diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java index b37c8fd8c34e..bce3f3e8f2e1 100644 --- a/core/tests/coretests/src/android/view/ViewGroupTest.java +++ b/core/tests/coretests/src/android/view/ViewGroupTest.java @@ -49,11 +49,11 @@ public class ViewGroupTest { public void testDispatchMouseEventsUnderCursor() { final Context context = getInstrumentation().getContext(); final TestView viewGroup = new TestView(context, 0 /* left */, 0 /* top */, - 200 /* right */, 200 /* bottom */); + 200 /* right */, 100 /* bottom */); final TestView viewA = spy(new TestView(context, 0 /* left */, 0 /* top */, - 100 /* right */, 200 /* bottom */)); + 100 /* right */, 100 /* bottom */)); final TestView viewB = spy(new TestView(context, 100 /* left */, 0 /* top */, - 200 /* right */, 200 /* bottom */)); + 200 /* right */, 100 /* bottom */)); viewGroup.addView(viewA); viewGroup.addView(viewB); @@ -73,10 +73,10 @@ public class ViewGroupTest { MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[2]; coords[0] = new MotionEvent.PointerCoords(); coords[0].x = 80; - coords[0].y = 100; + coords[0].y = 50; coords[1] = new MotionEvent.PointerCoords(); coords[1].x = 240; - coords[1].y = 100; + coords[1].y = 50; MotionEvent event; // Make sure the down event is active with a pointer which coordinate is different from the @@ -91,6 +91,10 @@ public class ViewGroupTest { viewGroup.onResolvePointerIcon(event, 0 /* pointerIndex */); verify(viewB).onResolvePointerIcon(event, 0); + event.setAction(MotionEvent.ACTION_SCROLL); + viewGroup.dispatchGenericMotionEvent(event); + verify(viewB).dispatchGenericMotionEvent(event); + event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */, MotionEvent.ACTION_POINTER_DOWN | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT), 2 /* pointerCount */, properties, coords, 0 /* metaState */, 0 /* buttonState */, @@ -102,8 +106,13 @@ public class ViewGroupTest { viewGroup.onResolvePointerIcon(event, 1 /* pointerIndex */); verify(viewB).onResolvePointerIcon(event, 1); + event.setAction(MotionEvent.ACTION_SCROLL); + viewGroup.dispatchGenericMotionEvent(event); + verify(viewB).dispatchGenericMotionEvent(event); + verify(viewA, never()).dispatchTouchEvent(any()); verify(viewA, never()).onResolvePointerIcon(any(), anyInt()); + verify(viewA, never()).dispatchGenericMotionEvent(any()); } /** diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index 43683ffad432..c6a9033210e3 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -87,5 +87,6 @@ <permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" /> <permission name="android.permission.READ_SEARCH_INDEXABLES" /> <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/> + <permission name="android.permission.QUERY_CLONED_APPS"/> </privapp-permissions> </permissions> diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 71598938f42f..603272e3df1d 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -151,6 +151,7 @@ android_library { ], static_libs: [ "androidx.appcompat_appcompat", + "androidx.core_core-animation", "androidx.arch.core_core-runtime", "androidx-constraintlayout_constraintlayout", "androidx.dynamicanimation_dynamicanimation", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java index 798250de89d0..26edd7d2268b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java @@ -117,6 +117,20 @@ public class FlingAnimationUtils { * @param endValue the end value of the animator * @param velocity the current velocity of the motion */ + public void apply(androidx.core.animation.Animator animator, + float currValue, float endValue, float velocity) { + apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion + */ public void apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity) { apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); @@ -152,6 +166,24 @@ public class FlingAnimationUtils { * @param maxDistance the maximum distance for this interaction; the maximum animation length * gets multiplied by the ratio between the actual distance and this value */ + public void apply(androidx.core.animation.Animator animator, + float currValue, float endValue, float velocity, float maxDistance) { + AnimatorProperties properties = getProperties(currValue, endValue, velocity, maxDistance); + animator.setDuration(properties.mDuration); + animator.setInterpolator(properties.getInterpolator()); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion + * @param maxDistance the maximum distance for this interaction; the maximum animation length + * gets multiplied by the ratio between the actual distance and this value + */ public void apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance) { AnimatorProperties properties = getProperties(currValue, endValue, velocity, @@ -367,6 +399,11 @@ public class FlingAnimationUtils { private static class AnimatorProperties { Interpolator mInterpolator; long mDuration; + + /** Get an AndroidX interpolator wrapper of the current mInterpolator */ + public androidx.core.animation.Interpolator getInterpolator() { + return mInterpolator::getInterpolation; + } } /** Builder for {@link #FlingAnimationUtils}. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 8def8ff1ab01..99c8acdb3116 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -817,13 +817,17 @@ public class BubbleController implements ConfigurationChangeListener, * @param interceptBack whether back should be intercepted or not. */ void updateWindowFlagsForBackpress(boolean interceptBack) { - if (mStackView != null && mAddedToWindowManager) { + if (mAddedToWindowManager) { mWmLayoutParams.flags = interceptBack ? 0 : WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - mWindowManager.updateViewLayout(mStackView, mWmLayoutParams); + if (mStackView != null) { + mWindowManager.updateViewLayout(mStackView, mWmLayoutParams); + } else if (mLayerView != null) { + mWindowManager.updateViewLayout(mLayerView, mWmLayoutParams); + } } } @@ -1049,6 +1053,20 @@ public class BubbleController implements ConfigurationChangeListener, mBubbleData.setExpanded(false /* expanded */); } + /** + * Update expanded state when a single bubble is dragged in Launcher. + * Will be called only when bubble bar is expanded. + * @param bubbleKey key of the bubble to collapse/expand + * @param isBeingDragged whether the bubble is being dragged + */ + public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) { + if (mBubbleData.getSelectedBubble() != null + && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) { + // Should collapse/expand only if equals to selected bubble. + mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged); + } + } + @VisibleForTesting public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) { boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key) @@ -1434,6 +1452,17 @@ public class BubbleController implements ConfigurationChangeListener, } } + /** + * Removes all the bubbles. + * <p> + * Must be called from the main thread. + */ + @VisibleForTesting + @MainThread + public void removeAllBubbles(@Bubbles.DismissReason int reason) { + mBubbleData.dismissAll(reason); + } + private void onEntryAdded(BubbleEntry entry) { if (canLaunchInTaskView(mContext, entry)) { updateBubble(entry); @@ -2095,14 +2124,25 @@ public class BubbleController implements ConfigurationChangeListener, } @Override - public void removeBubble(String key, int reason) { - // TODO (b/271466616) allow removals from launcher + public void removeBubble(String key) { + mMainExecutor.execute( + () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE)); + } + + @Override + public void removeAllBubbles() { + mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE)); } @Override public void collapseBubbles() { mMainExecutor.execute(() -> mController.collapseStack()); } + + @Override + public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) { + mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged)); + } } private class BubblesImpl implements Bubbles { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 282db9e18d81..f58b121ae04f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -108,8 +108,10 @@ import java.util.stream.Collectors; public class BubbleStackView extends FrameLayout implements ViewTreeObserver.OnComputeInternalInsetsListener { + // LINT.IfChange public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE = SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true); + // LINT.ThenChange(com/android/launcher3/taskbar/bubbles/BubbleDismissController.java) private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index 351319f5fb5e..4dda0688b790 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl @@ -31,8 +31,12 @@ interface IBubbles { oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3; - oneway void removeBubble(in String key, in int reason) = 4; + oneway void removeBubble(in String key) = 4; - oneway void collapseBubbles() = 5; + oneway void removeAllBubbles() = 5; + + oneway void collapseBubbles() = 6; + + oneway void onBubbleDrag(in String key, in boolean isBeingDragged) = 7; }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index b3602b30072d..689323b725ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -215,6 +215,14 @@ public class BubbleBarAnimationHelper { mExpandedViewAlphaAnimator.reverse(); } + /** + * Cancel current animations + */ + public void cancelAnimations() { + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); + mExpandedViewAlphaAnimator.cancel(); + } + private void updateExpandedView() { if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) { Log.w(TAG, "Trying to update the expanded view without a bubble"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 8ead18b139de..bc04bfc8c18b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -150,6 +150,12 @@ public class BubbleBarLayerView extends FrameLayout mExpandedView = null; } if (mExpandedView == null) { + if (expandedView.getParent() != null) { + // Expanded view might be animating collapse and is still attached + // Cancel current animations and remove from parent + mAnimationHelper.cancelAnimations(); + removeView(expandedView); + } mExpandedBubble = b; mExpandedView = expandedView; boolean isOverflowExpanded = b.getKey().equals(BubbleOverflow.KEY); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java index 76ca68bbfa75..517f9f2aba27 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java @@ -54,6 +54,15 @@ public class DesktopModeStatus { public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean( "persist.wm.debug.desktop_change_display", false); + + /** + * Flag to indicate that desktop stashing is enabled. + * When enabled, swiping home from desktop stashes the open apps. Next app that launches, + * will be added to the desktop. + */ + private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean( + "persist.wm.debug.desktop_stashing", false); + /** * Return {@code true} if desktop mode support is enabled */ @@ -84,6 +93,13 @@ public class DesktopModeStatus { } /** + * Return {@code true} if desktop task stashing is enabled when going home. + * Allows users to use home screen to add tasks to desktop. + */ + public static boolean isStashingEnabled() { + return IS_STASHING_ENABLED; + } + /** * Check if desktop mode is active * * @return {@code true} if active diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index f8d7b6bc3aad..b15fd912e32d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -150,20 +150,24 @@ class DesktopTasksController( * back to front during the launch. */ fun stashDesktopApps(displayId: Int) { - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps") - desktopModeTaskRepository.setStashed(displayId, true) + if (DesktopModeStatus.isStashingEnabled()) { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps") + desktopModeTaskRepository.setStashed(displayId, true) + } } /** * Clear the stashed state for the given display */ fun hideStashedDesktopApps(displayId: Int) { - KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: hideStashedApps displayId=%d", - displayId - ) - desktopModeTaskRepository.setStashed(displayId, false) + if (DesktopModeStatus.isStashingEnabled()) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: hideStashedApps displayId=%d", + displayId + ) + desktopModeTaskRepository.setStashed(displayId, false) + } } /** Get number of tasks that are marked as visible */ @@ -172,9 +176,13 @@ class DesktopTasksController( } /** Move a task with given `taskId` to desktop */ - fun moveToDesktop(taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction()) { + fun moveToDesktop( + decor: DesktopModeWindowDecoration, + taskId: Int, + wct: WindowContainerTransaction = WindowContainerTransaction() + ) { shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { - task -> moveToDesktop(task, wct) + task -> moveToDesktop(decor, task, wct) } } @@ -182,6 +190,7 @@ class DesktopTasksController( * Move a task to desktop */ fun moveToDesktop( + decor: DesktopModeWindowDecoration, task: RunningTaskInfo, wct: WindowContainerTransaction = WindowContainerTransaction() ) { @@ -195,7 +204,7 @@ class DesktopTasksController( addMoveToDesktopChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) + enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor) } else { shellTaskOrganizer.applyTransaction(wct) } @@ -730,7 +739,7 @@ class DesktopTasksController( * * @param taskInfo the task being dragged. * @param position position of surface when drag ends. - * @param y the Y position of the motion event. + * @param y the Y position of the top edge of the task * @param windowDecor the window decoration for the task being dragged */ fun onDragPositioningEnd( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 22929c763f27..16b23935559c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.app.ActivityManager; import android.graphics.PointF; @@ -36,6 +37,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration; import com.android.wm.shell.windowdecor.MoveToDesktopAnimator; import java.util.ArrayList; @@ -60,6 +62,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback; private MoveToDesktopAnimator mMoveToDesktopAnimator; + private DesktopModeWindowDecoration mDesktopModeWindowDecoration; public EnterDesktopTaskTransitionHandler( Transitions transitions) { @@ -128,6 +131,18 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition onAnimationEndCallback); } + /** + * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP + * @param wct WindowContainerTransaction for transition + * @param decor {@link DesktopModeWindowDecoration} of task being animated + */ + public void moveToDesktop(@NonNull WindowContainerTransaction wct, + DesktopModeWindowDecoration decor) { + mDesktopModeWindowDecoration = decor; + startTransition(Transitions.TRANSIT_MOVE_TO_DESKTOP, wct, + null /* onAnimationEndCallback */); + } + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @@ -167,136 +182,207 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE + if (type == Transitions.TRANSIT_MOVE_TO_DESKTOP && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - // Transitioning to freeform but keeping fullscreen bounds, so the crop is set - // to null and we don't require an animation - final SurfaceControl sc = change.getLeash(); - startT.setWindowCrop(sc, null); - - if (mMoveToDesktopAnimator == null - || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { - Slog.e(TAG, "No animator available for this transition"); - return false; - } - - // Calculate and set position of the task - final PointF position = mMoveToDesktopAnimator.getPosition(); - startT.setPosition(sc, position.x, position.y); - finishT.setPosition(sc, position.x, position.y); - - startT.apply(); - - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + return animateMoveToDesktop(change, startT, finishCallback); + } - return true; + if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE + && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + return animateStartDragToDesktopMode(change, startT, finishT, finishCallback); } - Rect endBounds = change.getEndAbsBounds(); + final Rect endBounds = change.getEndAbsBounds(); if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM && !endBounds.isEmpty()) { - // This Transition animates a task to freeform bounds after being dragged into freeform - // mode and brings the remaining freeform tasks to front - final SurfaceControl sc = change.getLeash(); - startT.setWindowCrop(sc, endBounds.width(), - endBounds.height()); - startT.apply(); - - // End the animation that shrinks the window when task is first dragged from fullscreen - if (mMoveToDesktopAnimator != null) { - mMoveToDesktopAnimator.endAnimator(); - } - - // We want to find the scale of the current bounds relative to the end bounds. The - // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be - // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to - // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds - final ValueAnimator animator = - ValueAnimator.ofFloat( - MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f); - animator.setDuration(FREEFORM_ANIMATION_DURATION); - final SurfaceControl.Transaction t = mTransactionSupplier.get(); - animator.addUpdateListener(animation -> { - final float animationValue = (float) animation.getAnimatedValue(); - t.setScale(sc, animationValue, animationValue); - - final float animationWidth = endBounds.width() * animationValue; - final float animationHeight = endBounds.height() * animationValue; - final int animationX = endBounds.centerX() - (int) (animationWidth / 2); - final int animationY = endBounds.centerY() - (int) (animationHeight / 2); - - t.setPosition(sc, animationX, animationY); - t.apply(); - }); - - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mOnAnimationFinishedCallback != null) { - mOnAnimationFinishedCallback.accept(finishT); - } - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); - } - }); - - animator.start(); - return true; + return animateFinalizeDragToDesktopMode(change, startT, finishT, finishCallback, + endBounds); } if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - // This Transition animates a task to fullscreen after being dragged from the status - // bar and then released back into the status bar area - final SurfaceControl sc = change.getLeash(); - // Hide the first (fullscreen) frame because the animation will start from the smaller - // scale size. - startT.hide(sc) - .setWindowCrop(sc, endBounds.width(), endBounds.height()) - .apply(); + return animateCancelDragToDesktopMode(change, startT, finishT, finishCallback, + endBounds); + } - if (mMoveToDesktopAnimator == null - || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { - Slog.e(TAG, "No animator available for this transition"); - return false; + return false; + } + + private boolean animateMoveToDesktop( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mDesktopModeWindowDecoration == null) { + Slog.e(TAG, "Window Decoration is not available for this transition"); + return false; + } + + final SurfaceControl leash = change.getLeash(); + final Rect startBounds = change.getStartAbsBounds(); + startT.setPosition(leash, startBounds.left, startBounds.right) + .setWindowCrop(leash, startBounds.width(), startBounds.height()) + .show(leash); + mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds); + + final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(), + change.getStartAbsBounds(), change.getEndAbsBounds()); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + SurfaceControl.Transaction t = mTransactionSupplier.get(); + animator.addUpdateListener(animation -> { + final Rect animationValue = (Rect) animator.getAnimatedValue(); + t.setPosition(leash, animationValue.left, animationValue.right) + .setWindowCrop(leash, animationValue.width(), animationValue.height()) + .show(leash); + mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mDesktopModeWindowDecoration.hideResizeVeil(); + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null, null)); } + }); + animator.start(); + return true; + } - // End the animation that shrinks the window when task is first dragged from fullscreen + private boolean animateStartDragToDesktopMode( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + // Transitioning to freeform but keeping fullscreen bounds, so the crop is set + // to null and we don't require an animation + final SurfaceControl sc = change.getLeash(); + startT.setWindowCrop(sc, null); + + if (mMoveToDesktopAnimator == null + || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { + Slog.e(TAG, "No animator available for this transition"); + return false; + } + + // Calculate and set position of the task + final PointF position = mMoveToDesktopAnimator.getPosition(); + startT.setPosition(sc, position.x, position.y); + finishT.setPosition(sc, position.x, position.y); + + startT.apply(); + + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null, null)); + + return true; + } + + private boolean animateFinalizeDragToDesktopMode( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull Rect endBounds) { + // This Transition animates a task to freeform bounds after being dragged into freeform + // mode and brings the remaining freeform tasks to front + final SurfaceControl sc = change.getLeash(); + startT.setWindowCrop(sc, endBounds.width(), + endBounds.height()); + startT.apply(); + + // End the animation that shrinks the window when task is first dragged from fullscreen + if (mMoveToDesktopAnimator != null) { mMoveToDesktopAnimator.endAnimator(); + } - final ValueAnimator animator = new ValueAnimator(); - animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f); - animator.setDuration(FREEFORM_ANIMATION_DURATION); - final SurfaceControl.Transaction t = mTransactionSupplier.get(); - - // Get position of the task - final float x = mMoveToDesktopAnimator.getPosition().x; - final float y = mMoveToDesktopAnimator.getPosition().y; - - animator.addUpdateListener(animation -> { - final float scale = (float) animation.getAnimatedValue(); - t.setPosition(sc, x * (1 - scale), y * (1 - scale)) - .setScale(sc, scale, scale) - .show(sc) - .apply(); - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mOnAnimationFinishedCallback != null) { - mOnAnimationFinishedCallback.accept(finishT); - } - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + // We want to find the scale of the current bounds relative to the end bounds. The + // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be + // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to + // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds + final ValueAnimator animator = + ValueAnimator.ofFloat( + MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + animator.addUpdateListener(animation -> { + final float animationValue = (float) animation.getAnimatedValue(); + t.setScale(sc, animationValue, animationValue); + + final float animationWidth = endBounds.width() * animationValue; + final float animationHeight = endBounds.height() * animationValue; + final int animationX = endBounds.centerX() - (int) (animationWidth / 2); + final int animationY = endBounds.centerY() - (int) (animationHeight / 2); + + t.setPosition(sc, animationX, animationY); + t.apply(); + }); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mOnAnimationFinishedCallback != null) { + mOnAnimationFinishedCallback.accept(finishT); } - }); - animator.start(); - return true; + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null, null)); + } + }); + + animator.start(); + return true; + } + private boolean animateCancelDragToDesktopMode( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull Rect endBounds) { + // This Transition animates a task to fullscreen after being dragged from the status + // bar and then released back into the status bar area + final SurfaceControl sc = change.getLeash(); + // Hide the first (fullscreen) frame because the animation will start from the smaller + // scale size. + startT.hide(sc) + .setWindowCrop(sc, endBounds.width(), endBounds.height()) + .apply(); + + if (mMoveToDesktopAnimator == null + || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { + Slog.e(TAG, "No animator available for this transition"); + return false; } - return false; + // End the animation that shrinks the window when task is first dragged from fullscreen + mMoveToDesktopAnimator.endAnimator(); + + final ValueAnimator animator = new ValueAnimator(); + animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + + // Get position of the task + final float x = mMoveToDesktopAnimator.getPosition().x; + final float y = mMoveToDesktopAnimator.getPosition().y; + + animator.addUpdateListener(animation -> { + final float scale = (float) animation.getAnimatedValue(); + t.setPosition(sc, x * (1 - scale), y * (1 - scale)) + .setScale(sc, scale, scale) + .show(sc) + .apply(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mOnAnimationFinishedCallback != null) { + mOnAnimationFinishedCallback.accept(finishT); + } + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null, null)); + } + }); + animator.start(); + return true; } @Nullable diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index e45dacf1189d..e2dce88d5958 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -168,6 +168,9 @@ public class Transitions implements RemoteCallable<Transitions>, public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 14; + /** Transition to animate task to desktop. */ + public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15; + private final WindowOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; 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 2d7e6a602f2f..4cc755bdf632 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 @@ -222,7 +222,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE || info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE - || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) { + || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE + || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) { mWindowDecorByTaskId.get(change.getTaskInfo().taskId) .addTransitionPausingRelayout(transition); } @@ -356,7 +357,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // App sometimes draws before the insets from WindowDecoration#relayout have // been added, so they must be added here mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct); - mDesktopTasksController.get().moveToDesktop(mTaskId, wct); + decoration.incrementRelayoutBlock(); + mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct); } decoration.closeHandleMenu(); } else if (id == R.id.fullscreen_button) { @@ -429,10 +431,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragPointerId = e.getPointerId(0); } final int dragPointerIdx = e.findPointerIndex(mDragPointerId); - mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo, - decoration.mTaskSurface, e.getRawY(dragPointerIdx))); - mDragPositioningCallback.onDragPositioningMove( + final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); + mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo, + decoration.mTaskSurface, newTaskBounds.top)); mIsDragging = true; mShouldClick = false; return true; @@ -458,10 +460,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final Point position = new Point( (int) (e.getRawX(dragPointerIdx) - e.getX(dragPointerIdx)), (int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx))); - mDragPositioningCallback.onDragPositioningEnd( + final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo, - position, e.getRawY(), mWindowDecorByTaskId.get(mTaskId))); + position, newTaskBounds.top, mWindowDecorByTaskId.get(mTaskId))); mIsDragging = false; return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java index 941617de3aec..1669cf4a222c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java @@ -17,6 +17,7 @@ package com.android.wm.shell.windowdecor; import android.annotation.IntDef; +import android.graphics.Rect; /** * Callback called when receiving drag-resize or drag-move related input events. @@ -46,13 +47,15 @@ public interface DragPositioningCallback { * Called when the pointer moves during a drag-resize or drag-move. * @param x x coordinate in window decoration coordinate system of the new pointer location * @param y y coordinate in window decoration coordinate system of the new pointer location + * @return the updated task bounds */ - void onDragPositioningMove(float x, float y); + Rect onDragPositioningMove(float x, float y); /** * Called when a drag-resize or drag-move stops. * @param x x coordinate in window decoration coordinate system where the drag resize stops * @param y y coordinate in window decoration coordinate system where the drag resize stops + * @return the final bounds for the dragged task */ - void onDragPositioningEnd(float x, float y); + Rect onDragPositioningEnd(float x, float y); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java index e1b6db595975..e0ee25242550 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java @@ -85,7 +85,7 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { } @Override - public void onDragPositioningMove(float x, float y) { + public Rect onDragPositioningMove(float x, float y) { final WindowContainerTransaction wct = new WindowContainerTransaction(); PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType, @@ -106,10 +106,11 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y); t.apply(); } + return new Rect(mRepositionTaskBounds); } @Override - public void onDragPositioningEnd(float x, float y) { + public Rect onDragPositioningEnd(float x, float y) { // If task has been resized or task was dragged into area outside of // mDisallowedAreaForEndBounds, apply WCT to finish it. if (isResizing() && mHasDragResized) { @@ -136,6 +137,7 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { mRepositionStartPoint.set(0, 0); mCtrlType = CTRL_TYPE_UNDEFINED; mHasDragResized = false; + return new Rect(mRepositionTaskBounds); } private boolean isResizing() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index ae3b5eb6a5b5..fb05c696af82 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -105,7 +105,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, } @Override - public void onDragPositioningMove(float x, float y) { + public Rect onDragPositioningMove(float x, float y) { PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType, mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, @@ -117,10 +117,11 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y); t.apply(); } + return new Rect(mRepositionTaskBounds); } @Override - public void onDragPositioningEnd(float x, float y) { + public Rect onDragPositioningEnd(float x, float y) { PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); if (isResizing()) { @@ -151,6 +152,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, mCtrlType = CTRL_TYPE_UNDEFINED; mTaskBoundsAtDragStart.setEmpty(); mRepositionStartPoint.set(0, 0); + return new Rect(mRepositionTaskBounds); } private boolean isResizing() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 1477cf7415cf..5d87cf8b25a6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -57,6 +57,7 @@ import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.After @@ -92,6 +93,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler @Mock lateinit var launchAdjacentController: LaunchAdjacentController + @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -276,8 +278,8 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() { val task = setUpFullscreenTask() task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN - controller.moveToDesktop(task) - val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + controller.moveToDesktop(desktopModeWindowDecoration, task) + val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) } @@ -286,15 +288,15 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() { val task = setUpFullscreenTask() task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM - controller.moveToDesktop(task) - val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + controller.moveToDesktop(desktopModeWindowDecoration, task) + val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_UNDEFINED) } @Test fun moveToDesktop_nonExistentTask_doesNothing() { - controller.moveToDesktop(999) + controller.moveToDesktop(desktopModeWindowDecoration, 999) verifyWCTNotExecuted() } @@ -305,9 +307,9 @@ class DesktopTasksControllerTest : ShellTestCase() { val fullscreenTask = setUpFullscreenTask() markTaskHidden(freeformTask) - controller.moveToDesktop(fullscreenTask) + controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTask) - with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + with(getLatestMoveToDesktopWct()) { // Operations should include home task, freeform task assertThat(hierarchyOps).hasSize(3) assertReorderSequence(homeTask, freeformTask, fullscreenTask) @@ -327,9 +329,9 @@ class DesktopTasksControllerTest : ShellTestCase() { val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY) markTaskHidden(freeformTaskSecond) - controller.moveToDesktop(fullscreenTaskDefault) + controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTaskDefault) - with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + with(getLatestMoveToDesktopWct()) { // Check that hierarchy operations do not include tasks from second display assertThat(hierarchyOps.map { it.container }) .doesNotContain(homeTaskSecond.token.asBinder()) @@ -498,6 +500,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) markTaskHidden(stashedFreeformTask) @@ -569,6 +572,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) markTaskHidden(stashedFreeformTask) @@ -626,6 +630,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun stashDesktopApps_stateUpdates() { + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) + controller.stashDesktopApps(DEFAULT_DISPLAY) assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue() @@ -634,6 +640,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun hideStashedDesktopApps_stateUpdates() { + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) + desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true) desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true) controller.hideStashedDesktopApps(DEFAULT_DISPLAY) @@ -715,6 +723,16 @@ class DesktopTasksControllerTest : ShellTestCase() { return arg.value } + private fun getLatestMoveToDesktopWct(): WindowContainerTransaction { + val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + if (ENABLE_SHELL_TRANSITIONS) { + verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any()) + } else { + verify(shellTaskOrganizer).applyTransaction(arg.capture()) + } + return arg.value + } + private fun verifyWCTNotExecuted() { if (ENABLE_SHELL_TRANSITIONS) { verify(transitions, never()).startTransition(anyInt(), any(), isNull()) diff --git a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml index 5d6c343c1fbc..accaa67db1fc 100644 --- a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml +++ b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml @@ -25,10 +25,12 @@ <color name="settingslib_color_blue100">#d2e3fc</color> <color name="settingslib_color_blue50">#e8f0fe</color> <color name="settingslib_color_green600">#1e8e3e</color> + <color name="settingslib_color_green500">#34A853</color> <color name="settingslib_color_green400">#5bb974</color> <color name="settingslib_color_green100">#ceead6</color> <color name="settingslib_color_green50">#e6f4ea</color> <color name="settingslib_color_red600">#d93025</color> + <color name="settingslib_color_red500">#B3261E</color> <color name="settingslib_color_red400">#ee675c</color> <color name="settingslib_color_red100">#fad2cf</color> <color name="settingslib_color_red50">#fce8e6</color> diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java index 5e2c43735361..f166a18f528d 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java @@ -42,9 +42,6 @@ public class LottieColorUtils { ".grey600", R.color.settingslib_color_grey400); map.put( - ".grey700", - R.color.settingslib_color_grey500); - map.put( ".grey800", R.color.settingslib_color_grey300); map.put( @@ -62,6 +59,12 @@ public class LottieColorUtils { map.put( ".green400", R.color.settingslib_color_green600); + map.put( + ".green200", + R.color.settingslib_color_green500); + map.put( + ".red200", + R.color.settingslib_color_red500); DARK_TO_LIGHT_THEME_COLOR_MAP = Collections.unmodifiableMap(map); } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 2bac32785d41..dca9e2148237 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -200,6 +200,7 @@ android_library { "lottie", "LowLightDreamLib", "motion_tool_lib", + "IntentResolver-core", ], manifest: "AndroidManifest.xml", @@ -383,6 +384,7 @@ android_library { "motion_tool_lib", "androidx.core_core-animation-testing-nodeps", "androidx.compose.ui_ui", + "IntentResolver-core", ], } diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 1ce347215954..b708fc27448d 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -88,7 +88,6 @@ thiruram@google.com tracyzhou@google.com tsuji@google.com twickham@google.com -vadimt@google.com victortulias@google.com winsonc@google.com wleshner@google.com diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 2711dad4080b..3af7a4502856 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -56,20 +56,6 @@ ] }, { - "name": "SystemUIGoogleScreenshotTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.Postsubmit" - } - ] - }, - { // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+) "name": "SystemUIGoogleBiometricsScreenshotTests", "options": [ @@ -151,21 +137,5 @@ } ] } - ], - "postsubmit": [ - { - "name": "SystemUIGoogleScreenshotTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { - "include-annotation": "android.platform.test.annotations.Postsubmit" - } - ] - } ] } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt new file mode 100644 index 000000000000..c94fad7246fa --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt @@ -0,0 +1,36 @@ +/* + * 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.surfaceeffects.shaders + +import android.graphics.RuntimeShader + +/** Simply renders a solid color. */ +class SolidColorShader(color: Int) : RuntimeShader(SHADER) { + // language=AGSL + private companion object { + private const val SHADER = + """ + layout(color) uniform vec4 in_color; + vec4 main(vec2 p) { + return in_color; + } + """ + } + + init { + setColorUniform("in_color", color) + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt new file mode 100644 index 000000000000..df07856e325e --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt @@ -0,0 +1,115 @@ +/* + * 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.surfaceeffects.shaders + +import android.graphics.Color +import android.graphics.RuntimeShader +import android.graphics.Shader +import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary + +/** + * Renders sparkles based on the luma matte. + * + * For example, you can pass in simplex noise as the luma matte and have a cloud looking sparkles. + * + * You may want to utilize this shader by: (Preferred) 1. Create a RuntimeShaderEffect and set the + * [RenderEffect] to the target [View]. + * 2. Create a custom [View], set the shader to the [Paint] and use [Canvas.drawPaint] in [onDraw]. + */ +class SparkleShader : RuntimeShader(SPARKLE_SHADER) { + // language=AGSL + companion object { + private const val UNIFORMS = + """ + // Used it for RenderEffect. For example: + // myView.setRenderEffect( + // RenderEffect.createRuntimeShaderEffect(SparkleShader(), "in_src") + // ) + uniform shader in_src; + uniform half in_time; + uniform half in_pixelate; + uniform shader in_lumaMatte; + layout(color) uniform vec4 in_color; + """ + private const val MAIN_SHADER = + """vec4 main(vec2 p) { + half3 src = in_src.eval(p).rgb; + half luma = getLuminosity(in_lumaMatte.eval(p).rgb); + half sparkle = sparkles(p - mod(p, in_pixelate), in_time); + half3 mask = maskLuminosity(in_color.rgb * sparkle, luma); + + return vec4(src * mask * in_color.a, in_color.a); + } + """ + private const val SPARKLE_SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER + + /** Highly recommended to use this value unless specified by design spec. */ + const val DEFAULT_SPARKLE_PIXELATE_AMOUNT = 0.8f + } + + init { + // Initializes the src and luma matte to be white. + setInputShader("in_src", SolidColorShader(Color.WHITE)) + setLumaMatteColor(Color.WHITE) + } + + /** + * Sets the time of the sparkle animation. + * + * This is used for animating sparkles. Note that this only makes the sparkles sparkle in place. + * In order to move the sparkles in x, y directions, move the luma matte input instead. + */ + fun setTime(time: Float) { + setFloatUniform("in_time", time) + } + + /** + * Sets pixelated amount of the sparkle. + * + * This value *must* be based on [resources.displayMetrics.density]. Otherwise, this will result + * in having different sparkle sizes on different screens. + * + * Expected to be used as follows: + * <pre> + * {@code + * val pixelDensity = context.resources.displayMetrics.density + * // Sparkles will be 0.8 of the pixel size. + * val sparkleShader = SparkleShader().apply { setPixelateAmount(pixelDensity * 0.8f) } + * } + * </pre> + */ + fun setPixelateAmount(pixelateAmount: Float) { + setFloatUniform("in_pixelate", pixelateAmount) + } + + /** + * Sets the luma matte for the sparkles. The luminosity determines the sparkle's visibility. + * Useful for setting a complex mask (e.g. simplex noise, texture, etc.) + */ + fun setLumaMatte(lumaMatte: Shader) { + setInputShader("in_lumaMatte", lumaMatte) + } + + /** Sets the luma matte for the sparkles. Useful for setting a solid color. */ + fun setLumaMatteColor(color: Int) { + setInputShader("in_lumaMatte", SolidColorShader(color)) + } + + /** Sets the color of the sparkles. Expect to have the alpha value encoded. */ + fun setColor(color: Int) { + setColorUniform("in_color", color) + } +} diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt index 5413f9097c5b..24064b1261b7 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt @@ -17,12 +17,10 @@ package com.android.systemui.scene.ui.composable import com.android.systemui.scene.shared.model.Scene -import com.android.systemui.scene.shared.model.SceneContainerNames import dagger.Module import dagger.multibindings.Multibinds -import javax.inject.Named @Module interface SceneModule { - @Multibinds @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) fun scenes(): Set<Scene> + @Multibinds fun scenes(): Set<Scene> } diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt index d3643747ad91..3e9b3975eef4 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt @@ -16,35 +16,29 @@ package com.android.systemui.scene.ui.composable +import android.app.AlertDialog import android.content.Context import com.android.systemui.bouncer.ui.composable.BouncerScene -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.bouncer.ui.composable.BouncerSceneDialogFactory import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.ui.composable.LockscreenScene -import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.qs.ui.composable.QuickSettingsScene -import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel import com.android.systemui.scene.shared.model.Scene -import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.shade.ui.composable.ShadeScene -import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel import com.android.systemui.statusbar.phone.SystemUIDialog import dagger.Module import dagger.Provides -import javax.inject.Named -import kotlinx.coroutines.CoroutineScope @Module object SceneModule { @Provides - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) fun scenes( - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) bouncer: BouncerScene, - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) gone: GoneScene, - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) lockScreen: LockscreenScene, - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) qs: QuickSettingsScene, - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) shade: ShadeScene, + bouncer: BouncerScene, + gone: GoneScene, + lockScreen: LockscreenScene, + qs: QuickSettingsScene, + shade: ShadeScene, ): Set<Scene> { return setOf( bouncer, @@ -57,70 +51,11 @@ object SceneModule { @Provides @SysUISingleton - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) - fun bouncerScene( - @Application context: Context, - viewModelFactory: BouncerViewModel.Factory, - ): BouncerScene { - return BouncerScene( - viewModel = - viewModelFactory.create( - containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, - ), - dialogFactory = { SystemUIDialog(context) }, - ) - } - - @Provides - @SysUISingleton - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) - fun goneScene(): GoneScene { - return GoneScene() - } - - @Provides - @SysUISingleton - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) - fun lockscreenScene( - @Application applicationScope: CoroutineScope, - viewModelFactory: LockscreenSceneViewModel.Factory, - ): LockscreenScene { - return LockscreenScene( - applicationScope = applicationScope, - viewModel = - viewModelFactory.create( - containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, - ), - ) - } - - @Provides - @SysUISingleton - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) - fun quickSettingsScene( - viewModelFactory: QuickSettingsSceneViewModel.Factory, - ): QuickSettingsScene { - return QuickSettingsScene( - viewModel = - viewModelFactory.create( - containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, - ), - ) - } - - @Provides - @SysUISingleton - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) - fun shadeScene( - @Application applicationScope: CoroutineScope, - viewModelFactory: ShadeSceneViewModel.Factory, - ): ShadeScene { - return ShadeScene( - applicationScope = applicationScope, - viewModel = - viewModelFactory.create( - containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, - ), - ) + fun bouncerSceneDialogFactory(@Application context: Context): BouncerSceneDialogFactory { + return object : BouncerSceneDialogFactory { + override fun invoke(): AlertDialog { + return SystemUIDialog(context) + } + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index d83596eeb853..6d9497dac8ea 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -52,24 +52,27 @@ import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow /** The bouncer scene displays authentication challenges like PIN, password, or pattern. */ -class BouncerScene( +@SysUISingleton +class BouncerScene +@Inject +constructor( private val viewModel: BouncerViewModel, - private val dialogFactory: () -> AlertDialog, + private val dialogFactory: BouncerSceneDialogFactory, ) : ComposableScene { override val key = SceneKey.Bouncer - override fun destinationScenes( - containerName: String, - ): StateFlow<Map<UserAction, SceneModel>> = + override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> = MutableStateFlow<Map<UserAction, SceneModel>>( mapOf( UserAction.Back to SceneModel(SceneKey.Lockscreen), @@ -79,7 +82,6 @@ class BouncerScene( @Composable override fun Content( - containerName: String, modifier: Modifier, ) = BouncerScene(viewModel, dialogFactory, modifier) } @@ -87,7 +89,7 @@ class BouncerScene( @Composable private fun BouncerScene( viewModel: BouncerViewModel, - dialogFactory: () -> AlertDialog, + dialogFactory: BouncerSceneDialogFactory, modifier: Modifier = Modifier, ) { val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() @@ -175,3 +177,7 @@ private fun BouncerScene( } } } + +interface BouncerSceneDialogFactory { + operator fun invoke(): AlertDialog +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index 10652678739c..ab7bc26d59e1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.scene.shared.model.Direction @@ -38,6 +39,7 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -45,15 +47,16 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** The lock screen scene shows when the device is locked. */ -class LockscreenScene( +@SysUISingleton +class LockscreenScene +@Inject +constructor( @Application private val applicationScope: CoroutineScope, private val viewModel: LockscreenSceneViewModel, ) : ComposableScene { override val key = SceneKey.Lockscreen - override fun destinationScenes( - containerName: String, - ): StateFlow<Map<UserAction, SceneModel>> = + override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> = viewModel.upDestinationSceneKey .map { pageKey -> destinationScenes(up = pageKey) } .stateIn( @@ -64,7 +67,6 @@ class LockscreenScene( @Composable override fun Content( - containerName: String, modifier: Modifier, ) { LockscreenScene( 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 f88fc21addff..d84e67620177 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 @@ -20,7 +20,6 @@ import android.annotation.StringRes import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -28,9 +27,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -39,6 +38,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap @@ -134,10 +134,11 @@ private fun PeopleScreenWithConversations( ) } - LazyColumn( - Modifier.fillMaxWidth().sysuiResTag("scroll_view"), - contentPadding = - PaddingValues( + Column( + Modifier.fillMaxWidth() + .sysuiResTag("scroll_view") + .verticalScroll(rememberScrollState()) + .padding( top = 16.dp, bottom = PeopleSpacePadding, start = 8.dp, @@ -151,7 +152,7 @@ private fun PeopleScreenWithConversations( if (recentTiles.isNotEmpty()) { if (hasPriorityConversations) { - item { Spacer(Modifier.height(35.dp)) } + Spacer(Modifier.height(35.dp)) } ConversationList(R.string.recent_conversations, recentTiles, onTileClicked) @@ -160,33 +161,30 @@ private fun PeopleScreenWithConversations( } } -private fun LazyListScope.ConversationList( +@Composable +private fun ConversationList( @StringRes headerTextResource: Int, tiles: List<PeopleTileViewModel>, onTileClicked: (PeopleTileViewModel) -> Unit ) { - item { - Text( - stringResource(headerTextResource), - Modifier.padding(start = 16.dp), - style = MaterialTheme.typography.labelLarge, - color = LocalAndroidColorScheme.current.deprecated.colorAccentPrimaryVariant, - ) - - Spacer(Modifier.height(10.dp)) - } + Text( + stringResource(headerTextResource), + Modifier.padding(start = 16.dp), + style = MaterialTheme.typography.labelLarge, + color = LocalAndroidColorScheme.current.deprecated.colorAccentPrimaryVariant, + ) + + Spacer(Modifier.height(10.dp)) tiles.forEachIndexed { index, tile -> if (index > 0) { - item { - Divider( - color = LocalAndroidColorScheme.current.deprecated.colorBackground, - thickness = 2.dp, - ) - } + Divider( + color = LocalAndroidColorScheme.current.deprecated.colorBackground, + thickness = 2.dp, + ) } - item(tile.key.toString()) { + key(tile.key.toString()) { Tile( tile, onTileClicked, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 30b80ca7fc1e..130395a38512 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -27,25 +27,28 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow /** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */ -class QuickSettingsScene( +@SysUISingleton +class QuickSettingsScene +@Inject +constructor( private val viewModel: QuickSettingsSceneViewModel, ) : ComposableScene { override val key = SceneKey.QuickSettings - override fun destinationScenes( - containerName: String, - ): StateFlow<Map<UserAction, SceneModel>> = + override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> = MutableStateFlow<Map<UserAction, SceneModel>>( mapOf( UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade), @@ -55,7 +58,6 @@ class QuickSettingsScene( @Composable override fun Content( - containerName: String, modifier: Modifier, ) { QuickSettingsScene( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt index 6f3363e940e5..a21366695f66 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt @@ -22,5 +22,5 @@ import com.android.systemui.scene.shared.model.Scene /** Compose-capable extension of [Scene]. */ interface ComposableScene : Scene { - @Composable fun Content(containerName: String, modifier: Modifier) + @Composable fun Content(modifier: Modifier) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index 0a4da1d6ba1e..007055221691 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -23,10 +23,12 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -35,12 +37,11 @@ import kotlinx.coroutines.flow.asStateFlow * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any * content from the scene framework. */ -class GoneScene : ComposableScene { +@SysUISingleton +class GoneScene @Inject constructor() : ComposableScene { override val key = SceneKey.Gone - override fun destinationScenes( - containerName: String, - ): StateFlow<Map<UserAction, SceneModel>> = + override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> = MutableStateFlow<Map<UserAction, SceneModel>>( mapOf( UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade), @@ -50,7 +51,6 @@ class GoneScene : ComposableScene { @Composable override fun Content( - containerName: String, modifier: Modifier, ) { /* diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 32986649388d..49e2bf97b3f0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -75,7 +75,6 @@ fun SceneContainer( if (key == currentSceneKey) { Scene( scene = composableScene, - containerName = viewModel.containerName, onSceneChanged = viewModel::setCurrentScene, modifier = Modifier.fillMaxSize(), ) @@ -88,12 +87,10 @@ fun SceneContainer( @Composable private fun Scene( scene: ComposableScene, - containerName: String, onSceneChanged: (SceneModel) -> Unit, modifier: Modifier = Modifier, ) { - val destinationScenes: Map<UserAction, SceneModel> by - scene.destinationScenes(containerName).collectAsState() + val destinationScenes: Map<UserAction, SceneModel> by scene.destinationScenes().collectAsState() val swipeLeftDestinationScene = destinationScenes[UserAction.Swipe(Direction.LEFT)] val swipeUpDestinationScene = destinationScenes[UserAction.Swipe(Direction.UP)] val swipeRightDestinationScene = destinationScenes[UserAction.Swipe(Direction.RIGHT)] @@ -107,7 +104,6 @@ private fun Scene( modifier = Modifier.align(Alignment.Center), ) { scene.Content( - containerName = containerName, modifier = Modifier, ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 27358f53aaf2..b73e0b26f208 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.qs.footer.ui.compose.QuickSettings @@ -35,6 +36,7 @@ import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -42,15 +44,16 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */ -class ShadeScene( +@SysUISingleton +class ShadeScene +@Inject +constructor( @Application private val applicationScope: CoroutineScope, private val viewModel: ShadeSceneViewModel, ) : ComposableScene { override val key = SceneKey.Shade - override fun destinationScenes( - containerName: String, - ): StateFlow<Map<UserAction, SceneModel>> = + override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> = viewModel.upDestinationSceneKey .map { sceneKey -> destinationScenes(up = sceneKey) } .stateIn( @@ -61,7 +64,6 @@ class ShadeScene( @Composable override fun Content( - containerName: String, modifier: Modifier, ) = ShadeScene(viewModel, modifier) 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 d43276c00f87..46f5971405df 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 @@ -190,6 +190,10 @@ object CustomizationProviderContract { /** Flag denoting transit clock are enabled in wallpaper picker. */ const val FLAG_NAME_PAGE_TRANSITIONS = "wallpaper_picker_page_transitions" + /** Flag denoting whether preview loading animation is enabled. */ + const val FLAG_NAME_WALLPAPER_PICKER_PREVIEW_ANIMATION = + "wallpaper_picker_preview_animation" + object Columns { /** String. Unique ID for the flag. */ const val NAME = "name" diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml index e4749381243a..5404cfad25c5 100644 --- a/packages/SystemUI/res/layout/media_projection_app_selector.xml +++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.internal.widget.ResolverDrawerLayout +<com.android.intentresolver.widget.ResolverDrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:layout_width="match_parent" @@ -84,7 +84,7 @@ android:id="@*android:id/tabcontent" android:layout_width="match_parent" android:layout_height="wrap_content"> - <com.android.internal.app.ResolverViewPager + <com.android.intentresolver.ResolverViewPager android:id="@*android:id/profile_pager" android:layout_width="match_parent" android:layout_height="wrap_content"/> @@ -92,4 +92,4 @@ </LinearLayout> </TabHost> -</com.android.internal.widget.ResolverDrawerLayout> +</com.android.intentresolver.widget.ResolverDrawerLayout> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 16eb21d8c4db..4d196aa5466d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -421,6 +421,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS * Refresh clock. Called in response to TIME_TICK broadcasts. */ void refresh() { + mLogBuffer.log(TAG, LogLevel.INFO, "refresh"); if (mSmartspaceController != null) { mSmartspaceController.requestSmartspaceUpdate(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 42a4e7202c82..dc1ddc77dd6c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -1197,6 +1197,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout { }); mPopup.show(); }); + + mUserSwitcherViewGroup.setAlpha(0f); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index bc24249b23c9..3b09910fbe88 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -81,7 +81,6 @@ import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.scene.domain.interactor.SceneInteractor; -import com.android.systemui.scene.shared.model.SceneContainerNames; import com.android.systemui.scene.shared.model.SceneKey; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -476,7 +475,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) { // When the scene framework transitions from bouncer to gone, we dismiss the keyguard. mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow( - mSceneInteractor.get().sceneTransitions(SceneContainerNames.SYSTEM_UI_DEFAULT), + mSceneInteractor.get().getTransitions(), sceneTransitionModel -> { if (sceneTransitionModel != null && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index a04d13b93ddd..8e92941c79fa 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -20,6 +20,7 @@ import static androidx.constraintlayout.widget.ConstraintSet.END; import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.animation.Animator; import android.animation.ValueAnimator; @@ -51,6 +52,10 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.shared.model.ScreenModel; +import com.android.systemui.keyguard.shared.model.ScreenState; import com.android.systemui.plugins.ClockController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -62,6 +67,9 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; +import kotlin.coroutines.CoroutineContext; +import kotlin.coroutines.EmptyCoroutineContext; + import java.io.PrintWriter; import javax.inject.Inject; @@ -91,6 +99,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final FeatureFlags mFeatureFlags; private final InteractionJankMonitor mInteractionJankMonitor; private final Rect mClipBounds = new Rect(); + private final KeyguardInteractor mKeyguardInteractor; private Boolean mStatusViewCentered = true; @@ -122,6 +131,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV KeyguardLogger logger, FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, + KeyguardInteractor keyguardInteractor, DumpManager dumpManager) { super(keyguardStatusView); mKeyguardSliceViewController = keyguardSliceViewController; @@ -134,12 +144,34 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mInteractionJankMonitor = interactionJankMonitor; mFeatureFlags = featureFlags; mDumpManager = dumpManager; + mKeyguardInteractor = keyguardInteractor; } @Override public void onInit() { mKeyguardClockSwitchController.init(); mDumpManager.registerDumpable(this); + if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + startCoroutines(EmptyCoroutineContext.INSTANCE); + } + } + + void startCoroutines(CoroutineContext context) { + collectFlow(mView, mKeyguardInteractor.getDozeTimeTick(), + (Long millis) -> { + dozeTimeTick(); + }, context); + + collectFlow(mView, mKeyguardInteractor.getScreenModel(), + (ScreenModel model) -> { + if (model.getState() == ScreenState.SCREEN_TURNING_ON) { + dozeTimeTick(); + } + }, context); + } + + public KeyguardStatusView getView() { + return mView; } @Override @@ -308,6 +340,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { @Override public void onTimeChanged() { + Slog.v(TAG, "onTimeChanged"); refreshTime(); } diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java index f00615bc0fe6..7c377d2841e3 100644 --- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java @@ -19,9 +19,6 @@ package com.android.systemui; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_EXPAND; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; import android.content.Context; import android.util.FloatProperty; import android.util.Log; @@ -34,6 +31,11 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; +import androidx.annotation.NonNull; +import androidx.core.animation.Animator; +import androidx.core.animation.AnimatorListenerAdapter; +import androidx.core.animation.ObjectAnimator; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -616,7 +618,7 @@ public class ExpandHelper implements Gefingerpoken { public boolean mCancelled; @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(@NonNull Animator animation) { if (!mCancelled) { mCallback.setUserExpandedChild(scaledView, expand); if (!mExpanding) { @@ -633,7 +635,7 @@ public class ExpandHelper implements Gefingerpoken { } @Override - public void onAnimationCancel(Animator animation) { + public void onAnimationCancel(@NonNull Animator animation) { mCancelled = true; } }); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt index e60d4e10f957..0c7d56f46530 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt @@ -100,6 +100,9 @@ class AuthBiometricFaceIconController( ) } else if (newState == STATE_ERROR && oldState != STATE_ERROR) { animateIconOnce(R.drawable.face_dialog_dark_to_error) + iconView.contentDescription = context.getString( + R.string.keyguard_face_failed + ) } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) { animateIconOnce(R.drawable.face_dialog_dark_to_checkmark) iconView.contentDescription = context.getString( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt index a1b15f4498a7..d82f458cbde2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt @@ -122,9 +122,7 @@ open class AuthBiometricFingerprintIconController( if (shouldAnimateIconViewForTransition(lastState, newState)) { iconView.playAnimation() } - if (isSideFps) { - LottieColorUtils.applyDynamicColors(context, iconView) - } + LottieColorUtils.applyDynamicColors(context, iconView) } override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index ebff0b05a52d..39a45f7f60a2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -26,6 +26,7 @@ import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROL import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import android.content.BroadcastReceiver; import android.content.Context; @@ -50,6 +51,7 @@ import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.util.Log; +import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -234,6 +236,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { public static final VibrationEffect EFFECT_CLICK = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + public static final int LONG_PRESS = HapticFeedbackConstants.LONG_PRESS; + private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurnedOn() { @@ -926,12 +930,24 @@ public class UdfpsController implements DozeReceiver, Dumpable { @VisibleForTesting public void playStartHaptic() { if (mAccessibilityManager.isTouchExplorationEnabled()) { - mVibrator.vibrate( - Process.myUid(), - mContext.getOpPackageName(), - EFFECT_CLICK, - "udfps-onStart-click", - UDFPS_VIBRATION_ATTRIBUTES); + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + if (mOverlay != null && mOverlay.getOverlayView() != null) { + mVibrator.performHapticFeedback( + mOverlay.getOverlayView(), + HapticFeedbackConstants.CONTEXT_CLICK + ); + } else { + Log.e(TAG, "No haptics played. Could not obtain overlay view to perform" + + "vibration. Either the controller overlay is null or has no view"); + } + } else { + mVibrator.vibrate( + Process.myUid(), + mContext.getOpPackageName(), + EFFECT_CLICK, + "udfps-onStart-click", + UDFPS_VIBRATION_ATTRIBUTES); + } } } @@ -1024,12 +1040,24 @@ public class UdfpsController implements DozeReceiver, Dumpable { mKeyguardViewManager.showPrimaryBouncer(true); // play the same haptic as the LockIconViewController longpress - mVibrator.vibrate( - Process.myUid(), - mContext.getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "aod-lock-icon-longpress", - LOCK_ICON_VIBRATION_ATTRIBUTES); + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + if (mOverlay != null && mOverlay.getOverlayView() != null) { + mVibrator.performHapticFeedback( + mOverlay.getOverlayView(), + UdfpsController.LONG_PRESS + ); + } else { + Log.e(TAG, "No haptics played. Could not obtain overlay view to perform" + + "vibration. Either the controller overlay is null or has no view"); + } + } else { + mVibrator.vibrate( + Process.myUid(), + mContext.getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "aod-lock-icon-longpress", + LOCK_ICON_VIBRATION_ATTRIBUTES); + } return; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 9bbf1ef04481..b68b92119836 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -26,6 +26,7 @@ import android.os.Bundle import android.text.method.ScrollingMovementMethod import android.util.Log import android.view.View +import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO import android.view.accessibility.AccessibilityManager import android.widget.Button import android.widget.TextView @@ -335,6 +336,13 @@ object BiometricViewBinder { // dismiss prompt when authenticated and confirmed launch { viewModel.isAuthenticated.collect { authState -> + // Disable background view for cancelling authentication once authenticated, + // and remove from talkback + if (authState.isAuthenticated) { + backgroundView.setOnClickListener(null) + backgroundView.importantForAccessibility = + IMPORTANT_FOR_ACCESSIBILITY_NO + } if (authState.isAuthenticatedAndConfirmed) { view.announceForAccessibility( view.resources.getString(R.string.biometric_dialog_authenticated) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 8e14237c0586..d8cf398b696b 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -24,6 +24,7 @@ import com.android.systemui.authentication.domain.interactor.AuthenticationInter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.bouncer.data.repository.BouncerRepository +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -31,9 +32,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.util.kotlin.pairwise -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -44,8 +43,9 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Encapsulates business logic and application state accessing use-cases. */ +@SysUISingleton class BouncerInteractor -@AssistedInject +@Inject constructor( @Application private val applicationScope: CoroutineScope, @Application private val applicationContext: Context, @@ -53,7 +53,6 @@ constructor( private val authenticationInteractor: AuthenticationInteractor, private val sceneInteractor: SceneInteractor, featureFlags: FeatureFlags, - @Assisted private val containerName: String, ) { /** The user-facing message to show in the bouncer. */ @@ -118,23 +117,19 @@ constructor( /** * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown. * - * @param containerName The name of the scene container to show the bouncer in. * @param message An optional message to show to the user in the bouncer. */ fun showOrUnlockDevice( - containerName: String, message: String? = null, ) { applicationScope.launch { if (authenticationInteractor.isAuthenticationRequired()) { repository.setMessage(message ?: promptMessage(getAuthenticationMethod())) sceneInteractor.setCurrentScene( - containerName = containerName, scene = SceneModel(SceneKey.Bouncer), ) } else { sceneInteractor.setCurrentScene( - containerName = containerName, scene = SceneModel(SceneKey.Gone), ) } @@ -180,7 +175,6 @@ constructor( if (isAuthenticated) { sceneInteractor.setCurrentScene( - containerName = containerName, scene = SceneModel(SceneKey.Gone), ) } else { @@ -228,11 +222,4 @@ constructor( else -> "" } } - - @AssistedFactory - interface Factory { - fun create( - containerName: String, - ): BouncerInteractor - } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt index 34e934bec003..d9ec5d0d1744 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt @@ -120,6 +120,8 @@ object KeyguardBouncerViewBinder { viewModel.isShowing.collect { isShowing -> view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE if (isShowing) { + // Reset security container because these views are not reinflated. + securityContainerController.reset() securityContainerController.reinflateViewFlipper { // Reset Security Container entirely. securityContainerController.onBouncerVisibilityChanged( diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index a4ef5cec6525..68e1a29bc609 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -22,13 +22,12 @@ import android.content.Context import com.android.systemui.R import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.util.kotlin.pairwise -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject import kotlin.math.ceil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,17 +44,15 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Holds UI state and handles user input on bouncer UIs. */ +@SysUISingleton class BouncerViewModel -@AssistedInject +@Inject constructor( @Application private val applicationContext: Context, @Application private val applicationScope: CoroutineScope, - interactorFactory: BouncerInteractor.Factory, + private val interactor: BouncerInteractor, featureFlags: FeatureFlags, - @Assisted containerName: String, ) { - private val interactor: BouncerInteractor = interactorFactory.create(containerName) - private val isInputEnabled: StateFlow<Boolean> = interactor.isThrottled .map { !it } @@ -222,11 +219,4 @@ constructor( */ val isUpdateAnimated: Boolean, ) - - @AssistedFactory - interface Factory { - fun create( - containerName: String, - ): BouncerViewModel - } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt index b387e4a66bd2..4c9dbe02fad8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -40,8 +40,6 @@ import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo import com.android.systemui.controls.ui.ControlsActivity import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker import java.util.concurrent.Executor import javax.inject.Inject @@ -50,7 +48,6 @@ import javax.inject.Inject * Activity for rearranging and removing controls for a given structure */ open class ControlsEditingActivity @Inject constructor( - featureFlags: FeatureFlags, @Main private val mainExecutor: Executor, private val controller: ControlsControllerImpl, private val userTracker: UserTracker, @@ -76,8 +73,6 @@ open class ControlsEditingActivity @Inject constructor( private var isFromFavoriting: Boolean = false - private val isNewFlowEnabled: Boolean = - featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS) private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback { private val startingUser = controller.currentUserId @@ -176,7 +171,7 @@ open class ControlsEditingActivity @Inject constructor( private fun bindButtons() { addControls = requireViewById<Button>(R.id.addControls).apply { isEnabled = true - visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE + visibility = View.VISIBLE setOnClickListener { if (saveButton.isEnabled) { // The user has made changes diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 59fa7f53fc17..23721c93508b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -33,7 +33,6 @@ import android.view.ViewStub import android.widget.Button import android.widget.FrameLayout import android.widget.TextView -import android.widget.Toast import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.activity.ComponentActivity @@ -41,24 +40,19 @@ import androidx.annotation.VisibleForTesting import androidx.viewpager2.widget.ViewPager2 import com.android.systemui.Prefs import com.android.systemui.R -import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.TooltipManager import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo import com.android.systemui.controls.ui.ControlsActivity import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker import java.text.Collator import java.util.concurrent.Executor import javax.inject.Inject open class ControlsFavoritingActivity @Inject constructor( - featureFlags: FeatureFlags, @Main private val executor: Executor, private val controller: ControlsControllerImpl, - private val listingController: ControlsListingController, private val userTracker: UserTracker, ) : ComponentActivity() { @@ -92,7 +86,6 @@ open class ControlsFavoritingActivity @Inject constructor( private lateinit var pageIndicator: ManagementPageIndicator private var mTooltipManager: TooltipManager? = null private lateinit var doneButton: View - private lateinit var otherAppsButton: View private lateinit var rearrangeButton: Button private var listOfStructures = emptyList<StructureContainer>() @@ -104,8 +97,6 @@ open class ControlsFavoritingActivity @Inject constructor( get() = openSource == EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR private val fromEditing: Boolean get() = openSource == EXTRA_SOURCE_VALUE_FROM_EDITING - private val isNewFlowEnabled: Boolean = - featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS) private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback { private val startingUser = controller.currentUserId @@ -124,20 +115,6 @@ open class ControlsFavoritingActivity @Inject constructor( onBackPressed() } - private val listingCallback = object : ControlsListingController.ControlsListingCallback { - - override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { - if (serviceInfos.size > 1) { - val newVisibility = if (isNewFlowEnabled) View.GONE else View.VISIBLE - if (otherAppsButton.visibility != newVisibility) { - otherAppsButton.post { - otherAppsButton.visibility = newVisibility - } - } - } - } - } - override fun onBackPressed() { if (fromEditing) { animateExitAndFinish() @@ -342,7 +319,7 @@ open class ControlsFavoritingActivity @Inject constructor( getString(R.string.controls_favorite_rearrange_button) } isEnabled = false - visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE + visibility = View.VISIBLE setOnClickListener { if (component == null) return@setOnClickListener saveFavorites() @@ -361,24 +338,6 @@ open class ControlsFavoritingActivity @Inject constructor( ) } } - otherAppsButton = requireViewById<Button>(R.id.other_apps).apply { - setOnClickListener { - if (doneButton.isEnabled) { - // The user has made changes - Toast.makeText( - applicationContext, - R.string.controls_favorite_toast_no_changes, - Toast.LENGTH_SHORT - ).show() - } - startActivity( - Intent(context, ControlsProviderSelectorActivity::class.java), - ActivityOptions - .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle() - ) - animateExitAndFinish() - } - } doneButton = requireViewById<Button>(R.id.done).apply { isEnabled = false @@ -415,7 +374,6 @@ open class ControlsFavoritingActivity @Inject constructor( override fun onStart() { super.onStart() - listingController.addCallback(listingCallback) userTracker.addCallback(userTrackerCallback, executor) if (DEBUG) { @@ -440,7 +398,6 @@ open class ControlsFavoritingActivity @Inject constructor( override fun onStop() { super.onStop() - listingController.removeCallback(listingCallback) userTracker.removeCallback(userTrackerCallback) if (DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt index 1eba66765ce3..83bec664876d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt @@ -124,8 +124,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( } private fun updateServices(newServices: List<ControlsServiceInfo>) { - if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && - activityTaskManagerProxy.supportsMultiWindow(context)) { + if (activityTaskManagerProxy.supportsMultiWindow(context)) { val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED) newServices.forEach { it.resolvePanelActivity(allowAllApps) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index 37138114c740..a7e9efd8ab03 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -34,12 +34,9 @@ import android.view.HapticFeedbackConstants import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.controls.ControlsMetricsLogger -import com.android.systemui.controls.settings.ControlsSettingsDialogManager import com.android.systemui.controls.settings.ControlsSettingsRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController @@ -60,8 +57,6 @@ class ControlActionCoordinatorImpl @Inject constructor( private val controlsMetricsLogger: ControlsMetricsLogger, private val vibrator: VibratorHelper, private val controlsSettingsRepository: ControlsSettingsRepository, - private val controlsSettingsDialogManager: ControlsSettingsDialogManager, - private val featureFlags: FeatureFlags, ) : ControlActionCoordinator { private var dialog: Dialog? = null private var pendingAction: Action? = null @@ -77,9 +72,6 @@ class ControlActionCoordinatorImpl @Inject constructor( } override fun closeDialogs() { - if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) { - controlsSettingsDialogManager.closeDialog() - } val isActivityFinishing = (activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed } if (isActivityFinishing == true) { @@ -169,7 +161,6 @@ class ControlActionCoordinatorImpl @Inject constructor( override fun runPendingAction(controlId: String) { if (isLocked) return if (pendingAction?.controlId == controlId) { - showSettingsDialogIfNeeded(pendingAction!!) pendingAction?.invoke() pendingAction = null } @@ -208,7 +199,6 @@ class ControlActionCoordinatorImpl @Inject constructor( true }, { pendingAction = null }, true /* afterKeyguardGone */) } else { - showSettingsDialogIfNeeded(action) action.invoke() } } @@ -243,15 +233,6 @@ class ControlActionCoordinatorImpl @Inject constructor( } } - private fun showSettingsDialogIfNeeded(action: Action) { - if (action.authIsRequired) { - return - } - if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) { - controlsSettingsDialogManager.maybeShowDialog(activityContext) {} - } - } - @VisibleForTesting fun createAction( controlId: String, diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt index 557dcf4accc7..8341964e1533 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -36,7 +36,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.management.ControlsAnimations import com.android.systemui.controls.settings.ControlsSettingsDialogManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject @@ -66,9 +65,7 @@ open class ControlsActivity @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lastConfiguration.setTo(resources.configuration) - if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) { - window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) - } + window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) setContentView(R.layout.controls_fullscreen) @@ -77,7 +74,7 @@ open class ControlsActivity @Inject constructor( requireViewById(R.id.control_detail_root), window, intent, - !featureFlags.isEnabled(Flags.USE_APP_PANELS) + false ) ) @@ -114,7 +111,7 @@ open class ControlsActivity @Inject constructor( parent = requireViewById(R.id.control_detail_root) parent.alpha = 0f - if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && !keyguardStateController.isUnlocked) { + if (!keyguardStateController.isUnlocked) { controlsSettingsDialogManager.maybeShowDialog(this) { uiController.show(parent, { finishOrReturnToDream() }, this) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 56c898d1e9b4..89204eb90857 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -506,30 +506,22 @@ class ControlsUiControllerImpl @Inject constructor ( val isPanel = selectedItem is SelectedItem.PanelItem val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure ?: EMPTY_STRUCTURE - val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS) val items = buildList { add(OverflowMenuAdapter.MenuItem( context.getText(R.string.controls_open_app), OPEN_APP_ID )) - if (newFlows || isPanel) { - if (extraApps) { - add(OverflowMenuAdapter.MenuItem( - context.getText(R.string.controls_menu_add_another_app), - ADD_APP_ID - )) - } - if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) { - add(OverflowMenuAdapter.MenuItem( - context.getText(R.string.controls_menu_remove), - REMOVE_APP_ID, - )) - } - } else { + if (extraApps) { + add(OverflowMenuAdapter.MenuItem( + context.getText(R.string.controls_menu_add_another_app), + ADD_APP_ID + )) + } + if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) { add(OverflowMenuAdapter.MenuItem( - context.getText(R.string.controls_menu_add), - ADD_CONTROLS_ID + context.getText(R.string.controls_menu_remove), + REMOVE_APP_ID, )) } if (!isPanel) { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 553405f2c944..5577cbcb0dd7 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -17,6 +17,7 @@ package com.android.systemui.dreams; import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE; +import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER; import android.content.ComponentName; import android.content.Context; @@ -161,7 +162,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ DreamOverlayStateController stateController, KeyguardUpdateMonitor keyguardUpdateMonitor, UiEventLogger uiEventLogger, - TouchInsetManager touchInsetManager, + @Named(DREAM_TOUCH_INSET_MANAGER) TouchInsetManager touchInsetManager, @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT) ComponentName lowLightDreamComponent, DreamOverlayCallbackController dreamOverlayCallbackController, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index c61b47758ab7..4bafe325cda0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -31,11 +31,13 @@ import com.android.systemui.dreams.DreamOverlayNotificationCountProvider; import com.android.systemui.dreams.DreamOverlayService; import com.android.systemui.dreams.complication.dagger.ComplicationComponent; import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule; +import com.android.systemui.touch.TouchInsetManager; import dagger.Module; import dagger.Provides; import java.util.Optional; +import java.util.concurrent.Executor; import javax.inject.Named; @@ -55,7 +57,7 @@ public interface DreamModule { String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user"; String DREAM_OVERLAY_SERVICE_COMPONENT = "dream_overlay_service_component"; String DREAM_OVERLAY_ENABLED = "dream_overlay_enabled"; - + String DREAM_TOUCH_INSET_MANAGER = "dream_touch_inset_manager"; String DREAM_SUPPORTED = "dream_supported"; String DREAM_OVERLAY_WINDOW_TITLE = "dream_overlay_window_title"; @@ -69,6 +71,15 @@ public interface DreamModule { } /** + * Provides a touch inset manager for dreams. + */ + @Provides + @Named(DREAM_TOUCH_INSET_MANAGER) + static TouchInsetManager providesTouchInsetManager(@Main Executor executor) { + return new TouchInsetManager(executor); + } + + /** * Provides whether dream overlay is enabled. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index b04d9705f857..775992be5a55 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -62,11 +62,12 @@ object Flags { val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply") /** - * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to - * enable it on release builds. + * This flag controls whether we register a listener for StatsD notification memory reports. + * For statsd to actually call the listener however, a server-side toggle needs to be + * enabled as well. */ val NOTIFICATION_MEMORY_LOGGING_ENABLED = - unreleasedFlag(119, "notification_memory_logging_enabled") + releasedFlag(119, "notification_memory_logging_enabled") // TODO(b/260335638): Tracking Bug @JvmField @@ -239,7 +240,7 @@ object Flags { /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */ // TODO(b/279794160): Tracking bug. - @JvmField val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer", teamfood = true) + @JvmField val DELAY_BOUNCER = releasedFlag(235, "delay_bouncer") /** Keyguard Migration */ @@ -282,6 +283,15 @@ object Flags { // TODO(b/291767565): Tracking bug. @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag(243, "migrate_keyguard_status_view") + /** Enables preview loading animation in the wallpaper picker. */ + // TODO(b/274443705): Tracking Bug + @JvmField + val WALLPAPER_PICKER_PREVIEW_ANIMATION = + unreleasedFlag( + 244, + "wallpaper_picker_preview_animation" + ) + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -616,13 +626,8 @@ object Flags { @JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag") // 2000 - device controls - @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels") - @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag(2001, "app_panels_all_apps_allowed") - @JvmField - val CONTROLS_MANAGEMENT_NEW_FLOWS = releasedFlag(2002, "controls_management_new_flows") - // Enables removing app from Home control panel as a part of a new flow // TODO(b/269132640): Tracking Bug @JvmField @@ -734,4 +739,12 @@ object Flags { // TODO(b/290213663): Tracking Bug @JvmField val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag(3100, "oneway_haptics_api_migration") + + /** Enable the Compose implementation of the PeopleSpaceActivity. */ + @JvmField + val COMPOSE_PEOPLE_SPACE = unreleasedFlag(293570761, "compose_people_space") + + /** Enable the Compose implementation of the Quick Settings footer actions. */ + @JvmField + val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag(293569320, "compose_qs_footer_actions") } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 5d7ea1cc5819..e2929aec0295 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -100,6 +100,7 @@ import android.view.WindowManagerPolicyConstants; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -173,6 +174,8 @@ import com.android.wm.shell.keyguard.KeyguardTransitions; import dagger.Lazy; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; @@ -257,6 +260,22 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private static final int SYSTEM_READY = 18; private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19; + /** Enum for reasons behind updating wakeAndUnlock state. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + value = { + WakeAndUnlockUpdateReason.HIDE, + WakeAndUnlockUpdateReason.SHOW, + WakeAndUnlockUpdateReason.FULFILL, + WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK, + }) + @interface WakeAndUnlockUpdateReason { + int HIDE = 0; + int SHOW = 1; + int FULFILL = 2; + int WAKE_AND_UNLOCK = 3; + } + /** * The default amount of time we stay awake (used for all key input) */ @@ -808,7 +827,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // dreaming. It's time to wake up. if (mUnlockingAndWakingFromDream) { Log.d(TAG, "waking from dream after unlock"); - mUnlockingAndWakingFromDream = false; + setUnlockAndWakeFromDream(false, WakeAndUnlockUpdateReason.FULFILL); if (mKeyguardStateController.isShowing()) { Log.d(TAG, "keyguard showing after keyguardGone, dismiss"); @@ -2685,7 +2704,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mKeyguardExitAnimationRunner = null; mWakeAndUnlocking = false; - mUnlockingAndWakingFromDream = false; + setUnlockAndWakeFromDream(false, WakeAndUnlockUpdateReason.SHOW); setPendingLock(false); // Force if we we're showing in the middle of hiding, to ensure we end up in the correct @@ -2791,6 +2810,51 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, tryKeyguardDone(); }; + private void setUnlockAndWakeFromDream(boolean updatedValue, + @WakeAndUnlockUpdateReason int reason) { + if (updatedValue == mUnlockingAndWakingFromDream) { + return; + } + + final String reasonDescription; + + switch(reason) { + case WakeAndUnlockUpdateReason.FULFILL: + reasonDescription = "fulfilling existing request"; + break; + case WakeAndUnlockUpdateReason.HIDE: + reasonDescription = "hiding keyguard"; + break; + case WakeAndUnlockUpdateReason.SHOW: + reasonDescription = "showing keyguard"; + break; + case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK: + reasonDescription = "waking to unlock"; + break; + default: + throw new IllegalStateException("Unexpected value: " + reason); + } + + final boolean unsetUnfulfilled = !updatedValue + && reason != WakeAndUnlockUpdateReason.FULFILL; + + mUnlockingAndWakingFromDream = updatedValue; + + final String description; + + if (unsetUnfulfilled) { + description = "Interrupting request to wake and unlock"; + } else if (mUnlockingAndWakingFromDream) { + description = "Initiating request to wake and unlock"; + } else { + description = "Fulfilling request to wake and unlock"; + } + + Log.d(TAG, String.format( + "Updating waking and unlocking request to %b. description:[%s]. reason:[%s]", + mUnlockingAndWakingFromDream, description, reasonDescription)); + } + /** * Handle message sent by {@link #hideLocked()} * @see #HIDE @@ -2810,8 +2874,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mHiding = true; - mUnlockingAndWakingFromDream = mStatusBarStateController.isDreaming() - && !mStatusBarStateController.isDozing(); + // If waking and unlocking, waking from dream has been set properly. + if (!mWakeAndUnlocking) { + setUnlockAndWakeFromDream(mStatusBarStateController.isDreaming() + && mPM.isInteractive(), WakeAndUnlockUpdateReason.HIDE); + } if ((mShowing && !mOccluded) || mUnlockingAndWakingFromDream) { if (mUnlockingAndWakingFromDream) { @@ -3218,7 +3285,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, flags |= StatusBarManager.DISABLE_RECENT; } - if (mPowerGestureIntercepted && mOccluded && isSecure()) { + if (mPowerGestureIntercepted && mOccluded && isSecure() + && mUpdateMonitor.isFaceEnrolled()) { flags |= StatusBarManager.DISABLE_RECENT; } @@ -3313,9 +3381,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } - public void onWakeAndUnlocking() { + /** + * Informs the keyguard view mediator that the device is waking and unlocking. + * @param fromDream Whether waking and unlocking is happening over an interactive dream. + */ + public void onWakeAndUnlocking(boolean fromDream) { Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking"); mWakeAndUnlocking = true; + setUnlockAndWakeFromDream(fromDream, WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK); mKeyguardViewControllerLazy.get().notifyKeyguardAuthenticated(/* primaryAuth */ false); userActivity(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index 6fd3e21b25a4..30f8f3edfa8f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -110,6 +110,18 @@ interface DeviceEntryFaceAuthRepository { fun lockoutFaceAuth() /** + * Cancel current face authentication and prevent it from running until [resumeFaceAuth] is + * invoked. + */ + fun pauseFaceAuth() + + /** + * Allow face auth paused using [pauseFaceAuth] to run again. The next invocation to + * [authenticate] will run as long as other gating conditions don't stop it from running. + */ + fun resumeFaceAuth() + + /** * Trigger face authentication. * * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be @@ -186,6 +198,15 @@ constructor( override val isAuthRunning: StateFlow<Boolean> get() = _isAuthRunning + private val faceAuthPaused = MutableStateFlow(false) + override fun pauseFaceAuth() { + faceAuthPaused.value = true + } + + override fun resumeFaceAuth() { + faceAuthPaused.value = false + } + private val keyguardSessionId: InstanceId? get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD) @@ -329,11 +350,7 @@ constructor( "isFaceAuthenticationEnabled", tableLogBuffer ), - logAndObserve( - userRepository.userSwitchingInProgress.isFalse(), - "userSwitchingNotInProgress", - tableLogBuffer - ), + logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer), logAndObserve( keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway", @@ -454,7 +471,6 @@ constructor( } private fun handleFaceCancellationError() { - cancelNotReceivedHandlerJob?.cancel() applicationScope.launch { faceAuthRequestedWhileCancellation?.let { faceAuthLogger.launchingQueuedFaceAuthRequest(it) @@ -483,6 +499,7 @@ constructor( } private fun onFaceAuthRequestCompleted() { + cancelNotReceivedHandlerJob?.cancel() cancellationInProgress = false _isAuthRunning.value = false authCancellationSignal = null diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 9ee990243b96..f1b344199516 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -47,16 +47,15 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnl import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn @@ -177,7 +176,7 @@ interface KeyguardRepository { val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState> /** Receive an event for doze time tick */ - val dozeTimeTick: Flow<Unit> + val dozeTimeTick: Flow<Long> /** * Returns `true` if the keyguard is showing; `false` otherwise. @@ -248,6 +247,7 @@ constructor( private val dreamOverlayCallbackController: DreamOverlayCallbackController, @Main private val mainDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, + private val systemClock: SystemClock, ) : KeyguardRepository { private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) override val animateBottomAreaDozingTransitions = @@ -398,11 +398,11 @@ constructor( _isDozing.value = isDozing } - private val _dozeTimeTick = MutableSharedFlow<Unit>() - override val dozeTimeTick = _dozeTimeTick.asSharedFlow() + private val _dozeTimeTick = MutableStateFlow<Long>(0) + override val dozeTimeTick = _dozeTimeTick.asStateFlow() override fun dozeTimeTick() { - _dozeTimeTick.tryEmit(Unit) + _dozeTimeTick.value = systemClock.uptimeMillis() } private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt index 5ef9a9e0482c..e4e6a6dae6b0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt @@ -56,6 +56,9 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA get() = emptyFlow() override fun lockoutFaceAuth() = Unit + override fun pauseFaceAuth() = Unit + + override fun resumeFaceAuth() = Unit /** * Trigger face authentication. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt index 252982fd019f..ac936b10fe43 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt @@ -24,14 +24,11 @@ import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn @@ -46,24 +43,17 @@ constructor( private val burnInHelperWrapper: BurnInHelperWrapper, @Application private val scope: CoroutineScope, private val configurationRepository: ConfigurationRepository, - private val systemClock: SystemClock, + private val keyguardInteractor: KeyguardInteractor, ) { - private val _dozeTimeTick = MutableStateFlow<Long>(0) - val dozeTimeTick: StateFlow<Long> = _dozeTimeTick.asStateFlow() - val udfpsBurnInXOffset: StateFlow<Int> = burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true) val udfpsBurnInYOffset: StateFlow<Int> = burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false) val udfpsBurnInProgress: StateFlow<Float> = - dozeTimeTick + keyguardInteractor.dozeTimeTick .mapLatest { burnInHelperWrapper.burnInProgressOffset() } .stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset()) - fun dozeTimeTick() { - _dozeTimeTick.value = systemClock.uptimeMillis() - } - /** * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the * max burn-in offset on any configuration changes. If the max burn-in offset is specified in @@ -77,7 +67,9 @@ constructor( .flatMapLatest { val maxBurnInOffsetPixels = context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId) - dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis) } + keyguardInteractor.dozeTimeTick.mapLatest { + calculateOffset(maxBurnInOffsetPixels, isXAxis) + } } .stateIn( scope, @@ -102,7 +94,9 @@ constructor( .flatMapLatest { scale -> val maxBurnInOffsetPixels = context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId) - dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis, scale) } + keyguardInteractor.dozeTimeTick.mapLatest { + calculateOffset(maxBurnInOffsetPixels, isXAxis, scale) + } } .stateIn( scope, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt new file mode 100644 index 000000000000..dac6ef5e6082 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt @@ -0,0 +1,169 @@ +/* + * 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.domain.interactor + +import android.animation.ValueAnimator +import com.android.app.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.DozeStateModel +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.util.kotlin.Utils.Companion.toTriple +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +@SysUISingleton +class FromDreamingLockscreenHostedTransitionInteractor +@Inject +constructor( + override val transitionRepository: KeyguardTransitionRepository, + override val transitionInteractor: KeyguardTransitionInteractor, + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, +) : + TransitionInteractor( + fromState = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + ) { + + override fun start() { + listenForDreamingLockscreenHostedToLockscreen() + listenForDreamingLockscreenHostedToGone() + listenForDreamingLockscreenHostedToDozing() + listenForDreamingLockscreenHostedToOccluded() + listenForDreamingLockscreenHostedToPrimaryBouncer() + } + + private fun listenForDreamingLockscreenHostedToLockscreen() { + scope.launch { + keyguardInteractor.isActiveDreamLockscreenHosted + // Add a slight delay to prevent transitioning to lockscreen from happening too soon + // as dozing can arrive in a slight gap after the lockscreen hosted dream stops. + .onEach { delay(50) } + .sample( + combine( + keyguardInteractor.dozeTransitionModel, + transitionInteractor.startedKeyguardTransitionStep, + ::Pair + ), + ::toTriple + ) + .collect { + (isActiveDreamLockscreenHosted, dozeTransitionModel, lastStartedTransition) -> + if ( + !isActiveDreamLockscreenHosted && + DozeStateModel.isDozeOff(dozeTransitionModel.to) && + lastStartedTransition.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) { + startTransitionTo(KeyguardState.LOCKSCREEN) + } + } + } + } + + private fun listenForDreamingLockscreenHostedToOccluded() { + scope.launch { + keyguardInteractor.isActiveDreamLockscreenHosted + .sample( + combine( + keyguardInteractor.isKeyguardOccluded, + transitionInteractor.startedKeyguardTransitionStep, + ::Pair, + ), + ::toTriple + ) + .collect { (isActiveDreamLockscreenHosted, isOccluded, lastStartedTransition) -> + if ( + isOccluded && + !isActiveDreamLockscreenHosted && + lastStartedTransition.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) { + startTransitionTo(KeyguardState.OCCLUDED) + } + } + } + } + + private fun listenForDreamingLockscreenHostedToPrimaryBouncer() { + scope.launch { + keyguardInteractor.primaryBouncerShowing + .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) + .collect { (isBouncerShowing, lastStartedTransitionStep) -> + if ( + isBouncerShowing && + lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) { + startTransitionTo(KeyguardState.PRIMARY_BOUNCER) + } + } + } + } + + private fun listenForDreamingLockscreenHostedToGone() { + scope.launch { + keyguardInteractor.biometricUnlockState + .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) + .collect { (biometricUnlockState, lastStartedTransitionStep) -> + if ( + lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED && + biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM + ) { + startTransitionTo(KeyguardState.GONE) + } + } + } + } + + private fun listenForDreamingLockscreenHostedToDozing() { + scope.launch { + combine( + keyguardInteractor.dozeTransitionModel, + transitionInteractor.startedKeyguardTransitionStep, + ::Pair + ) + .collect { (dozeTransitionModel, lastStartedTransitionStep) -> + if ( + dozeTransitionModel.to == DozeStateModel.DOZE && + lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) { + startTransitionTo(KeyguardState.DOZING) + } + } + } + } + + override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { + return ValueAnimator().apply { + interpolator = Interpolators.LINEAR + duration = + if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds + else DEFAULT_DURATION.inWholeMilliseconds + } + } + + companion object { + private val DEFAULT_DURATION = 500.milliseconds + val TO_LOCKSCREEN_DURATION = 1167.milliseconds + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 98d7434166b4..954ff6f23561 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -94,11 +94,16 @@ constructor( private fun listenForDreamingToGone() { scope.launch { - keyguardInteractor.biometricUnlockState.collect { biometricUnlockState -> - if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) { - startTransitionTo(KeyguardState.GONE) + keyguardInteractor.biometricUnlockState + .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) + .collect { (biometricUnlockState, lastStartedTransitionStep) -> + if ( + lastStartedTransitionStep.to == KeyguardState.DREAMING && + biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM + ) { + startTransitionTo(KeyguardState.GONE) + } } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index f82633fbba27..2b08b3d04e19 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -48,6 +48,7 @@ constructor( listenForGoneToAodOrDozing() listenForGoneToDreaming() listenForGoneToLockscreen() + listenForGoneToDreamingLockscreenHosted() } // Primarily for when the user chooses to lock down the device @@ -63,12 +64,35 @@ constructor( } } + private fun listenForGoneToDreamingLockscreenHosted() { + scope.launch { + keyguardInteractor.isActiveDreamLockscreenHosted + .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) + .collect { (isActiveDreamLockscreenHosted, lastStartedStep) -> + if (isActiveDreamLockscreenHosted && lastStartedStep.to == KeyguardState.GONE) { + startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + } + } + } + } + private fun listenForGoneToDreaming() { scope.launch { keyguardInteractor.isAbleToDream - .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) - .collect { (isAbleToDream, lastStartedStep) -> - if (isAbleToDream && lastStartedStep.to == KeyguardState.GONE) { + .sample( + combine( + transitionInteractor.startedKeyguardTransitionStep, + keyguardInteractor.isActiveDreamLockscreenHosted, + ::Pair + ), + ::toTriple + ) + .collect { (isAbleToDream, lastStartedStep, isActiveDreamLockscreenHosted) -> + if ( + isAbleToDream && + lastStartedStep.to == KeyguardState.GONE && + !isActiveDreamLockscreenHosted + ) { startTransitionTo(KeyguardState.DREAMING) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index ed1bf3ef2753..6b28b270032e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -70,17 +70,27 @@ constructor( combine( transitionInteractor.startedKeyguardTransitionStep, transitionInteractor.finishedKeyguardState, - ::Pair + keyguardInteractor.isActiveDreamLockscreenHosted, + ::Triple ), - ::toTriple + ::toQuad ) - .collect { (isAbleToDream, lastStartedTransition, finishedKeyguardState) -> + .collect { + ( + isAbleToDream, + lastStartedTransition, + finishedKeyguardState, + isActiveDreamLockscreenHosted) -> val isOnLockscreen = finishedKeyguardState == KeyguardState.LOCKSCREEN val isTransitionInterruptible = lastStartedTransition.to == KeyguardState.LOCKSCREEN && !invalidFromStates.contains(lastStartedTransition.from) if (isAbleToDream && (isOnLockscreen || isTransitionInterruptible)) { - startTransitionTo(KeyguardState.DREAMING) + if (isActiveDreamLockscreenHosted) { + startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + } else { + startTransitionTo(KeyguardState.DREAMING) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index e1754f55b1dd..9142d1faa90a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -27,6 +27,8 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.util.kotlin.Utils.Companion.toQuad +import com.android.systemui.util.kotlin.Utils.Companion.toQuint +import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -52,6 +54,7 @@ constructor( listenForPrimaryBouncerToGone() listenForPrimaryBouncerToAodOrDozing() listenForPrimaryBouncerToLockscreenOrOccluded() + listenForPrimaryBouncerToDreamingLockscreenHosted() } private fun listenForPrimaryBouncerToLockscreenOrOccluded() { @@ -62,17 +65,24 @@ constructor( keyguardInteractor.wakefulnessModel, transitionInteractor.startedKeyguardTransitionStep, keyguardInteractor.isKeyguardOccluded, - ::Triple + keyguardInteractor.isActiveDreamLockscreenHosted, + ::toQuad ), - ::toQuad + ::toQuint ) - .collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep, occluded) - -> + .collect { + ( + isBouncerShowing, + wakefulnessState, + lastStartedTransitionStep, + occluded, + isActiveDreamLockscreenHosted) -> if ( !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER && (wakefulnessState.state == WakefulnessState.AWAKE || - wakefulnessState.state == WakefulnessState.STARTING_TO_WAKE) + wakefulnessState.state == WakefulnessState.STARTING_TO_WAKE) && + !isActiveDreamLockscreenHosted ) { startTransitionTo( if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN @@ -111,6 +121,30 @@ constructor( } } + private fun listenForPrimaryBouncerToDreamingLockscreenHosted() { + scope.launch { + keyguardInteractor.primaryBouncerShowing + .sample( + combine( + keyguardInteractor.isActiveDreamLockscreenHosted, + transitionInteractor.startedKeyguardTransitionStep, + ::Pair + ), + ::toTriple + ) + .collect { + (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) -> + if ( + !isBouncerShowing && + isActiveDreamLockscreenHosted && + lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER + ) { + startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + } + } + } + } + private fun listenForPrimaryBouncerToGone() { scope.launch { keyguardInteractor.isKeyguardGoingAway diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index cc159168009e..53d3c076247e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -34,9 +34,9 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState +import com.android.systemui.keyguard.shared.model.ScreenModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -87,7 +87,7 @@ constructor( /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing /** Receive an event for doze time tick */ - val dozeTimeTick: Flow<Unit> = repository.dozeTimeTick + val dozeTimeTick: Flow<Long> = repository.dozeTimeTick /** Whether Always-on Display mode is available. */ val isAodAvailable: Flow<Boolean> = repository.isAodAvailable /** Doze transition information. */ @@ -100,7 +100,7 @@ constructor( /** Whether the system is dreaming with an overlay active */ val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay /** Whether the system is dreaming and the active dream is hosted in lockscreen */ - val isActiveDreamLockscreenHosted: Flow<Boolean> = repository.isActiveDreamLockscreenHosted + val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted /** Event for when the camera gesture is detected */ val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow { val callback = @@ -122,6 +122,9 @@ constructor( /** The device wake/sleep state */ val wakefulnessModel: StateFlow<WakefulnessModel> = repository.wakefulness + /** The device screen state */ + val screenModel: StateFlow<ScreenModel> = repository.screenModel + /** * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means * that doze mode is not running and DREAMING is ok to commence. @@ -237,8 +240,16 @@ constructor( repository.setQuickSettingsVisible(isVisible) } - fun setKeyguardRootVisibility(statusBarState: Int, goingToFullShade: Boolean, isOcclusionTransitionRunning: Boolean) { - repository.setKeyguardVisibility(statusBarState, goingToFullShade, isOcclusionTransitionRunning) + fun setKeyguardRootVisibility( + statusBarState: Int, + goingToFullShade: Boolean, + isOcclusionTransitionRunning: Boolean + ) { + repository.setKeyguardVisibility( + statusBarState, + goingToFullShade, + isOcclusionTransitionRunning + ) } fun setClockPosition(x: Int, y: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index a48684381345..9c796f846994 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -412,7 +412,11 @@ constructor( KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_PAGE_TRANSITIONS, value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PAGE_TRANSITIONS) - ) + ), + KeyguardPickerFlag( + name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_PREVIEW_ANIMATION, + value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PREVIEW_ANIMATION) + ), ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index efc1bd0fff04..ba7b9870103a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -45,6 +45,7 @@ constructor( is FromOccludedTransitionInteractor -> Log.d(TAG, "Started $it") is FromDozingTransitionInteractor -> Log.d(TAG, "Started $it") is FromAlternateBouncerTransitionInteractor -> Log.d(TAG, "Started $it") + is FromDreamingLockscreenHostedTransitionInteractor -> Log.d(TAG, "Started $it") } it.start() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 45bf20d3ec44..8c4c7ae53ce6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -24,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED @@ -89,12 +90,20 @@ constructor( val dreamingToLockscreenTransition: Flow<TransitionStep> = repository.transition(DREAMING, LOCKSCREEN) + /** DREAMING_LOCKSCREEN_HOSTED->LOCKSCREEN transition information. */ + val dreamingLockscreenHostedToLockscreenTransition: Flow<TransitionStep> = + repository.transition(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN) + /** GONE->AOD transition information. */ val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD) /** GONE->DREAMING transition information. */ val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING) + /** GONE->DREAMING_LOCKSCREEN_HOSTED transition information. */ + val goneToDreamingLockscreenHostedTransition: Flow<TransitionStep> = + repository.transition(GONE, DREAMING_LOCKSCREEN_HOSTED) + /** LOCKSCREEN->AOD transition information. */ val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD) @@ -102,6 +111,10 @@ constructor( val lockscreenToDreamingTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, DREAMING) + /** LOCKSCREEN->DREAMING_LOCKSCREEN_HOSTED transition information. */ + val lockscreenToDreamingLockscreenHostedTransition: Flow<TransitionStep> = + repository.transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED) + /** LOCKSCREEN->OCCLUDED transition information. */ val lockscreenToOccludedTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, OCCLUDED) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt index 4244e5565a4b..6115d90430b3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt @@ -92,6 +92,7 @@ constructor( KeyguardState.DOZING -> false KeyguardState.AOD -> false KeyguardState.DREAMING -> true + KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true KeyguardState.ALTERNATE_BOUNCER -> true KeyguardState.PRIMARY_BOUNCER -> true KeyguardState.LOCKSCREEN -> true diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt index 1c200b086990..278c68d3c55b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt @@ -19,10 +19,9 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -30,17 +29,14 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** Hosts business and application state accessing logic for the lockscreen scene. */ +@SysUISingleton class LockscreenSceneInteractor -@AssistedInject +@Inject constructor( @Application applicationScope: CoroutineScope, private val authenticationInteractor: AuthenticationInteractor, - bouncerInteractorFactory: BouncerInteractor.Factory, - @Assisted private val containerName: String, + private val bouncerInteractor: BouncerInteractor, ) { - private val bouncerInteractor: BouncerInteractor = - bouncerInteractorFactory.create(containerName) - /** Whether the device is currently locked. */ val isDeviceLocked: StateFlow<Boolean> = authenticationInteractor.isUnlocked @@ -67,13 +63,6 @@ constructor( /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */ fun dismissLockscreen() { - bouncerInteractor.showOrUnlockDevice(containerName = containerName) - } - - @AssistedFactory - interface Factory { - fun create( - containerName: String, - ): LockscreenSceneInteractor + bouncerInteractor.showOrUnlockDevice() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt index d9690b7e3b38..56f552961432 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt @@ -50,6 +50,12 @@ abstract class StartKeyguardTransitionModule { @Binds @IntoSet + abstract fun fromDreamingLockscreenHosted( + impl: FromDreamingLockscreenHostedTransitionInteractor + ): TransitionInteractor + + @Binds + @IntoSet abstract fun fromOccluded(impl: FromOccludedTransitionInteractor): TransitionInteractor @Binds diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index 8f4776fa2ed3..2a3f8520a63c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger +import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -49,6 +50,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * Encapsulates business logic related face authentication being triggered for device entry from @@ -69,6 +71,7 @@ constructor( private val faceAuthenticationLogger: FaceAuthenticationLogger, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, + private val userRepository: UserRepository, ) : CoreStartable, KeyguardFaceAuthInteractor { private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf() @@ -128,6 +131,23 @@ constructor( } } .launchIn(applicationScope) + + // User switching should stop face auth and then when it is complete we should trigger face + // auth so that the switched user can unlock the device with face auth. + userRepository.userSwitchingInProgress + .pairwise(false) + .onEach { (wasSwitching, isSwitching) -> + if (!wasSwitching && isSwitching) { + repository.pauseFaceAuth() + } else if (wasSwitching && !isSwitching) { + repository.resumeFaceAuth() + runFaceAuth( + FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING, + fallbackToDetect = true + ) + } + } + .launchIn(applicationScope) } override fun onSwipeUpOnBouncer() { @@ -199,8 +219,10 @@ constructor( } else { faceAuthenticationStatusOverride.value = null applicationScope.launch { - faceAuthenticationLogger.authRequested(uiEvent) - repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) + withContext(mainDispatcher) { + faceAuthenticationLogger.authRequested(uiEvent) + repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) + } } } } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt index 87b4321769b5..1e20cdbed9eb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt @@ -35,6 +35,12 @@ enum class KeyguardState { * parties to present their own UI over keyguard, like a screensaver. */ DREAMING, + /* + * A device state after the device times out, which can be from both LOCKSCREEN or GONE states. + * It is a special version of DREAMING state but not DOZING. The active dream will be windowless + * and hosted in the lockscreen. + */ + DREAMING_LOCKSCREEN_HOSTED, /** * The device has entered a special low-power mode within SystemUI, also called the Always-on * Display (AOD). A minimal UI is presented to show critical information. If the device is in diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt new file mode 100644 index 000000000000..113f01c0b122 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt @@ -0,0 +1,50 @@ +/* + * 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.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down GONE->DREAMING_LOCKSCREEN_HOSTED transition into discrete steps for corresponding + * views to consume. + */ +@SysUISingleton +class GoneToDreamingLockscreenHostedTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = TO_DREAMING_DURATION, + transitionFlow = interactor.goneToDreamingLockscreenHostedTransition, + ) + + /** Lockscreen views alpha - hide immediately */ + val lockscreenAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 1.milliseconds, + onStep = { 0f }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index f212a553aeb3..abd178ca6c1d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -19,12 +19,11 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.R import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.shared.model.SceneKey -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -32,15 +31,13 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** Models UI state and handles user input for the lockscreen scene. */ +@SysUISingleton class LockscreenSceneViewModel -@AssistedInject +@Inject constructor( @Application applicationScope: CoroutineScope, - interactorFactory: LockscreenSceneInteractor.Factory, - @Assisted containerName: String, + private val interactor: LockscreenSceneInteractor, ) { - private val interactor: LockscreenSceneInteractor = interactorFactory.create(containerName) - /** The icon for the "lock" button on the lockscreen. */ val lockButtonIcon: StateFlow<Icon> = interactor.isDeviceLocked @@ -98,11 +95,4 @@ constructor( ) ) } - - @AssistedFactory - interface Factory { - fun create( - containerName: String, - ): LockscreenSceneViewModel - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt index b307f1b43b45..dd586076be83 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt @@ -112,6 +112,7 @@ open class UdfpsLockscreenViewModel( KeyguardState.OFF, KeyguardState.DOZING, KeyguardState.DREAMING, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, KeyguardState.AOD, KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index 42164c795b06..fdb3ddd7294f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -30,16 +30,11 @@ import android.os.IBinder import android.os.ResultReceiver import android.os.UserHandle import android.view.ViewGroup -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry +import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider +import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider +import com.android.intentresolver.ChooserActivity +import com.android.intentresolver.chooser.TargetInfo import com.android.internal.annotations.VisibleForTesting -import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider -import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider -import com.android.internal.app.ChooserActivity -import com.android.internal.app.ResolverListController -import com.android.internal.app.chooser.NotSelectableTargetInfo -import com.android.internal.app.chooser.TargetInfo import com.android.systemui.R import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController @@ -56,12 +51,8 @@ class MediaProjectionAppSelectorActivity( private val activityLauncher: AsyncActivityLauncher, /** This is used to override the dependency in a screenshot test */ @VisibleForTesting - private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)? -) : - ChooserActivity(), - MediaProjectionAppSelectorView, - MediaProjectionAppSelectorResultHandler, - LifecycleOwner { + private val listControllerFactory: ((userHandle: UserHandle) -> ChooserListController)? +) : ChooserActivity(), MediaProjectionAppSelectorView, MediaProjectionAppSelectorResultHandler { @Inject constructor( @@ -69,8 +60,6 @@ class MediaProjectionAppSelectorActivity( activityLauncher: AsyncActivityLauncher ) : this(componentFactory, activityLauncher, listControllerFactory = null) - private val lifecycleRegistry = LifecycleRegistry(this) - override val lifecycle = lifecycleRegistry private lateinit var configurationController: ConfigurationController private lateinit var controller: MediaProjectionAppSelectorController private lateinit var recentsViewController: MediaProjectionRecentsViewController @@ -84,7 +73,6 @@ class MediaProjectionAppSelectorActivity( override fun getLayoutResource() = R.layout.media_projection_app_selector public override fun onCreate(bundle: Bundle?) { - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) component = componentFactory.create(view = this, resultHandler = this) component.lifecycleObservers.forEach { lifecycle.addObserver(it) } @@ -107,26 +95,6 @@ class MediaProjectionAppSelectorActivity( controller.init() } - override fun onStart() { - super.onStart() - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START) - } - - override fun onResume() { - super.onResume() - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) - } - - override fun onPause() { - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE) - super.onPause() - } - - override fun onStop() { - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) - super.onStop() - } - override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) configurationController.onConfigurationChanged(newConfig) @@ -137,13 +105,13 @@ class MediaProjectionAppSelectorActivity( override fun createBlockerEmptyStateProvider(): EmptyStateProvider = component.emptyStateProvider - override fun createListController(userHandle: UserHandle): ResolverListController = + override fun createListController(userHandle: UserHandle): ChooserListController = listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle) override fun startSelected(which: Int, always: Boolean, filtered: Boolean) { val currentListAdapter = mChooserMultiProfilePagerAdapter.activeListAdapter val targetInfo = currentListAdapter.targetInfoForPosition(which, filtered) ?: return - if (targetInfo is NotSelectableTargetInfo) return + if (targetInfo.isNotSelectableTargetInfo) return val intent = createIntent(targetInfo) @@ -183,7 +151,6 @@ class MediaProjectionAppSelectorActivity( } override fun onDestroy() { - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) component.lifecycleObservers.forEach { lifecycle.removeObserver(it) } // onDestroy is also called when an app is selected, in that case we only want to send // RECORD_CONTENT_TASK but not RECORD_CANCEL diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt index 829b0ddbe3a8..fd14e2b9a96b 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt @@ -17,10 +17,10 @@ package com.android.systemui.mediaprojection.appselector import android.content.Context import android.os.UserHandle +import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState +import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider +import com.android.intentresolver.ResolverListAdapter import com.android.internal.R as AndroidR -import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState -import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider -import com.android.internal.app.ResolverListAdapter import com.android.systemui.R import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 2adc211ef23f..0842fe0dd764 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -31,6 +31,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.pm.ShortcutManager import android.graphics.drawable.Icon +import android.os.Process import android.os.UserHandle import android.os.UserManager import android.provider.Settings @@ -317,7 +318,9 @@ constructor( return } - if (user == userTracker.userHandle) { + // When switched to a secondary user, the sysUI is still running in the main user, we will + // need to update the shortcut in the secondary user. + if (user == getCurrentRunningUser()) { updateNoteTaskAsUserInternal(user) } else { // TODO(b/278729185): Replace fire and forget service with a bounded service. @@ -354,6 +357,9 @@ constructor( updateNoteTaskAsUser(user) } + // Returns the [UserHandle] that this class is running on. + @VisibleForTesting internal fun getCurrentRunningUser(): UserHandle = Process.myUserHandle() + private val SecureSettings.preferredUser: UserHandle get() { val trackingUserId = userTracker.userHandle.identifier diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index 7f0f89415280..d1d3e3de39f0 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -28,6 +28,8 @@ import androidx.activity.ComponentActivity; import androidx.lifecycle.ViewModelProvider; import com.android.systemui.compose.ComposeFacade; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.people.ui.view.PeopleViewBinder; import com.android.systemui.people.ui.viewmodel.PeopleViewModel; @@ -43,11 +45,14 @@ public class PeopleSpaceActivity extends ComponentActivity { private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; private final PeopleViewModel.Factory mViewModelFactory; + private final FeatureFlags mFeatureFlags; @Inject - public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory) { + public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory, + FeatureFlags featureFlags) { super(); mViewModelFactory = viewModelFactory; + mFeatureFlags = featureFlags; } @Override @@ -67,7 +72,8 @@ public class PeopleSpaceActivity extends ComponentActivity { return null; }; - if (ComposeFacade.INSTANCE.isComposeAvailable()) { + if (mFeatureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE) + && ComposeFacade.INSTANCE.isComposeAvailable()) { Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity"); ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 8d9475d9a53e..ddb395b6b598 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -52,6 +52,7 @@ 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.flags.Flags; import com.android.systemui.media.controls.ui.MediaHost; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSContainerController; @@ -285,7 +286,8 @@ 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()) { + if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS) + || !ComposeFacade.INSTANCE.isComposeAvailable()) { Log.d(TAG, "Binding the View implementation of the QS footer actions"); mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 3e7bdd1a1444..9bb192b4c271 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -34,6 +34,7 @@ import android.provider.Settings.Global; import android.service.notification.ZenModeConfig; import android.service.quicksettings.Tile; import android.text.TextUtils; +import android.util.Log; import android.view.View; import android.widget.Switch; @@ -315,6 +316,7 @@ public class DndTile extends QSTileImpl<BooleanState> { private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { public void onZenChanged(int zen) { + Log.d(TAG, "Zen changed to " + zen + ". Requesting refresh of tile."); refreshState(zen); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 36dec1d112b9..5e6a44bf8130 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -16,30 +16,19 @@ package com.android.systemui.qs.ui.viewmodel +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject /** Models UI state and handles user input for the quick settings scene. */ +@SysUISingleton class QuickSettingsSceneViewModel -@AssistedInject +@Inject constructor( - lockscreenSceneInteractorFactory: LockscreenSceneInteractor.Factory, - @Assisted containerName: String, + private val lockscreenSceneInteractor: LockscreenSceneInteractor, ) { - private val lockscreenSceneInteractor: LockscreenSceneInteractor = - lockscreenSceneInteractorFactory.create(containerName) - /** Notifies that some content in quick settings was clicked. */ fun onContentClicked() { lockscreenSceneInteractor.dismissLockscreen() } - - @AssistedFactory - interface Factory { - fun create( - containerName: String, - ): QuickSettingsSceneViewModel - } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index bf40a2d0ad51..03bd11bb433e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -97,7 +97,6 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.buttons.KeyButtonView; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import com.android.systemui.scene.domain.interactor.SceneInteractor; -import com.android.systemui.scene.shared.model.SceneContainerNames; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeViewController; @@ -221,8 +220,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // If scene framework is enabled, set the scene container window to // visible and let the touch "slip" into that window. if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) { - mSceneInteractor.get().setVisible( - SceneContainerNames.SYSTEM_UI_DEFAULT, true); + mSceneInteractor.get().setVisible(true); } else { centralSurfaces.onInputFocusTransfer( mInputFocusTransferStarted, false /* cancel */, diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 26c52199f493..398e64b1981b 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -19,7 +19,6 @@ package com.android.systemui.scene import com.android.systemui.scene.domain.startable.SceneContainerStartableModule import com.android.systemui.scene.shared.model.SceneContainerConfigModule import com.android.systemui.scene.ui.composable.SceneModule -import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModelModule import dagger.Module @Module( @@ -27,7 +26,6 @@ import dagger.Module [ SceneContainerConfigModule::class, SceneContainerStartableModule::class, - SceneContainerViewModelModule::class, SceneModule::class, ], ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index 0a86d35b7b62..1fca4886a31f 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -29,26 +29,20 @@ import kotlinx.coroutines.flow.asStateFlow class SceneContainerRepository @Inject constructor( - private val containerConfigByName: Map<String, SceneContainerConfig>, + private val config: SceneContainerConfig, ) { - private val containerVisibilityByName: Map<String, MutableStateFlow<Boolean>> = - containerConfigByName - .map { (containerName, _) -> containerName to MutableStateFlow(true) } - .toMap() - private val currentSceneByContainerName: Map<String, MutableStateFlow<SceneModel>> = - containerConfigByName - .map { (containerName, config) -> - containerName to MutableStateFlow(SceneModel(config.initialSceneKey)) - } - .toMap() - private val sceneTransitionProgressByContainerName: Map<String, MutableStateFlow<Float>> = - containerConfigByName - .map { (containerName, _) -> containerName to MutableStateFlow(1f) } - .toMap() - private val sceneTransitionByContainerName: - Map<String, MutableStateFlow<SceneTransitionModel?>> = - containerConfigByName.keys.associateWith { MutableStateFlow(null) } + private val _isVisible = MutableStateFlow(true) + val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow() + + private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey)) + val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow() + + private val _transitionProgress = MutableStateFlow(1f) + val transitionProgress: StateFlow<Float> = _transitionProgress.asStateFlow() + + private val _transitions = MutableStateFlow<SceneTransitionModel?>(null) + val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow() /** * Returns the keys to all scenes in the container with the given name. @@ -56,100 +50,50 @@ constructor( * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. */ - fun allSceneKeys(containerName: String): List<SceneKey> { - return containerConfigByName[containerName]?.sceneKeys - ?: error(noSuchContainerErrorMessage(containerName)) + fun allSceneKeys(): List<SceneKey> { + return config.sceneKeys } /** Sets the current scene in the container with the given name. */ - fun setCurrentScene(containerName: String, scene: SceneModel) { - check(allSceneKeys(containerName).contains(scene.key)) { + fun setCurrentScene(scene: SceneModel) { + check(allSceneKeys().contains(scene.key)) { """ - Cannot set current scene key to "${scene.key}". The container "$containerName" does - not contain a scene with that key. + Cannot set current scene key to "${scene.key}". The configuration does not contain a + scene with that key. """ .trimIndent() } - currentSceneByContainerName.setValue(containerName, scene) + _currentScene.value = scene } /** Sets the scene transition in the container with the given name. */ - fun setSceneTransition(containerName: String, from: SceneKey, to: SceneKey) { - check(allSceneKeys(containerName).contains(from)) { + fun setSceneTransition(from: SceneKey, to: SceneKey) { + check(allSceneKeys().contains(from)) { """ - Cannot set current scene key to "$from". The container "$containerName" does - not contain a scene with that key. + Cannot set current scene key to "$from". The configuration does not contain a scene + with that key. """ .trimIndent() } - check(allSceneKeys(containerName).contains(to)) { + check(allSceneKeys().contains(to)) { """ - Cannot set current scene key to "$to". The container "$containerName" does - not contain a scene with that key. + Cannot set current scene key to "$to". The configuration does not contain a scene + with that key. """ .trimIndent() } - sceneTransitionByContainerName.setValue( - containerName, - SceneTransitionModel(from = from, to = to) - ) - } - - /** The current scene in the container with the given name. */ - fun currentScene(containerName: String): StateFlow<SceneModel> { - return currentSceneByContainerName.mutableOrError(containerName).asStateFlow() - } - - /** - * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene - * transition occurs. The flow begins with a `null` value at first, because the initial scene is - * not something that we transition to from another scene. - */ - fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> { - return sceneTransitionByContainerName.mutableOrError(containerName).asStateFlow() + _transitions.value = SceneTransitionModel(from = from, to = to) } /** Sets whether the container with the given name is visible. */ - fun setVisible(containerName: String, isVisible: Boolean) { - containerVisibilityByName.setValue(containerName, isVisible) - } - - /** Whether the container with the given name should be visible. */ - fun isVisible(containerName: String): StateFlow<Boolean> { - return containerVisibilityByName.mutableOrError(containerName).asStateFlow() + fun setVisible(isVisible: Boolean) { + _isVisible.value = isVisible } /** Sets scene transition progress to the current scene in the container with the given name. */ - fun setSceneTransitionProgress(containerName: String, progress: Float) { - sceneTransitionProgressByContainerName.setValue(containerName, progress) - } - - /** Progress of the transition into the current scene in the container with the given name. */ - fun sceneTransitionProgress(containerName: String): StateFlow<Float> { - return sceneTransitionProgressByContainerName.mutableOrError(containerName).asStateFlow() - } - - private fun <T> Map<String, MutableStateFlow<T>>.mutableOrError( - containerName: String, - ): MutableStateFlow<T> { - return this[containerName] ?: error(noSuchContainerErrorMessage(containerName)) - } - - private fun <T> Map<String, MutableStateFlow<T>>.setValue( - containerName: String, - value: T, - ) { - val mutable = mutableOrError(containerName) - mutable.value = value - } - - private fun noSuchContainerErrorMessage(containerName: String): String { - return """ - No container named "$containerName". Existing containers: - ${containerConfigByName.values.joinToString(", ") { it.name }} - """ - .trimIndent() + fun setSceneTransitionProgress(progress: Float) { + _transitionProgress.value = progress } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index f03f040c206d..39daad33f75e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -30,12 +30,8 @@ import kotlinx.coroutines.flow.asStateFlow /** * Generic business logic and app state accessors for the scene framework. * - * Note that scene container specific business logic does not belong in this class. Instead, it - * should be hoisted to a class that is specific to that scene container, for an example, please see - * [SystemUiDefaultSceneContainerStartable]. - * - * Also note that this class should not depend on state or logic of other modules or features. - * Instead, other feature modules should depend on and call into this class when their parts of the + * Note that this class should not depend on state or logic of other modules or features. Instead, + * other feature modules should depend on and call into this class when their parts of the * application state change. */ @SysUISingleton @@ -51,50 +47,42 @@ constructor( * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. */ - fun allSceneKeys(containerName: String): List<SceneKey> { - return repository.allSceneKeys(containerName) + fun allSceneKeys(): List<SceneKey> { + return repository.allSceneKeys() } /** Sets the scene in the container with the given name. */ - fun setCurrentScene(containerName: String, scene: SceneModel) { - val currentSceneKey = repository.currentScene(containerName).value.key - repository.setCurrentScene(containerName, scene) - repository.setSceneTransition(containerName, from = currentSceneKey, to = scene.key) + fun setCurrentScene(scene: SceneModel) { + val currentSceneKey = repository.currentScene.value.key + repository.setCurrentScene(scene) + repository.setSceneTransition(from = currentSceneKey, to = scene.key) } /** The current scene in the container with the given name. */ - fun currentScene(containerName: String): StateFlow<SceneModel> { - return repository.currentScene(containerName) - } + val currentScene: StateFlow<SceneModel> = repository.currentScene /** Sets the visibility of the container with the given name. */ - fun setVisible(containerName: String, isVisible: Boolean) { - return repository.setVisible(containerName, isVisible) + fun setVisible(isVisible: Boolean) { + return repository.setVisible(isVisible) } /** Whether the container with the given name is visible. */ - fun isVisible(containerName: String): StateFlow<Boolean> { - return repository.isVisible(containerName) - } + val isVisible: StateFlow<Boolean> = repository.isVisible /** Sets scene transition progress to the current scene in the container with the given name. */ - fun setSceneTransitionProgress(containerName: String, progress: Float) { - repository.setSceneTransitionProgress(containerName, progress) + fun setSceneTransitionProgress(progress: Float) { + repository.setSceneTransitionProgress(progress) } /** Progress of the transition into the current scene in the container with the given name. */ - fun sceneTransitionProgress(containerName: String): StateFlow<Float> { - return repository.sceneTransitionProgress(containerName) - } + val transitionProgress: StateFlow<Float> = repository.transitionProgress /** * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene * transition occurs. The flow begins with a `null` value at first, because the initial scene is * not something that we transition to from another scene. */ - fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> { - return repository.sceneTransitions(containerName) - } + val transitions: StateFlow<SceneTransitionModel?> = repository.transitions private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null) diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 92384d68157b..1c87eb25004e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -28,7 +28,6 @@ import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.model.SysUiState import com.android.systemui.model.updateFlags import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING @@ -44,12 +43,11 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch /** - * Hooks up business logic that manipulates the state of the [SceneInteractor] for the default - * system UI scene container (the one named [SceneContainerNames.SYSTEM_UI_DEFAULT]) based on state - * from other systems. + * Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI + * scene container based on state from other systems. */ @SysUISingleton -class SystemUiDefaultSceneContainerStartable +class SceneContainerStartable @Inject constructor( @Application private val applicationScope: CoroutineScope, @@ -72,13 +70,10 @@ constructor( /** Updates the visibility of the scene container based on the current scene. */ private fun hydrateVisibility() { applicationScope.launch { - sceneInteractor - .currentScene(CONTAINER_NAME) + sceneInteractor.currentScene .map { it.key } .distinctUntilChanged() - .collect { sceneKey -> - sceneInteractor.setVisible(CONTAINER_NAME, sceneKey != SceneKey.Gone) - } + .collect { sceneKey -> sceneInteractor.setVisible(sceneKey != SceneKey.Gone) } } } @@ -87,7 +82,7 @@ constructor( applicationScope.launch { authenticationInteractor.isUnlocked .map { isUnlocked -> - val currentSceneKey = sceneInteractor.currentScene(CONTAINER_NAME).value.key + val currentSceneKey = sceneInteractor.currentScene.value.key val isBypassEnabled = authenticationInteractor.isBypassEnabled() when { isUnlocked -> @@ -135,8 +130,7 @@ constructor( /** Keeps [SysUiState] up-to-date */ private fun hydrateSystemUiState() { applicationScope.launch { - sceneInteractor - .currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT) + sceneInteractor.currentScene .map { it.key } .distinctUntilChanged() .collect { sceneKey -> @@ -155,12 +149,7 @@ constructor( private fun switchToScene(targetSceneKey: SceneKey) { sceneInteractor.setCurrentScene( - containerName = CONTAINER_NAME, scene = SceneModel(targetSceneKey), ) } - - companion object { - private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT - } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt index b3de2d158a53..8da1803053f4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt @@ -27,6 +27,6 @@ interface SceneContainerStartableModule { @Binds @IntoMap - @ClassKey(SystemUiDefaultSceneContainerStartable::class) - fun bind(impl: SystemUiDefaultSceneContainerStartable): CoreStartable + @ClassKey(SceneContainerStartable::class) + fun bind(impl: SceneContainerStartable): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt index 354de8ac7aa5..31597c1752db 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt @@ -27,8 +27,6 @@ import kotlinx.coroutines.flow.asStateFlow * takes care of rendering the current scene and allowing scenes to be switched from one to another * based on either user action (for example, swiping down while on the lock screen scene may switch * to the shade scene). - * - * The framework also supports multiple containers, each one with its own configuration. */ interface Scene { @@ -59,7 +57,7 @@ interface Scene { * The API is designed such that it's possible to emit ever-changing values for each * [UserAction] to enable, disable, or change the destination scene of a given user action. */ - fun destinationScenes(containerName: String): StateFlow<Map<UserAction, SceneModel>> = + fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> = MutableStateFlow(emptyMap<UserAction, SceneModel>()).asStateFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt index 0327edbb06b4..8204edc33fe4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt @@ -16,10 +16,8 @@ package com.android.systemui.scene.shared.model -/** Models the configuration of a single scene container. */ +/** Models the configuration of the scene container. */ data class SceneContainerConfig( - /** Container name. Must be unique across all containers in System UI. */ - val name: String, /** * The keys to all scenes in the container, sorted by z-order such that the last one renders on diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt index 7562a5a848d8..f74005b03816 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt @@ -16,44 +16,27 @@ package com.android.systemui.scene.shared.model -import com.android.systemui.dagger.SysUISingleton import dagger.Module import dagger.Provides -import javax.inject.Named @Module object SceneContainerConfigModule { @Provides - fun containerConfigs(): Map<String, SceneContainerConfig> { - return mapOf( - SceneContainerNames.SYSTEM_UI_DEFAULT to - SceneContainerConfig( - name = SceneContainerNames.SYSTEM_UI_DEFAULT, - // Note that this list is in z-order. The first one is the bottom-most and the - // last - // one is top-most. - sceneKeys = - listOf( - SceneKey.Gone, - SceneKey.Lockscreen, - SceneKey.Bouncer, - SceneKey.Shade, - SceneKey.QuickSettings, - ), - initialSceneKey = SceneKey.Lockscreen, + fun containerConfig(): SceneContainerConfig { + return SceneContainerConfig( + // Note that this list is in z-order. The first one is the bottom-most and the + // last + // one is top-most. + sceneKeys = + listOf( + SceneKey.Gone, + SceneKey.Lockscreen, + SceneKey.Bouncer, + SceneKey.Shade, + SceneKey.QuickSettings, ), + initialSceneKey = SceneKey.Lockscreen, ) } - - @Provides - @SysUISingleton - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) - fun provideDefaultSceneContainerConfig( - configs: Map<String, SceneContainerConfig>, - ): SceneContainerConfig { - return checkNotNull(configs[SceneContainerNames.SYSTEM_UI_DEFAULT]) { - "No SceneContainerConfig named \"${SceneContainerNames.SYSTEM_UI_DEFAULT}\"." - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index 005f48d9f250..f44748a99080 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -17,16 +17,20 @@ package com.android.systemui.scene.ui.viewmodel import android.view.MotionEvent +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.RemoteUserInput import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow -/** Models UI state for a single scene container. */ -class SceneContainerViewModel( +/** Models UI state for the scene container. */ +@SysUISingleton +class SceneContainerViewModel +@Inject +constructor( private val interactor: SceneInteractor, - val containerName: String, ) { /** A flow of motion events originating from outside of the scene framework. */ val remoteUserInput: StateFlow<RemoteUserInput?> = interactor.remoteUserInput @@ -37,22 +41,22 @@ class SceneContainerViewModel( * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. */ - val allSceneKeys: List<SceneKey> = interactor.allSceneKeys(containerName) + val allSceneKeys: List<SceneKey> = interactor.allSceneKeys() /** The current scene. */ - val currentScene: StateFlow<SceneModel> = interactor.currentScene(containerName) + val currentScene: StateFlow<SceneModel> = interactor.currentScene /** Whether the container is visible. */ - val isVisible: StateFlow<Boolean> = interactor.isVisible(containerName) + val isVisible: StateFlow<Boolean> = interactor.isVisible /** Requests a transition to the scene with the given key. */ fun setCurrentScene(scene: SceneModel) { - interactor.setCurrentScene(containerName, scene) + interactor.setCurrentScene(scene) } /** Notifies of the progress of a scene transition. */ fun setSceneTransitionProgress(progress: Float) { - interactor.setSceneTransitionProgress(containerName, progress) + interactor.setSceneTransitionProgress(progress) } /** Handles a [MotionEvent] representing remote user input. */ diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt deleted file mode 100644 index 100f42764322..000000000000 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.scene.ui.viewmodel - -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.SceneContainerNames -import dagger.Module -import dagger.Provides -import javax.inject.Named - -@Module -object SceneContainerViewModelModule { - - @Provides - @SysUISingleton - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) - fun defaultSceneContainerViewModel( - interactor: SceneInteractor, - ): SceneContainerViewModel { - return SceneContainerViewModel( - interactor = interactor, - containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, - ) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 773f35e0c665..2ea63c2a9847 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -140,6 +140,7 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel; import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder; import com.android.systemui.keyguard.ui.view.KeyguardRootView; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel; @@ -597,6 +598,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel; private final LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel; private final GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel; + private final GoneToDreamingLockscreenHostedTransitionViewModel + mGoneToDreamingLockscreenHostedTransitionViewModel; + private final LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -605,6 +609,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final CoroutineDispatcher mMainDispatcher; private boolean mIsAnyMultiShadeExpanded; private boolean mIsOcclusionTransitionRunning = false; + private boolean mIsGoneToDreamingLockscreenHostedTransitionRunning; private int mDreamingToLockscreenTransitionTranslationY; private int mOccludedToLockscreenTransitionTranslationY; private int mLockscreenToDreamingTransitionTranslationY; @@ -652,6 +657,25 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump step.getTransitionState() == TransitionState.RUNNING; }; + private final Consumer<TransitionStep> mGoneToDreamingLockscreenHostedTransition = + (TransitionStep step) -> { + mIsOcclusionTransitionRunning = + step.getTransitionState() == TransitionState.RUNNING; + mIsGoneToDreamingLockscreenHostedTransitionRunning = mIsOcclusionTransitionRunning; + }; + + private final Consumer<TransitionStep> mLockscreenToDreamingLockscreenHostedTransition = + (TransitionStep step) -> { + mIsOcclusionTransitionRunning = + step.getTransitionState() == TransitionState.RUNNING; + }; + + private final Consumer<TransitionStep> mDreamingLockscreenHostedToLockscreenTransition = + (TransitionStep step) -> { + mIsOcclusionTransitionRunning = + step.getTransitionState() == TransitionState.RUNNING; + }; + private final Consumer<TransitionStep> mLockscreenToOccludedTransition = (TransitionStep step) -> { mIsOcclusionTransitionRunning = @@ -734,6 +758,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel, LockscreenToDreamingTransitionViewModel lockscreenToDreamingTransitionViewModel, GoneToDreamingTransitionViewModel goneToDreamingTransitionViewModel, + GoneToDreamingLockscreenHostedTransitionViewModel + goneToDreamingLockscreenHostedTransitionViewModel, LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel, @Main CoroutineDispatcher mainDispatcher, KeyguardTransitionInteractor keyguardTransitionInteractor, @@ -761,6 +787,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mOccludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel; mLockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel; mGoneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel; + mGoneToDreamingLockscreenHostedTransitionViewModel = + goneToDreamingLockscreenHostedTransitionViewModel; mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; mKeyguardInteractor = keyguardInteractor; @@ -1091,6 +1119,24 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mDreamingToLockscreenTransitionTranslationY), setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); + // Gone -> Dreaming hosted in lockscreen + collectFlow(mView, mKeyguardTransitionInteractor + .getGoneToDreamingLockscreenHostedTransition(), + mGoneToDreamingLockscreenHostedTransition, mMainDispatcher); + collectFlow(mView, mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), + mMainDispatcher); + + // Lockscreen -> Dreaming hosted in lockscreen + collectFlow(mView, mKeyguardTransitionInteractor + .getLockscreenToDreamingLockscreenHostedTransition(), + mLockscreenToDreamingLockscreenHostedTransition, mMainDispatcher); + + // Dreaming hosted in lockscreen -> Lockscreen + collectFlow(mView, mKeyguardTransitionInteractor + .getDreamingLockscreenHostedToLockscreenTransition(), + mDreamingLockscreenHostedToLockscreenTransition, mMainDispatcher); + // Occluded->Lockscreen collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(), mOccludedToLockscreenTransition, mMainDispatcher); @@ -1496,13 +1542,18 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mAnimateNextPositionUpdate = false; } + private boolean shouldAnimateKeyguardStatusViewAlignment() { + // Do not animate when transitioning from Gone->DreamingLockscreenHosted + return !mIsGoneToDreamingLockscreenHostedTransitionRunning; + } + private void updateClockAppearance() { int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard; boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled(); boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange(); mKeyguardStatusViewController.displayClock(computeDesiredClockSize(), shouldAnimateClockChange); - updateKeyguardStatusViewAlignment(/* animate= */true); + updateKeyguardStatusViewAlignment(/* animate= */shouldAnimateKeyguardStatusViewAlignment()); int userSwitcherHeight = mKeyguardQsUserSwitchController != null ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0; if (mKeyguardUserSwitcherController != null) { @@ -1625,6 +1676,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // overlap. return true; } + if (isActiveDreamLockscreenHosted()) { + // Dreaming hosted in lockscreen, no "visible" notifications. Safe to center the clock. + return true; + } if (mNotificationListContainer.hasPulsingNotifications()) { // Pulsing notification appears on the right. Move clock left to avoid overlap. return false; @@ -1653,6 +1708,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return mDozing && mDozeParameters.getAlwaysOn(); } + + private boolean isActiveDreamLockscreenHosted() { + return mKeyguardInteractor.isActiveDreamLockscreenHosted().getValue(); + } + private boolean hasVisibleNotifications() { return mNotificationStackScrollLayoutController .getVisibleNotificationCount() != 0 @@ -2820,7 +2880,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void onScreenTurningOn() { - mKeyguardStatusViewController.dozeTimeTick(); + if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + mKeyguardStatusViewController.dozeTimeTick(); + } } private void onMiddleClicked() { @@ -3070,10 +3132,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } - @Override public void dozeTimeTick() { mLockIconViewController.dozeTimeTick(); - mKeyguardStatusViewController.dozeTimeTick(); + if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + mKeyguardStatusViewController.dozeTimeTick(); + } if (mInterpolatedDarkAmount > 0) { positionClockAndNotifications(); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index e02c4275f095..29551188c5c7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -36,7 +36,6 @@ import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig -import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.scene.ui.view.SceneWindowRootView import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel @@ -73,11 +72,8 @@ abstract class ShadeViewProviderModule { fun providesWindowRootView( layoutInflater: LayoutInflater, featureFlags: FeatureFlags, - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) viewModelProvider: Provider<SceneContainerViewModel>, - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) containerConfigProvider: Provider<SceneContainerConfig>, - @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>, layoutInsetController: NotificationInsetsController, ): WindowRootView { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 8a96a4764e66..0b3ed5601c2e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -16,12 +16,11 @@ package com.android.systemui.shade.ui.viewmodel +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.shared.model.SceneKey -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -29,32 +28,29 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** Models UI state and handles user input for the shade scene. */ +@SysUISingleton class ShadeSceneViewModel -@AssistedInject +@Inject constructor( @Application private val applicationScope: CoroutineScope, - lockscreenSceneInteractorFactory: LockscreenSceneInteractor.Factory, - @Assisted private val containerName: String, + private val lockscreenSceneInteractor: LockscreenSceneInteractor, ) { - private val lockScreenInteractor: LockscreenSceneInteractor = - lockscreenSceneInteractorFactory.create(containerName) - /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: StateFlow<SceneKey> = - lockScreenInteractor.isDeviceLocked + lockscreenSceneInteractor.isDeviceLocked .map { isLocked -> upDestinationSceneKey(isLocked = isLocked) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = upDestinationSceneKey( - isLocked = lockScreenInteractor.isDeviceLocked.value, + isLocked = lockscreenSceneInteractor.isDeviceLocked.value, ), ) /** Notifies that some content in the shade was clicked. */ fun onContentClicked() { - lockScreenInteractor.dismissLockscreen() + lockscreenSceneInteractor.dismissLockscreen() } private fun upDestinationSceneKey( @@ -62,11 +58,4 @@ constructor( ): SceneKey { return if (isLocked) SceneKey.Lockscreen else SceneKey.Gone } - - @AssistedFactory - interface Factory { - fun create( - containerName: String, - ): ShadeSceneViewModel - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationMemoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationMemoryModule.kt new file mode 100644 index 000000000000..92e9765936d7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationMemoryModule.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.dagger + +import com.android.systemui.CoreStartable +import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +interface NotificationMemoryModule { + + /** Binds memory monitor into startable map. */ + @Binds + @IntoMap + @ClassKey(NotificationMemoryMonitor::class) + fun bindsNotificationMemoryMonitorStartable(monitor: NotificationMemoryMonitor): CoreStartable +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 31d4ab99098e..ea3a8f59d85b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -91,6 +91,7 @@ import javax.inject.Provider; NotificationSectionHeadersModule.class, NotificationListViewModelModule.class, ActivatableNotificationViewModelModule.class, + NotificationMemoryModule.class, }) public interface NotificationsModule { @Binds diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index f7bd177594a2..106d11f6bcc1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -39,7 +39,6 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder import com.android.systemui.statusbar.notification.logging.NotificationLogger -import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.wm.shell.bubbles.Bubbles @@ -72,7 +71,6 @@ class NotificationsControllerImpl @Inject constructor( private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager, private val bubblesOptional: Optional<Bubbles>, private val fgsNotifListener: ForegroundServiceNotificationListener, - private val memoryMonitor: Lazy<NotificationMemoryMonitor>, private val featureFlags: FeatureFlags ) : NotificationsController { @@ -108,7 +106,6 @@ class NotificationsControllerImpl @Inject constructor( notificationLogger.setUpWithContainer(listContainer) peopleSpaceWidgetManager.attach(notificationListener) fgsNotifListener.init() - memoryMonitor.get().init() } // TODO: Convert all functions below this line into listeners instead of public methods diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt index f38c1e557b25..0fdf681aac26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.logging import android.util.Log +import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -32,13 +33,13 @@ constructor( private val featureFlags: FeatureFlags, private val notificationMemoryDumper: NotificationMemoryDumper, private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>, -) { +) : CoreStartable { companion object { private const val TAG = "NotificationMemory" } - fun init() { + override fun start() { Log.d(TAG, "NotificationMemoryMonitor initialized.") notificationMemoryDumper.init() if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 36a8e9833d39..5e7e4be60104 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -426,16 +426,21 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView updateAppearAnimationAlpha(); updateAppearRect(); mAppearAnimator.addListener(new AnimatorListenerAdapter() { - private boolean mWasCancelled; + private boolean mRunWithoutInterruptions; @Override public void onAnimationEnd(Animator animation) { if (onFinishedRunnable != null) { onFinishedRunnable.run(); } - if (!mWasCancelled) { + if (mRunWithoutInterruptions) { enableAppearDrawing(false); - onAppearAnimationFinished(isAppearing); + } + + // We need to reset the View state, even if the animation was cancelled + onAppearAnimationFinished(isAppearing); + + if (mRunWithoutInterruptions) { InteractionJankMonitor.getInstance().end(getCujType(isAppearing)); } else { InteractionJankMonitor.getInstance().cancel(getCujType(isAppearing)); @@ -444,7 +449,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public void onAnimationStart(Animator animation) { - mWasCancelled = false; + mRunWithoutInterruptions = true; Configuration.Builder builder = Configuration.Builder .withView(getCujType(isAppearing), ActivatableNotificationView.this); InteractionJankMonitor.getInstance().begin(builder); @@ -452,7 +457,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public void onAnimationCancel(Animator animation) { - mWasCancelled = true; + mRunWithoutInterruptions = false; } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index cc087785163e..adff681bb53b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -471,7 +471,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp }; final boolean wakingFromDream = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM - && !mStatusBarStateController.isDozing(); + && mPowerManager.isInteractive(); if (mMode != MODE_NONE && !wakingFromDream) { wakeUp.run(); @@ -509,7 +509,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp // later to awaken. } mNotificationShadeWindowController.setNotificationShadeFocusable(false); - mKeyguardViewMediator.onWakeAndUnlocking(); + mKeyguardViewMediator.onWakeAndUnlocking(wakingFromDream); Trace.endSection(); break; case MODE_ONLY_WAKE: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index ed9722e04da0..801cdbf0e83e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -38,7 +38,6 @@ import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.keyguard.domain.interactor.BurnInInteractor; import com.android.systemui.keyguard.domain.interactor.DozeInteractor; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.ShadeViewController; @@ -94,7 +93,6 @@ public final class DozeServiceHost implements DozeHost { private NotificationShadeWindowViewController mNotificationShadeWindowViewController; private final AuthController mAuthController; private final NotificationIconAreaController mNotificationIconAreaController; - private final BurnInInteractor mBurnInInteractor; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private ShadeViewController mNotificationPanel; private View mAmbientIndicationContainer; @@ -118,8 +116,7 @@ public final class DozeServiceHost implements DozeHost { NotificationWakeUpCoordinator notificationWakeUpCoordinator, AuthController authController, NotificationIconAreaController notificationIconAreaController, - DozeInteractor dozeInteractor, - BurnInInteractor burnInInteractor) { + DozeInteractor dozeInteractor) { super(); mDozeLog = dozeLog; mPowerManager = powerManager; @@ -138,7 +135,6 @@ public final class DozeServiceHost implements DozeHost { mNotificationWakeUpCoordinator = notificationWakeUpCoordinator; mAuthController = authController; mNotificationIconAreaController = notificationIconAreaController; - mBurnInInteractor = burnInInteractor; mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener); mDozeInteractor = dozeInteractor; } @@ -317,7 +313,6 @@ public final class DozeServiceHost implements DozeHost { if (mAmbientIndicationContainer instanceof DozeReceiver) { ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick(); } - mBurnInInteractor.dozeTimeTick(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 8c3050d48d53..122728716eee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -30,7 +30,6 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.RemoteUserInput -import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeLogger import com.android.systemui.shade.ShadeViewController @@ -183,7 +182,7 @@ class PhoneStatusBarViewController private constructor( sceneInteractor.get() .onRemoteUserInput(RemoteUserInput.translateMotionEvent(event)) // TODO(b/291965119): remove once view is expanded to cover the status bar - sceneInteractor.get().setVisible(SceneContainerNames.SYSTEM_UI_DEFAULT, true) + sceneInteractor.get().setVisible(true) return false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index 68a6b3d62bae..fa9b9d2c571a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -37,7 +37,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.scene.domain.interactor.SceneInteractor; -import com.android.systemui.scene.shared.model.SceneContainerNames; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -126,7 +125,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) { javaAdapter.get().alwaysCollectFlow( - sceneInteractor.get().isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT), + sceneInteractor.get().isVisible(), this::onShadeExpansionFullyChanged); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index 1c3a8850df8d..dabdcc5fc0f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -46,7 +46,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; -import com.android.systemui.qs.SettingObserver; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.Utils; import com.android.systemui.util.settings.GlobalSettings; @@ -68,17 +67,17 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { private final Context mContext; private final UserTracker mUserTracker; private final BroadcastDispatcher mBroadcastDispatcher; - private final SettingObserver mModeSetting; - private final SettingObserver mConfigSetting; private final NotificationManager mNoMan; private final AlarmManager mAlarmManager; private final SetupObserver mSetupObserver; private final UserManager mUserManager; + private final GlobalSettings mGlobalSettings; private int mUserId; private boolean mRegistered; private ZenModeConfig mConfig; - private int mZenMode; + // This value is changed in the main thread, but may be read in a background thread. + private volatile int mZenMode; private long mZenUpdateTime; private NotificationManager.Policy mConsolidatedNotificationPolicy; @@ -111,18 +110,20 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mUserTracker = userTracker; - mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE, - userTracker.getUserId()) { + mGlobalSettings = globalSettings; + + ContentObserver modeContentObserver = new ContentObserver(handler) { @Override - protected void handleValueChanged(int value, boolean observedChange) { + public void onChange(boolean selfChange) { + int value = getModeSettingValueFromProvider(); + Log.d(TAG, "Zen mode setting changed to " + value); updateZenMode(value); fireZenChanged(value); } }; - mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG, - userTracker.getUserId()) { + ContentObserver configContentObserver = new ContentObserver(handler) { @Override - protected void handleValueChanged(int value, boolean observedChange) { + public void onChange(boolean selfChange) { try { Trace.beginSection("updateZenModeConfig"); updateZenModeConfig(); @@ -132,9 +133,9 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { } }; mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - mModeSetting.setListening(true); - updateZenMode(mModeSetting.getValue()); - mConfigSetting.setListening(true); + globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver); + updateZenMode(getModeSettingValueFromProvider()); + globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, configContentObserver); updateZenModeConfig(); updateConsolidatedNotificationPolicy(); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); @@ -146,6 +147,10 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { dumpManager.registerDumpable(getClass().getSimpleName(), this); } + private int getModeSettingValueFromProvider() { + return mGlobalSettings.getInt(Global.ZEN_MODE, /* default */ Global.ZEN_MODE_OFF); + } + @Override public boolean isVolumeRestricted() { return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java index 06cc96e2e0cd..d696979f1859 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java @@ -46,6 +46,7 @@ public class StatusBarWindowView extends FrameLayout { public StatusBarWindowView(Context context, AttributeSet attrs) { super(context, attrs); + setClipChildren(false); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java index 757b4e50c3f8..13c1019591a1 100644 --- a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java +++ b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java @@ -25,16 +25,12 @@ import android.view.ViewGroup; import androidx.concurrent.futures.CallbackToFutureAdapter; -import com.android.systemui.dagger.qualifiers.Main; - import com.google.common.util.concurrent.ListenableFuture; import java.util.HashMap; import java.util.HashSet; import java.util.concurrent.Executor; -import javax.inject.Inject; - /** * {@link TouchInsetManager} handles setting the touchable inset regions for a given View. This * is useful for passing through touch events for all but select areas. @@ -153,8 +149,7 @@ public class TouchInsetManager { * Default constructor. * @param executor An {@link Executor} to marshal all operations on. */ - @Inject - public TouchInsetManager(@Main Executor executor) { + public TouchInsetManager(Executor executor) { mExecutor = executor; } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt index 1e73cb3b9b24..1e6556645afb 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt @@ -79,15 +79,15 @@ constructor( private val hapticsScale: Float get() { - val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.1") - return intensityString.toFloatOrNull() ?: 0.1f + val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.5") + return intensityString.toFloatOrNull() ?: 0.5f } private val hapticsScaleTick: Float get() { val intensityString = - SystemProperties.get("persist.unfold.haptics_scale_end_tick", "0.6") - return intensityString.toFloatOrNull() ?: 0.6f + SystemProperties.get("persist.unfold.haptics_scale_end_tick", "1.0") + return intensityString.toFloatOrNull() ?: 1.0f } private val primitivesCount: Int diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 3abae6bcd197..e447c29b351f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -733,29 +733,20 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // is // not enough to trigger a dismissal of the keyguard. underTest.onViewAttached() - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer, null) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null)) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) // While listening, going from the bouncer scene to the gone scene, does dismiss the // keyguard. - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Gone, null) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null)) runCurrent() verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) // While listening, moving back to the bouncer scene does not dismiss the keyguard // again. clearInvocations(viewMediatorCallback) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer, null) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null)) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) @@ -763,18 +754,12 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // scene // does not dismiss the keyguard while we're not listening. underTest.onViewDetached() - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Gone, null) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null)) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) // While not listening, moving back to the bouncer does not dismiss the keyguard. - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer, null) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null)) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) @@ -782,10 +767,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // gone // scene now does dismiss the keyguard again. underTest.onViewAttached() - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Gone, null) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null)) runCurrent() verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java new file mode 100644 index 000000000000..ba3dbf0a0cb7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2020 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.keyguard; + +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.view.View; +import android.view.ViewTreeObserver; + +import com.android.internal.jank.InteractionJankMonitor; +import com.android.keyguard.logging.KeyguardLogger; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; +import com.android.systemui.statusbar.notification.AnimatableProperty; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.ScreenOffAnimationController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; + +import org.junit.Before; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { + + @Mock protected KeyguardStatusView mKeyguardStatusView; + @Mock protected KeyguardSliceViewController mKeyguardSliceViewController; + @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController; + @Mock protected KeyguardStateController mKeyguardStateController; + @Mock protected KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock protected ConfigurationController mConfigurationController; + @Mock protected DozeParameters mDozeParameters; + @Mock protected ScreenOffAnimationController mScreenOffAnimationController; + @Mock protected KeyguardLogger mKeyguardLogger; + @Mock protected KeyguardStatusViewController mControllerMock; + @Mock protected FeatureFlags mFeatureFlags; + @Mock protected InteractionJankMonitor mInteractionJankMonitor; + @Mock protected ViewTreeObserver mViewTreeObserver; + @Mock protected DumpManager mDumpManager; + protected FakeKeyguardRepository mFakeKeyguardRepository; + + protected KeyguardStatusViewController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + KeyguardInteractorFactory.WithDependencies deps = KeyguardInteractorFactory.create(); + mFakeKeyguardRepository = deps.getRepository(); + + mController = new KeyguardStatusViewController( + mKeyguardStatusView, + mKeyguardSliceViewController, + mKeyguardClockSwitchController, + mKeyguardStateController, + mKeyguardUpdateMonitor, + mConfigurationController, + mDozeParameters, + mScreenOffAnimationController, + mKeyguardLogger, + mFeatureFlags, + mInteractionJankMonitor, + deps.getKeyguardInteractor(), + mDumpManager) { + @Override + void setProperty( + AnimatableProperty property, + float value, + boolean animate) { + // Route into the mock version for verification + mControllerMock.setProperty(property, value, animate); + } + }; + + when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver); + } + + protected void givenViewAttached() { + ArgumentCaptor<View.OnAttachStateChangeListener> captor = + ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); + verify(mKeyguardStatusView, atLeast(1)).addOnAttachStateChangeListener(captor.capture()); + + for (View.OnAttachStateChangeListener listener : captor.getAllValues()) { + listener.onViewAttachedToWindow(mKeyguardStatusView); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 7114c22b5701..20d9ef1e86b1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -23,80 +23,21 @@ import static org.mockito.Mockito.when; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; -import com.android.internal.jank.InteractionJankMonitor; -import com.android.keyguard.logging.KeyguardLogger; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.ClockConfig; import com.android.systemui.plugins.ClockController; import com.android.systemui.statusbar.notification.AnimatableProperty; -import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; -import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; @SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) @RunWith(AndroidTestingRunner.class) -public class KeyguardStatusViewControllerTest extends SysuiTestCase { - - @Mock private KeyguardStatusView mKeyguardStatusView; - @Mock private KeyguardSliceViewController mKeyguardSliceViewController; - @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController; - @Mock private KeyguardStateController mKeyguardStateController; - @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock private ConfigurationController mConfigurationController; - @Mock private DozeParameters mDozeParameters; - @Mock private ScreenOffAnimationController mScreenOffAnimationController; - @Mock private KeyguardLogger mKeyguardLogger; - @Mock private KeyguardStatusViewController mControllerMock; - @Mock private FeatureFlags mFeatureFlags; - @Mock private InteractionJankMonitor mInteractionJankMonitor; - - @Mock private DumpManager mDumpManager; - - @Captor - private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor; - - private KeyguardStatusViewController mController; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - - mController = new KeyguardStatusViewController( - mKeyguardStatusView, - mKeyguardSliceViewController, - mKeyguardClockSwitchController, - mKeyguardStateController, - mKeyguardUpdateMonitor, - mConfigurationController, - mDozeParameters, - mScreenOffAnimationController, - mKeyguardLogger, - mFeatureFlags, - mInteractionJankMonitor, - mDumpManager) { - @Override - void setProperty( - AnimatableProperty property, - float value, - boolean animate) { - // Route into the mock version for verification - mControllerMock.setProperty(property, value, animate); - } - }; - } +public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest { @Test public void dozeTimeTick_updatesSlice() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt new file mode 100644 index 000000000000..2b9797ee56d4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt @@ -0,0 +1,71 @@ +/* + * 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.keyguard + +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import com.android.systemui.keyguard.shared.model.ScreenModel +import com.android.systemui.keyguard.shared.model.ScreenState +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class KeyguardStatusViewControllerWithCoroutinesTest : KeyguardStatusViewControllerBaseTest() { + + @Test + fun dozeTimeTickUpdatesSlices() = runTest { + mController.startCoroutines(coroutineContext) + givenViewAttached() + runCurrent() + clearInvocations(mKeyguardSliceViewController) + + mFakeKeyguardRepository.dozeTimeTick() + runCurrent() + verify(mKeyguardSliceViewController).refresh() + + coroutineContext.cancelChildren() + } + + @Test + fun onScreenTurningOnUpdatesSlices() = runTest { + mController.startCoroutines(coroutineContext) + givenViewAttached() + runCurrent() + clearInvocations(mKeyguardSliceViewController) + + mFakeKeyguardRepository.setScreenModel(ScreenModel(ScreenState.SCREEN_ON)) + runCurrent() + verify(mKeyguardSliceViewController, never()).refresh() + + // Should only be called during a 'turning on' event + mFakeKeyguardRepository.setScreenModel(ScreenModel(ScreenState.SCREEN_TURNING_ON)) + runCurrent() + verify(mKeyguardSliceViewController).refresh() + + coroutineContext.cancelChildren() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index 5867a40c78fa..44a2b682bf37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -20,12 +20,13 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import android.animation.ObjectAnimator; import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.core.animation.AnimatorTestRule; +import androidx.core.animation.ObjectAnimator; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; @@ -37,6 +38,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,6 +47,9 @@ import org.junit.runner.RunWith; @RunWithLooper public class ExpandHelperTest extends SysuiTestCase { + @Rule + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private ExpandableNotificationRow mRow; private ExpandHelper mExpandHelper; diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 58982d13481d..7ab8e8b229a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -23,6 +23,7 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.internal.util.FunctionalUtils.ThrowingConsumer; import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -61,6 +62,7 @@ import android.os.RemoteException; import android.os.VibrationAttributes; import android.testing.TestableLooper.RunWithLooper; import android.util.Pair; +import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; @@ -1128,6 +1130,36 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled_oneWayHapticsEnabled() + throws RemoteException { + when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // WHEN ACTION_HOVER is received + verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); + MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0); + mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent); + enterEvent.recycle(); + MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0); + mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent); + moveEvent.recycle(); + + // THEN context click haptic is played + verify(mVibrator).performHapticFeedback( + any(), + eq(HapticFeedbackConstants.CONTEXT_CLICK) + ); + } + + @Test public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException { // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isDisplayConfigured()).thenReturn(false); @@ -1160,6 +1192,35 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled__oneWayHapticsEnabled() + throws RemoteException { + when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // WHEN ACTION_DOWN is received + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); + mBiometricExecutor.runAllReady(); + moveEvent.recycle(); + + // THEN NO haptic played + verify(mVibrator, never()).performHapticFeedback(any(), anyInt()); + } + + @Test public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath() throws RemoteException { // Disable new touch detection. @@ -1514,4 +1575,45 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN is fingerDown should be FALSE assertFalse(mUdfpsController.isFingerDown()); } + + @Test + public void playHaptic_onAodInterrupt_oneWayHapticsDisabled_onAcquiredBad_usesVibrate() + throws RemoteException { + // GIVEN UDFPS overlay is showing + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // GIVEN there's been an AoD interrupt + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); + mScreenObserver.onScreenTurnedOn(); + mUdfpsController.onAodInterrupt(0, 0, 0, 0); + + // THEN vibrate is used + verify(mVibrator).vibrate( + anyInt(), + anyString(), + eq(UdfpsController.EFFECT_CLICK), + eq("aod-lock-icon-longpress"), + eq(UdfpsController.LOCK_ICON_VIBRATION_ATTRIBUTES) + ); + } + + @Test + public void playHaptic_onAodInterrupt_oneWayHapticsEnabled_onAcquiredBad_performHapticFeedback() + throws RemoteException { + when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); + // GIVEN UDFPS overlay is showing + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // GIVEN there's been an AoD interrupt + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); + mScreenObserver.onScreenTurnedOn(); + mUdfpsController.onAodInterrupt(0, 0, 0, 0); + + // THEN vibrate is used + verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 6babf0490ea9..14fc931522a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -69,13 +69,12 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) + underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) @@ -101,14 +100,13 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setAutoConfirmEnabled(true) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) + underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) underTest.clearMessage() @@ -138,13 +136,12 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) + underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.clearMessage() @@ -168,14 +165,13 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun passwordAuthMethod() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) + underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) @@ -201,14 +197,13 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun patternAuthMethod() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) + underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) @@ -239,13 +234,12 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_notLocked_switchesToGoneScene() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() - underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) + underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -253,12 +247,11 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) utils.authenticationRepository.setUnlocked(false) - underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) + underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -266,8 +259,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_customMessageShown() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password @@ -275,7 +267,7 @@ class BouncerInteractorTest : SysuiTestCase() { utils.authenticationRepository.setUnlocked(false) val customMessage = "Hello there!" - underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1, customMessage) + underTest.showOrUnlockDevice(customMessage) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(customMessage) @@ -287,11 +279,10 @@ class BouncerInteractorTest : SysuiTestCase() { val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) val message by collectLastValue(underTest.message) - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() - underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1) + underTest.showOrUnlockDevice() runCurrent() assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer) assertThat(isThrottled).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index b1533fecbc5e..1f089ca8b98e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -72,18 +72,14 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -96,18 +92,14 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onPasswordInputChanged() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -122,16 +114,12 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenCorrect() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("password") @@ -144,18 +132,14 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenWrong() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("wrong") @@ -170,18 +154,14 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_correctAfterWrong() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("wrong") diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index f69cbb8fd004..af54989002e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -75,8 +75,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -84,10 +83,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -101,8 +97,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragStart() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -110,10 +105,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -129,18 +121,14 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenCorrect() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() @@ -180,8 +168,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenWrong() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -189,10 +176,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() @@ -216,8 +200,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_correctAfterWrong() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -225,10 +208,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 8edc6cf8dd54..c12ed033d752 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -75,15 +75,11 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -96,16 +92,12 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onPinButtonClicked() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -120,16 +112,12 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onBackspaceButtonClicked() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -146,15 +134,11 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onPinEdit() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -172,16 +156,12 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onBackspaceButtonLongPressed() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -200,14 +180,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenCorrect() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -222,16 +198,12 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenWrong() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -250,16 +222,12 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_correctAfterWrong() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -286,15 +254,11 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenCorrect() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) utils.authenticationRepository.setAutoConfirmEnabled(true) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -307,17 +271,13 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenWrong() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) utils.authenticationRepository.setAutoConfirmEnabled(true) - sceneInteractor.setCurrentScene( - SceneTestUtils.CONTAINER_1, - SceneModel(SceneKey.Bouncer) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit -> diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt index 38372a33f1e6..692d794f21f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt @@ -24,7 +24,6 @@ import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.settings.ControlsSettingsDialogManager import com.android.systemui.controls.settings.FakeControlsSettingsRepository import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController @@ -36,7 +35,6 @@ import org.junit.runner.RunWith import org.mockito.Answers import org.mockito.Mock import org.mockito.Mockito -import org.mockito.Mockito.`when` import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.doNothing import org.mockito.Mockito.doReturn @@ -44,6 +42,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.spy import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import java.util.Optional @@ -102,14 +101,11 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { metricsLogger, vibratorHelper, controlsSettingsRepository, - controlsSettingsDialogManager, - featureFlags )) coordinator.activityContext = mContext `when`(cvh.cws.ci.controlId).thenReturn(ID) `when`(cvh.cws.control?.isAuthRequired()).thenReturn(true) - `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(false) action = spy(coordinator.Action(ID, {}, false, true)) doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean()) @@ -155,13 +151,11 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { coordinator.toggle(cvh, "", true) verify(coordinator).bouncerOrRun(action) - verify(controlsSettingsDialogManager).maybeShowDialog(any(), any()) verify(action).invoke() } @Test fun testToggleWhenLockedDoesNotTriggerDialog_featureFlagEnabled() { - `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(true) action = spy(coordinator.Action(ID, {}, false, false)) doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt index 71d2ec152e5a..bd3d09dfbb20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt @@ -16,8 +16,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.CustomIconCache import com.android.systemui.controls.controller.ControlsControllerImpl -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -46,7 +44,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { } private val uiExecutor = FakeExecutor(FakeSystemClock()) - private val featureFlags = FakeFeatureFlags() @Mock lateinit var controller: ControlsControllerImpl @@ -65,7 +62,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { ActivityTestRule( /* activityFactory= */ SingleActivityFactory { TestableControlsEditingActivity( - featureFlags, uiExecutor, controller, userTracker, @@ -81,8 +77,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false) } @Test @@ -101,16 +95,7 @@ class ControlsEditingActivityTest : SysuiTestCase() { } @Test - fun testNewFlowDisabled_addControlsButton_gone() { - with(launchActivity()) { - val addControlsButton = requireViewById<Button>(R.id.addControls) - assertThat(addControlsButton.visibility).isEqualTo(View.GONE) - } - } - - @Test - fun testNewFlowEnabled_addControlsButton_visible() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) + fun testAddControlsButton_visible() { with(launchActivity()) { val addControlsButton = requireViewById<Button>(R.id.addControls) assertThat(addControlsButton.visibility).isEqualTo(View.VISIBLE) @@ -120,7 +105,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { @Test fun testNotLaunchFromFavoriting_saveButton_disabled() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) with(launchActivity(isFromFavoriting = false)) { val saveButton = requireViewById<Button>(R.id.done) assertThat(saveButton.isEnabled).isFalse() @@ -129,7 +113,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { @Test fun testLaunchFromFavoriting_saveButton_enabled() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) with(launchActivity(isFromFavoriting = true)) { val saveButton = requireViewById<Button>(R.id.done) assertThat(saveButton.isEnabled).isTrue() @@ -138,7 +121,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { @Test fun testNotFromFavoriting_addControlsPressed_launchesFavouriting() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) with(launchActivity(isFromFavoriting = false)) { val addControls = requireViewById<Button>(R.id.addControls) @@ -177,7 +159,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { ) class TestableControlsEditingActivity( - featureFlags: FakeFeatureFlags, executor: FakeExecutor, controller: ControlsControllerImpl, userTracker: UserTracker, @@ -186,7 +167,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { private val latch: CountDownLatch ) : ControlsEditingActivity( - featureFlags, executor, controller, userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt index f11c296ad572..70d93a10b445 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt @@ -17,14 +17,11 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.ControlStatus -import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.createLoadDataObject import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture @@ -73,8 +70,6 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @Mock lateinit var controller: ControlsControllerImpl - @Mock lateinit var listingController: ControlsListingController - @Mock lateinit var userTracker: UserTracker private var latch: CountDownLatch = CountDownLatch(1) @@ -82,9 +77,6 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback> @Captor - private lateinit var listingCallback: - ArgumentCaptor<ControlsListingController.ControlsListingCallback> - @Captor private lateinit var controlsCallback: ArgumentCaptor<Consumer<ControlsController.LoadData>> @Rule @@ -93,10 +85,8 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { ActivityTestRule( /* activityFactory= */ SingleActivityFactory { TestableControlsFavoritingActivity( - featureFlags, executor, controller, - listingController, userTracker, mockDispatcher, latch @@ -109,7 +99,6 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false) } // b/259549854 to root-cause and fix @@ -130,14 +119,8 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { } @Test - fun testNewFlowEnabled_buttons() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) + fun testButtons() { with(launchActivity()) { - verify(listingController).addCallback(listingCallback.capture()) - listingCallback.value.onServicesUpdated( - listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java)) - ) - val rearrangeButton = requireViewById<Button>(R.id.rearrange) assertThat(rearrangeButton.visibility).isEqualTo(View.VISIBLE) assertThat(rearrangeButton.isEnabled).isFalse() @@ -149,36 +132,8 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { } @Test - fun testNewFlowDisabled_buttons() { - with(launchActivity()) { - verify(listingController).addCallback(listingCallback.capture()) - activityRule.runOnUiThread { - listingCallback.value.onServicesUpdated( - listOf( - mock(ControlsServiceInfo::class.java), - mock(ControlsServiceInfo::class.java) - ) - ) - } - - val rearrangeButton = requireViewById<Button>(R.id.rearrange) - assertThat(rearrangeButton.visibility).isEqualTo(View.GONE) - assertThat(rearrangeButton.isEnabled).isFalse() - - val otherAppsButton = requireViewById<Button>(R.id.other_apps) - otherAppsButton.waitForPost() - assertThat(otherAppsButton.visibility).isEqualTo(View.VISIBLE) - } - } - - @Test - fun testNewFlowEnabled_rearrangePressed_savesAndlaunchesActivity() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) + fun testRearrangePressed_savesAndlaunchesActivity() { with(launchActivity()) { - verify(listingController).addCallback(capture(listingCallback)) - listingCallback.value.onServicesUpdated( - listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java)) - ) verify(controller).loadForComponent(any(), capture(controlsCallback), any()) activityRule.runOnUiThread { controlsCallback.value.accept( @@ -224,19 +179,15 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { ) class TestableControlsFavoritingActivity( - featureFlags: FeatureFlags, executor: Executor, controller: ControlsControllerImpl, - listingController: ControlsListingController, userTracker: UserTracker, private val mockDispatcher: OnBackInvokedDispatcher, private val latch: CountDownLatch ) : ControlsFavoritingActivity( - featureFlags, executor, controller, - listingController, userTracker, ) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt index ee213f7c4257..b1061ba76c14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt @@ -37,7 +37,6 @@ import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED -import com.android.systemui.flags.Flags.USE_APP_PANELS import com.android.systemui.settings.UserTracker import com.android.systemui.util.ActivityTaskManagerProxy import com.android.systemui.util.concurrency.FakeExecutor @@ -124,8 +123,6 @@ class ControlsListingControllerImplTest : SysuiTestCase() { arrayOf(componentName.packageName) ) - // Return true by default, we'll test the false path - `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true) // Return false by default, we'll test the true path `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(false) @@ -445,34 +442,6 @@ class ControlsListingControllerImplTest : SysuiTestCase() { } @Test - fun testActivityDefaultEnabled_flagDisabled_nullPanel() { - `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(false) - val serviceInfo = ServiceInfo( - componentName, - activityName, - ) - - `when`(packageManager.getComponentEnabledSetting(eq(activityName))) - .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) - - setUpQueryResult(listOf( - ActivityInfo( - activityName, - enabled = true, - exported = true, - permission = Manifest.permission.BIND_CONTROLS - ) - )) - - val list = listOf(serviceInfo) - serviceListingCallbackCaptor.value.onServicesReloaded(list) - - executor.runAllReady() - - assertNull(controller.getCurrentServices()[0].panelActivity) - } - - @Test fun testActivityDifferentPackage_nullPanel() { val serviceInfo = ServiceInfo( componentName, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 0ffa2d7b970e..d8d3f92911ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -275,7 +275,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { TestableLooper.get(this).processAllMessages(); mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER); - mViewMediator.onWakeAndUnlocking(); + mViewMediator.onWakeAndUnlocking(false); mViewMediator.onStartedWakingUp(OFF_BECAUSE_OF_USER, false); TestableLooper.get(this).processAllMessages(); @@ -337,6 +337,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.onSystemReady(); TestableLooper.get(this).processAllMessages(); + when(mPowerManager.isInteractive()).thenReturn(true); + // Given device is dreaming when(mUpdateMonitor.isDreaming()).thenReturn(true); @@ -707,14 +709,14 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @Test public void testWakeAndUnlocking() { - mViewMediator.onWakeAndUnlocking(); + mViewMediator.onWakeAndUnlocking(false); verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); } @Test public void testWakeAndUnlockingOverDream() { // Send signal to wake - mViewMediator.onWakeAndUnlocking(); + mViewMediator.onWakeAndUnlocking(true); // Ensure not woken up yet verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); @@ -743,7 +745,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @Test public void testWakeAndUnlockingOverDream_signalAuthenticateIfStillShowing() { // Send signal to wake - mViewMediator.onWakeAndUnlocking(); + mViewMediator.onWakeAndUnlocking(true); // Ensure not woken up yet verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); @@ -773,6 +775,35 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } @Test + public void testWakeAndUnlockingOverNonInteractiveDream_noWakeByKeyguardViewMediator() { + // Send signal to wake + mViewMediator.onWakeAndUnlocking(false); + + // Ensure not woken up yet + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + + // Verify keyguard told of authentication + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); + mViewMediator.mViewMediatorCallback.keyguardDonePending(true, + mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); + final ArgumentCaptor<Runnable> animationRunnableCaptor = + ArgumentCaptor.forClass(Runnable.class); + verify(mStatusBarKeyguardViewManager).startPreHideAnimation( + animationRunnableCaptor.capture()); + + when(mStatusBarStateController.isDreaming()).thenReturn(true); + when(mStatusBarStateController.isDozing()).thenReturn(false); + animationRunnableCaptor.getValue().run(); + + when(mKeyguardStateController.isShowing()).thenReturn(false); + mViewMediator.mViewMediatorCallback.keyguardGone(); + + // Verify not woken up. + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + } + + @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) public void testDoKeyguardWhileInteractive_resets() { mViewMediator.setShowingLocked(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 01a6c64a6898..85ee0e4d6ec3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -541,10 +541,8 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun authenticateDoesNotRunIfUserIsCurrentlySwitching() = - testScope.runTest { - testGatingCheckForFaceAuth { fakeUserRepository.setUserSwitching(true) } - } + fun authenticateDoesNotRunIfFaceAuthIsCurrentlyPaused() = + testScope.runTest { testGatingCheckForFaceAuth { underTest.pauseFaceAuth() } } @Test fun authenticateDoesNotRunIfKeyguardIsNotShowing() = @@ -840,7 +838,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { @Test fun detectDoesNotRunWhenUserSwitchingInProgress() = - testScope.runTest { testGatingCheckForDetect { fakeUserRepository.setUserSwitching(true) } } + testScope.runTest { testGatingCheckForDetect { underTest.pauseFaceAuth() } } @Test fun detectDoesNotRunWhenKeyguardGoingAway() = @@ -1130,7 +1128,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { .addLockoutResetCallback(faceLockoutResetCallback.capture()) biometricSettingsRepository.setFaceEnrolled(true) biometricSettingsRepository.setIsFaceAuthEnabled(true) - fakeUserRepository.setUserSwitching(false) + underTest.resumeFaceAuth() trustRepository.setCurrentUserTrusted(false) keyguardRepository.setKeyguardGoingAway(false) keyguardRepository.setWakefulnessModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index ba7d3490e56d..5e3376a45488 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -49,6 +49,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn @@ -85,13 +86,14 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { private val mainDispatcher = StandardTestDispatcher() private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) + private lateinit var systemClock: FakeSystemClock private lateinit var underTest: KeyguardRepositoryImpl @Before fun setUp() { MockitoAnnotations.initMocks(this) - + systemClock = FakeSystemClock() underTest = KeyguardRepositoryImpl( statusBarStateController, @@ -107,6 +109,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { dreamOverlayCallbackController, mainDispatcher, testScope.backgroundScope, + systemClock, ) } @@ -167,11 +170,15 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Test fun dozeTimeTick() = testScope.runTest { - var dozeTimeTickValue = collectLastValue(underTest.dozeTimeTick) + val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick) + assertThat(lastDozeTimeTick).isEqualTo(0L) + + // WHEN dozeTimeTick updated + systemClock.setUptimeMillis(systemClock.uptimeMillis() + 5) underTest.dozeTimeTick() - runCurrent() - assertThat(dozeTimeTickValue()).isNull() + // THEN listeners were updated to the latest uptime millis + assertThat(systemClock.uptimeMillis()).isEqualTo(lastDozeTimeTick) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt index 069a4862ccdc..630826954057 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt @@ -23,10 +23,9 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat -import junit.framework.Assert.assertEquals import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -48,7 +47,8 @@ class BurnInInteractorTest : SysuiTestCase() { @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper private lateinit var configurationRepository: FakeConfigurationRepository - private lateinit var systemClock: FakeSystemClock + private lateinit var keyguardInteractor: KeyguardInteractor + private lateinit var fakeKeyguardRepository: FakeKeyguardRepository private lateinit var testScope: TestScope private lateinit var underTest: BurnInInteractor @@ -56,8 +56,10 @@ class BurnInInteractorTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) configurationRepository = FakeConfigurationRepository() - systemClock = FakeSystemClock() - + KeyguardInteractorFactory.create().let { + keyguardInteractor = it.keyguardInteractor + fakeKeyguardRepository = it.repository + } whenever(burnInHelperWrapper.burnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset) setBurnInProgress(.65f) @@ -68,26 +70,11 @@ class BurnInInteractorTest : SysuiTestCase() { burnInHelperWrapper, testScope.backgroundScope, configurationRepository, - systemClock, + keyguardInteractor, ) } @Test - fun dozeTimeTick_updatesOnDozeTimeTick() = - testScope.runTest { - // Initial state set to 0 - val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick) - assertEquals(0L, lastDozeTimeTick) - - // WHEN dozeTimeTick updated - incrementUptimeMillis() - underTest.dozeTimeTick() - - // THEN listeners were updated to the latest uptime millis - assertThat(systemClock.uptimeMillis()).isEqualTo(lastDozeTimeTick) - } - - @Test fun udfpsBurnInOffset_updatesOnResolutionScaleChange() = testScope.runTest { val udfpsBurnInOffsetX by collectLastValue(underTest.udfpsBurnInXOffset) @@ -111,25 +98,18 @@ class BurnInInteractorTest : SysuiTestCase() { assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress) setBurnInProgress(.88f) - incrementUptimeMillis() - underTest.dozeTimeTick() + fakeKeyguardRepository.dozeTimeTick(10) assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress) setBurnInProgress(.92f) - incrementUptimeMillis() - underTest.dozeTimeTick() + fakeKeyguardRepository.dozeTimeTick(20) assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress) setBurnInProgress(.32f) - incrementUptimeMillis() - underTest.dozeTimeTick() + fakeKeyguardRepository.dozeTimeTick(30) assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress) } - private fun incrementUptimeMillis() { - systemClock.setUptimeMillis(systemClock.uptimeMillis() + 5) - } - private fun setBurnInProgress(progress: Float) { burnInProgress = progress whenever(burnInHelperWrapper.burnInProgressOffset()).thenReturn(burnInProgress) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 8636dd8df3b0..93f208ee14f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -48,6 +48,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -74,6 +75,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository + private lateinit var fakeUserRepository: FakeUserRepository private lateinit var fakeDeviceEntryFingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository @@ -98,6 +100,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { .keyguardTransitionInteractor fakeDeviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() + fakeUserRepository = FakeUserRepository() underTest = SystemUIKeyguardFaceAuthInteractor( mContext, @@ -131,7 +134,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { featureFlags, FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")), keyguardUpdateMonitor, - fakeDeviceEntryFingerprintAuthRepository + fakeDeviceEntryFingerprintAuthRepository, + fakeUserRepository, ) } @@ -212,6 +216,38 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { } @Test + fun faceAuthIsPausedWhenUserSwitchingIsInProgress() = + testScope.runTest { + underTest.start() + + fakeUserRepository.setUserSwitching(false) + runCurrent() + fakeUserRepository.setUserSwitching(true) + runCurrent() + + assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue() + } + + @Test + fun faceAuthIsUnpausedWhenUserSwitchingIsInComplete() = + testScope.runTest { + underTest.start() + + // previously running + fakeUserRepository.setUserSwitching(true) + runCurrent() + fakeUserRepository.setUserSwitching(false) + runCurrent() + + assertThat(faceAuthRepository.isFaceAuthPaused()).isFalse() + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value!!.first) + .isEqualTo(FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING) + assertThat(faceAuthRepository.runningAuthRequest.value!!.second).isEqualTo(true) + } + + @Test fun faceAuthIsRequestedWhenPrimaryBouncerIsVisible() = testScope.runTest { underTest.start() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index d01a46e06b9b..daf5ce691df0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -89,6 +89,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { FromAlternateBouncerTransitionInteractor private lateinit var fromPrimaryBouncerTransitionInteractor: FromPrimaryBouncerTransitionInteractor + private lateinit var fromDreamingLockscreenHostedTransitionInteractor: + FromDreamingLockscreenHostedTransitionInteractor @Before fun setUp() { @@ -140,6 +142,15 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { ) .apply { start() } + fromDreamingLockscreenHostedTransitionInteractor = + FromDreamingLockscreenHostedTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + fromAodTransitionInteractor = FromAodTransitionInteractor( scope = testScope, @@ -299,6 +310,38 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun lockscreenToDreamingLockscreenHosted() = + testScope.runTest { + // GIVEN a device that is not dreaming or dozing + keyguardRepository.setDreamingWithOverlay(false) + keyguardRepository.setWakefulnessModel(startingToWake()) + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + runCurrent() + + // GIVEN a prior transition has run to LOCKSCREEN + runTransition(KeyguardState.GONE, KeyguardState.LOCKSCREEN) + + // WHEN the device begins to dream and the dream is lockscreen hosted + keyguardRepository.setDreamingWithOverlay(true) + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + advanceUntilIdle() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur + assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun lockscreenToDozing() = testScope.runTest { // GIVEN a device with AOD not available @@ -353,6 +396,149 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun dreamingLockscreenHostedToLockscreen() = + testScope.runTest { + // GIVEN a device dreaming with the lockscreen hosted dream and not dozing + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + keyguardRepository.setWakefulnessModel(startingToWake()) + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + runCurrent() + + // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED + runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + + // WHEN the lockscreen hosted dream stops + keyguardRepository.setIsActiveDreamLockscreenHosted(false) + advanceUntilIdle() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to Lockscreen should occur + assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun dreamingLockscreenHostedToGone() = + testScope.runTest { + // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED + runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + + // WHEN biometrics succeeds with wake and unlock from dream mode + keyguardRepository.setBiometricUnlockState( + BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM + ) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to Gone should occur + assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + assertThat(info.to).isEqualTo(KeyguardState.GONE) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun dreamingLockscreenHostedToPrimaryBouncer() = + testScope.runTest { + // GIVEN a device dreaming with lockscreen hosted dream and not dozing + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + keyguardRepository.setWakefulnessModel(startingToWake()) + runCurrent() + + // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED + runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + + // WHEN the primary bouncer is set to show + bouncerRepository.setPrimaryShow(true) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to PRIMARY_BOUNCER should occur + assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun dreamingLockscreenHostedToDozing() = + testScope.runTest { + // GIVEN a device is dreaming with lockscreen hosted dream + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + runCurrent() + + // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED + runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + + // WHEN the device begins to sleep + keyguardRepository.setIsActiveDreamLockscreenHosted(false) + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.INITIALIZED, to = DozeStateModel.DOZE) + ) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to DOZING should occur + assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + assertThat(info.to).isEqualTo(KeyguardState.DOZING) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun dreamingLockscreenHostedToOccluded() = + testScope.runTest { + // GIVEN device is dreaming with lockscreen hosted dream and not occluded + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + keyguardRepository.setKeyguardOccluded(false) + runCurrent() + + // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED + runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + + // WHEN the keyguard is occluded and the lockscreen hosted dream stops + keyguardRepository.setIsActiveDreamLockscreenHosted(false) + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun dozingToLockscreen() = testScope.runTest { // GIVEN a prior transition has run to DOZING @@ -533,6 +719,38 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun goneToDreamingLockscreenHosted() = + testScope.runTest { + // GIVEN a device that is not dreaming or dozing + keyguardRepository.setDreamingWithOverlay(false) + keyguardRepository.setWakefulnessModel(startingToWake()) + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + runCurrent() + + // GIVEN a prior transition has run to GONE + runTransition(KeyguardState.LOCKSCREEN, KeyguardState.GONE) + + // WHEN the device begins to dream with the lockscreen hosted dream + keyguardRepository.setDreamingWithOverlay(true) + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + advanceUntilIdle() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur + assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.GONE) + assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun alternateBouncerToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -726,6 +944,34 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun primaryBouncerToDreamingLockscreenHosted() = + testScope.runTest { + // GIVEN device dreaming with the lockscreen hosted dream and not dozing + keyguardRepository.setIsActiveDreamLockscreenHosted(true) + keyguardRepository.setWakefulnessModel(startingToWake()) + + // GIVEN a prior transition has run to PRIMARY_BOUNCER + bouncerRepository.setPrimaryShow(true) + runTransition(KeyguardState.DREAMING_LOCKSCREEN_HOSTED, KeyguardState.PRIMARY_BOUNCER) + + // WHEN the primary bouncer stops showing and lockscreen hosted dream still active + bouncerRepository.setPrimaryShow(false) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur + assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER) + assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun occludedToGone() = testScope.runTest { // GIVEN a device on lockscreen diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt index ca6a5b6234b9..d825c2a01464 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt @@ -21,7 +21,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1 import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat @@ -91,7 +90,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { @Test fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setUnlocked(false) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) @@ -104,7 +103,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { @Test fun dismissLockScreen_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setUnlocked(true) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) @@ -117,7 +116,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { @Test fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setUnlocked(false) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) @@ -131,11 +130,11 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() = testScope.runTest { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) - sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen)) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Lockscreen)) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(isUnlocked).isFalse() - sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone)) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone)) assertThat(isUnlocked).isFalse() } @@ -145,13 +144,13 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { testScope.runTest { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) runCurrent() - sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Shade)) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade)) runCurrent() utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) runCurrent() assertThat(isUnlocked).isFalse() - sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone)) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone)) assertThat(isUnlocked).isFalse() } @@ -159,10 +158,10 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { @Test fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) runCurrent() - sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.QuickSettings)) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.QuickSettings)) runCurrent() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt index b019a21387da..6efec99ba834 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt @@ -38,7 +38,6 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -68,6 +67,7 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { private lateinit var featureFlags: FakeFeatureFlags private lateinit var burnInInteractor: BurnInInteractor private lateinit var shadeRepository: FakeShadeRepository + private lateinit var keyguardInteractor: KeyguardInteractor @Mock private lateinit var burnInHelper: BurnInHelperWrapper @Mock private lateinit var dialogManager: SystemUIDialogManager @@ -79,35 +79,32 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testScope = TestScope() configRepository = FakeConfigurationRepository() - keyguardRepository = FakeKeyguardRepository() - bouncerRepository = FakeKeyguardBouncerRepository() - shadeRepository = FakeShadeRepository() - fakeCommandQueue = FakeCommandQueue() featureFlags = FakeFeatureFlags().apply { set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) set(Flags.FACE_AUTH_REFACTOR, false) } + KeyguardInteractorFactory.create(featureFlags = featureFlags).let { + keyguardInteractor = it.keyguardInteractor + keyguardRepository = it.repository + } + bouncerRepository = FakeKeyguardBouncerRepository() + shadeRepository = FakeShadeRepository() + fakeCommandQueue = FakeCommandQueue() burnInInteractor = BurnInInteractor( context, burnInHelper, testScope.backgroundScope, configRepository, - FakeSystemClock(), + keyguardInteractor ) underTest = UdfpsKeyguardInteractor( configRepository, burnInInteractor, - KeyguardInteractor( - keyguardRepository, - fakeCommandQueue, - featureFlags, - bouncerRepository, - configRepository, - ), + keyguardInteractor, shadeRepository, dialogManager, ) @@ -215,7 +212,7 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { private fun setAwake() { keyguardRepository.setDozeAmount(0f) - burnInInteractor.dozeTimeTick() + keyguardRepository.dozeTimeTick() bouncerRepository.setAlternateVisible(false) keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index ba8e0f277b6b..63ee240fd2c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -22,9 +22,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1 import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat @@ -51,20 +49,15 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private val underTest = LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, - interactorFactory = - object : LockscreenSceneInteractor.Factory { - override fun create(containerName: String): LockscreenSceneInteractor { - return utils.lockScreenSceneInteractor( + interactor = + utils.lockScreenSceneInteractor( + authenticationInteractor = authenticationInteractor, + bouncerInteractor = + utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), - ) - } - }, - containerName = CONTAINER_1 + sceneInteractor = sceneInteractor, + ), + ), ) @Test @@ -116,7 +109,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() @@ -129,7 +122,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -142,7 +135,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() @@ -155,7 +148,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onLockButtonClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt index b985b3ca83da..bd17de3ee939 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt @@ -20,21 +20,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase -import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.phone.SystemUIDialogManager -import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -58,9 +56,9 @@ class UdfpsAodViewModelTest : SysuiTestCase() { private lateinit var configRepository: FakeConfigurationRepository private lateinit var bouncerRepository: KeyguardBouncerRepository private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var fakeCommandQueue: FakeCommandQueue private lateinit var featureFlags: FakeFeatureFlags private lateinit var shadeRepository: FakeShadeRepository + private lateinit var keyguardInteractor: KeyguardInteractor @Mock private lateinit var dialogManager: SystemUIDialogManager @Mock private lateinit var burnInHelper: BurnInHelperWrapper @@ -70,17 +68,21 @@ class UdfpsAodViewModelTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding) testScope = TestScope() - configRepository = FakeConfigurationRepository() - keyguardRepository = FakeKeyguardRepository() - bouncerRepository = FakeKeyguardBouncerRepository() - fakeCommandQueue = FakeCommandQueue() shadeRepository = FakeShadeRepository() featureFlags = FakeFeatureFlags().apply { set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) set(Flags.FACE_AUTH_REFACTOR, false) } - + KeyguardInteractorFactory.create( + featureFlags = featureFlags, + ) + .also { + keyguardInteractor = it.keyguardInteractor + keyguardRepository = it.repository + configRepository = it.configurationRepository + bouncerRepository = it.bouncerRepository + } val udfpsKeyguardInteractor = UdfpsKeyguardInteractor( configRepository, @@ -89,15 +91,9 @@ class UdfpsAodViewModelTest : SysuiTestCase() { burnInHelper, testScope.backgroundScope, configRepository, - FakeSystemClock(), - ), - KeyguardInteractor( - keyguardRepository, - fakeCommandQueue, - featureFlags, - bouncerRepository, - configRepository, + keyguardInteractor, ), + keyguardInteractor, shadeRepository, dialogManager, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt index 0fbcec23f247..80ab418fbd30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt @@ -30,12 +30,11 @@ import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.phone.SystemUIDialogManager -import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -90,14 +89,10 @@ class UdfpsFingerprintViewModelTest : SysuiTestCase() { testScope.backgroundScope, ) val keyguardInteractor = - KeyguardInteractor( - keyguardRepository, - fakeCommandQueue, - featureFlags, - bouncerRepository, - configRepository, - ) - + KeyguardInteractorFactory.create( + featureFlags = featureFlags, + ) + .keyguardInteractor underTest = FingerprintViewModel( context, @@ -109,7 +104,7 @@ class UdfpsFingerprintViewModelTest : SysuiTestCase() { burnInHelper, testScope.backgroundScope, configRepository, - FakeSystemClock(), + keyguardInteractor, ), keyguardInteractor, shadeRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt index 41ae93183850..0456824abfbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -39,7 +40,6 @@ import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock -import com.android.systemui.util.time.FakeSystemClock import com.android.wm.shell.animation.Interpolators import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat @@ -72,6 +72,7 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() { private lateinit var transitionRepository: FakeKeyguardTransitionRepository private lateinit var configRepository: FakeConfigurationRepository private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var keyguardInteractor: KeyguardInteractor private lateinit var bouncerRepository: FakeKeyguardBouncerRepository private lateinit var shadeRepository: FakeShadeRepository private lateinit var featureFlags: FakeFeatureFlags @@ -81,23 +82,21 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testScope = TestScope() transitionRepository = FakeKeyguardTransitionRepository() - configRepository = FakeConfigurationRepository() - keyguardRepository = FakeKeyguardRepository() - bouncerRepository = FakeKeyguardBouncerRepository() shadeRepository = FakeShadeRepository() featureFlags = FakeFeatureFlags().apply { set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) set(Flags.FACE_AUTH_REFACTOR, false) } - val keyguardInteractor = - KeyguardInteractor( - keyguardRepository, - commandQueue = mock(), - featureFlags, - bouncerRepository, - configRepository, - ) + KeyguardInteractorFactory.create( + featureFlags = featureFlags, + ) + .also { + keyguardInteractor = it.keyguardInteractor + keyguardRepository = it.repository + configRepository = it.configurationRepository + bouncerRepository = it.bouncerRepository + } underTest = UdfpsLockscreenViewModel( @@ -115,7 +114,7 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() { burnInHelperWrapper = mock(), testScope.backgroundScope, configRepository, - FakeSystemClock(), + keyguardInteractor, ), keyguardInteractor, shadeRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index c65a2d36e223..d933b57e8e15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -702,9 +702,10 @@ internal class NoteTaskControllerTest : SysuiTestCase() { // region updateNoteTaskAsUser @Test fun updateNoteTaskAsUser_sameUser_shouldUpdateShortcuts() { - val user = userTracker.userHandle + val user = UserHandle.CURRENT val controller = spy(createNoteTaskController()) doNothing().whenever(controller).updateNoteTaskAsUserInternal(any()) + whenever(controller.getCurrentRunningUser()).thenReturn(user) controller.updateNoteTaskAsUser(user) @@ -714,10 +715,10 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Test fun updateNoteTaskAsUser_differentUser_shouldUpdateShortcutsInUserProcess() { - // FakeUserTracker will default to UserHandle.SYSTEM. val user = UserHandle.CURRENT val controller = spy(createNoteTaskController(isEnabled = true)) doNothing().whenever(controller).updateNoteTaskAsUserInternal(any()) + whenever(controller.getCurrentRunningUser()).thenReturn(UserHandle.SYSTEM) controller.updateNoteTaskAsUser(user) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index ed7a59ea7032..ee42a7011264 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -20,9 +20,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1 import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat @@ -48,26 +46,21 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { private val underTest = QuickSettingsSceneViewModel( - lockscreenSceneInteractorFactory = - object : LockscreenSceneInteractor.Factory { - override fun create(containerName: String): LockscreenSceneInteractor { - return utils.lockScreenSceneInteractor( + lockscreenSceneInteractor = + utils.lockScreenSceneInteractor( + authenticationInteractor = authenticationInteractor, + bouncerInteractor = + utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), - ) - } - }, - containerName = CONTAINER_1 + sceneInteractor = sceneInteractor, + ), + ), ) @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -80,7 +73,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 9ce378dd079f..826a6ccfbaec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -41,7 +41,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { @Test fun allSceneKeys() { val underTest = utils.fakeSceneContainerRepository() - assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1)) + assertThat(underTest.allSceneKeys()) .isEqualTo( listOf( SceneKey.QuickSettings, @@ -53,115 +53,58 @@ class SceneContainerRepositoryTest : SysuiTestCase() { ) } - @Test(expected = IllegalStateException::class) - fun allSceneKeys_noSuchContainer_throws() { - val underTest = utils.fakeSceneContainerRepository() - underTest.allSceneKeys("nonExistingContainer") - } - @Test fun currentScene() = runTest { val underTest = utils.fakeSceneContainerRepository() - val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade)) + underTest.setCurrentScene(SceneModel(SceneKey.Shade)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) } @Test(expected = IllegalStateException::class) - fun currentScene_noSuchContainer_throws() { - val underTest = utils.fakeSceneContainerRepository() - underTest.currentScene("nonExistingContainer") - } - - @Test(expected = IllegalStateException::class) - fun setCurrentScene_noSuchContainer_throws() { - val underTest = utils.fakeSceneContainerRepository() - underTest.setCurrentScene("nonExistingContainer", SceneModel(SceneKey.Shade)) - } - - @Test(expected = IllegalStateException::class) fun setCurrentScene_noSuchSceneInContainer_throws() { val underTest = utils.fakeSceneContainerRepository( - setOf( - utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1), - utils.fakeSceneContainerConfig( - SceneTestUtils.CONTAINER_2, - listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) - ), - ) + utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), ) - underTest.setCurrentScene(SceneTestUtils.CONTAINER_2, SceneModel(SceneKey.Shade)) + underTest.setCurrentScene(SceneModel(SceneKey.Shade)) } @Test fun isVisible() = runTest { val underTest = utils.fakeSceneContainerRepository() - val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1)) + val isVisible by collectLastValue(underTest.isVisible) assertThat(isVisible).isTrue() - underTest.setVisible(SceneTestUtils.CONTAINER_1, false) + underTest.setVisible(false) assertThat(isVisible).isFalse() - underTest.setVisible(SceneTestUtils.CONTAINER_1, true) + underTest.setVisible(true) assertThat(isVisible).isTrue() } - @Test(expected = IllegalStateException::class) - fun isVisible_noSuchContainer_throws() { - val underTest = utils.fakeSceneContainerRepository() - underTest.isVisible("nonExistingContainer") - } - - @Test(expected = IllegalStateException::class) - fun setVisible_noSuchContainer_throws() { - val underTest = utils.fakeSceneContainerRepository() - underTest.setVisible("nonExistingContainer", false) - } - @Test - fun sceneTransitionProgress() = runTest { + fun transitionProgress() = runTest { val underTest = utils.fakeSceneContainerRepository() - val sceneTransitionProgress by - collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1)) + val sceneTransitionProgress by collectLastValue(underTest.transitionProgress) assertThat(sceneTransitionProgress).isEqualTo(1f) - underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.1f) + underTest.setSceneTransitionProgress(0.1f) assertThat(sceneTransitionProgress).isEqualTo(0.1f) - underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.9f) + underTest.setSceneTransitionProgress(0.9f) assertThat(sceneTransitionProgress).isEqualTo(0.9f) } - @Test(expected = IllegalStateException::class) - fun sceneTransitionProgress_noSuchContainer_throws() { - val underTest = utils.fakeSceneContainerRepository() - underTest.sceneTransitionProgress("nonExistingContainer") - } - @Test fun setSceneTransition() = runTest { - val underTest = - utils.fakeSceneContainerRepository( - setOf( - utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1), - utils.fakeSceneContainerConfig( - SceneTestUtils.CONTAINER_2, - listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) - ), - ) - ) - val sceneTransition by - collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_2)) + val underTest = utils.fakeSceneContainerRepository() + val sceneTransition by collectLastValue(underTest.transitions) assertThat(sceneTransition).isNull() - underTest.setSceneTransition( - SceneTestUtils.CONTAINER_2, - SceneKey.Lockscreen, - SceneKey.QuickSettings - ) + underTest.setSceneTransition(SceneKey.Lockscreen, SceneKey.QuickSettings) assertThat(sceneTransition) .isEqualTo( SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings) @@ -169,46 +112,20 @@ class SceneContainerRepositoryTest : SysuiTestCase() { } @Test(expected = IllegalStateException::class) - fun setSceneTransition_noSuchContainer_throws() { - val underTest = utils.fakeSceneContainerRepository() - underTest.setSceneTransition("nonExistingContainer", SceneKey.Lockscreen, SceneKey.Shade) - } - - @Test(expected = IllegalStateException::class) fun setSceneTransition_noFromSceneInContainer_throws() { val underTest = utils.fakeSceneContainerRepository( - setOf( - utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1), - utils.fakeSceneContainerConfig( - SceneTestUtils.CONTAINER_2, - listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) - ), - ) + utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), ) - underTest.setSceneTransition( - SceneTestUtils.CONTAINER_2, - SceneKey.Shade, - SceneKey.Lockscreen - ) + underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen) } @Test(expected = IllegalStateException::class) fun setSceneTransition_noToSceneInContainer_throws() { val underTest = utils.fakeSceneContainerRepository( - setOf( - utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1), - utils.fakeSceneContainerConfig( - SceneTestUtils.CONTAINER_2, - listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) - ), - ) + utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), ) - underTest.setSceneTransition( - SceneTestUtils.CONTAINER_2, - SceneKey.Shade, - SceneKey.Lockscreen - ) + underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index d2bbfa85604b..13a602dcddb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -41,48 +41,46 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun allSceneKeys() { - assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1)) - .isEqualTo(utils.fakeSceneKeys()) + assertThat(underTest.allSceneKeys()).isEqualTo(utils.fakeSceneKeys()) } @Test fun currentScene() = runTest { - val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade)) + underTest.setCurrentScene(SceneModel(SceneKey.Shade)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) } @Test fun sceneTransitionProgress() = runTest { - val progress by - collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1)) + val progress by collectLastValue(underTest.transitionProgress) assertThat(progress).isEqualTo(1f) - underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.55f) + underTest.setSceneTransitionProgress(0.55f) assertThat(progress).isEqualTo(0.55f) } @Test fun isVisible() = runTest { - val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1)) + val isVisible by collectLastValue(underTest.isVisible) assertThat(isVisible).isTrue() - underTest.setVisible(SceneTestUtils.CONTAINER_1, false) + underTest.setVisible(false) assertThat(isVisible).isFalse() - underTest.setVisible(SceneTestUtils.CONTAINER_1, true) + underTest.setVisible(true) assertThat(isVisible).isTrue() } @Test fun sceneTransitions() = runTest { - val transitions by collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_1)) + val transitions by collectLastValue(underTest.transitions) assertThat(transitions).isNull() - val initialSceneKey = underTest.currentScene(SceneTestUtils.CONTAINER_1).value.key - underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade)) + val initialSceneKey = underTest.currentScene.value.key + underTest.setCurrentScene(SceneModel(SceneKey.Shade)) assertThat(transitions) .isEqualTo( SceneTransitionModel( @@ -91,7 +89,7 @@ class SceneInteractorTest : SysuiTestCase() { ) ) - underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.QuickSettings)) + underTest.setCurrentScene(SceneModel(SceneKey.QuickSettings)) assertThat(transitions) .isEqualTo( SceneTransitionModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 6f6c5a589f44..b6bd31f43d30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -28,7 +28,6 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.model.SysUiState import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.util.mockito.mock @@ -47,7 +46,7 @@ import org.mockito.Mockito.verify @SmallTest @RunWith(JUnit4::class) -class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { +class SceneContainerStartableTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope @@ -66,7 +65,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { private val sysUiState: SysUiState = mock() private val underTest = - SystemUiDefaultSceneContainerStartable( + SceneContainerStartable( applicationScope = testScope.backgroundScope, sceneInteractor = sceneInteractor, authenticationInteractor = authenticationInteractor, @@ -84,14 +83,8 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @Test fun hydrateVisibility_featureEnabled() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) - val isVisible by - collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT)) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val isVisible by collectLastValue(sceneInteractor.isVisible) prepareState( isFeatureEnabled = true, isDeviceUnlocked = true, @@ -104,24 +97,15 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { assertThat(isVisible).isFalse() - sceneInteractor.setCurrentScene( - SceneContainerNames.SYSTEM_UI_DEFAULT, - SceneModel(SceneKey.Shade) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade)) assertThat(isVisible).isTrue() } @Test fun hydrateVisibility_featureDisabled() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) - val isVisible by - collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT)) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val isVisible by collectLastValue(sceneInteractor.isVisible) prepareState( isFeatureEnabled = false, isDeviceUnlocked = true, @@ -133,28 +117,17 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { underTest.start() assertThat(isVisible).isTrue() - sceneInteractor.setCurrentScene( - SceneContainerNames.SYSTEM_UI_DEFAULT, - SceneModel(SceneKey.Gone) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone)) assertThat(isVisible).isTrue() - sceneInteractor.setCurrentScene( - SceneContainerNames.SYSTEM_UI_DEFAULT, - SceneModel(SceneKey.Shade) - ) + sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade)) assertThat(isVisible).isTrue() } @Test fun switchToLockscreenWhenDeviceLocks_featureEnabled() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = true, @@ -171,12 +144,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @Test fun switchToLockscreenWhenDeviceLocks_featureDisabled() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -193,12 +161,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = false, @@ -215,12 +178,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -237,12 +195,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) prepareState( isFeatureEnabled = true, isBypassEnabled = true, @@ -259,12 +212,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) prepareState( isFeatureEnabled = true, isBypassEnabled = false, @@ -281,12 +229,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) prepareState( isFeatureEnabled = false, isBypassEnabled = true, @@ -303,12 +246,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @Test fun switchToGoneWhenDeviceSleepsUnlocked_featureEnabled() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = true, @@ -325,12 +263,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @Test fun switchToGoneWhenDeviceSleepsUnlocked_featureDisabled() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = true, @@ -347,12 +280,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @Test fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = false, @@ -369,12 +297,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @Test fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() = testScope.runTest { - val currentSceneKey by - collectLastValue( - sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { - it.key - } - ) + val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -403,10 +326,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { SceneKey.QuickSettings, ) .forEachIndexed { index, sceneKey -> - sceneInteractor.setCurrentScene( - SceneContainerNames.SYSTEM_UI_DEFAULT, - SceneModel(sceneKey), - ) + sceneInteractor.setCurrentScene(SceneModel(sceneKey)) runCurrent() verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY) @@ -422,9 +342,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled) authenticationRepository.setUnlocked(isDeviceUnlocked) keyguardRepository.setBypassEnabled(isBypassEnabled) - initialSceneKey?.let { - sceneInteractor.setCurrentScene(SceneContainerNames.SYSTEM_UI_DEFAULT, SceneModel(it)) - } + initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneModel(it)) } } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index 63ea918c904a..0ab98ad512ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -45,7 +45,6 @@ class SceneContainerViewModelTest : SysuiTestCase() { private val underTest = SceneContainerViewModel( interactor = interactor, - containerName = SceneTestUtils.CONTAINER_1, ) @Test @@ -53,10 +52,10 @@ class SceneContainerViewModelTest : SysuiTestCase() { val isVisible by collectLastValue(underTest.isVisible) assertThat(isVisible).isTrue() - interactor.setVisible(SceneTestUtils.CONTAINER_1, false) + interactor.setVisible(false) assertThat(isVisible).isFalse() - interactor.setVisible(SceneTestUtils.CONTAINER_1, true) + interactor.setVisible(true) assertThat(isVisible).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 47ca49d03cc7..9bcc8aa0ac12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -99,6 +99,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.view.KeyguardRootView; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel; @@ -297,7 +298,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; @Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel; - + @Mock protected GoneToDreamingLockscreenHostedTransitionViewModel + mGoneToDreamingLockscreenHostedTransitionViewModel; @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel; @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor; @@ -371,6 +373,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardLogger, mFeatureFlags, mInteractionJankMonitor, + mKeyguardInteractor, mDumpManager)); when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false); @@ -477,6 +480,20 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mGoneToDreamingTransitionViewModel.lockscreenTranslationY(anyInt())) .thenReturn(emptyFlow()); + // Gone->Dreaming lockscreen hosted + when(mKeyguardTransitionInteractor.getGoneToDreamingLockscreenHostedTransition()) + .thenReturn(emptyFlow()); + when(mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha()) + .thenReturn(emptyFlow()); + + // Dreaming lockscreen hosted->Lockscreen + when(mKeyguardTransitionInteractor.getDreamingLockscreenHostedToLockscreenTransition()) + .thenReturn(emptyFlow()); + + // Lockscreen->Dreaming lockscreen hosted + when(mKeyguardTransitionInteractor.getLockscreenToDreamingLockscreenHostedTransition()) + .thenReturn(emptyFlow()); + // Lockscreen->Occluded when(mKeyguardTransitionInteractor.getLockscreenToOccludedTransition()) .thenReturn(emptyFlow()); @@ -612,6 +629,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mOccludedToLockscreenTransitionViewModel, mLockscreenToDreamingTransitionViewModel, mGoneToDreamingTransitionViewModel, + mGoneToDreamingLockscreenHostedTransitionViewModel, mLockscreenToOccludedTransitionViewModel, mMainDispatcher, mKeyguardTransitionInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 6e9fba64263b..8739b28c940e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -20,7 +20,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -48,20 +47,15 @@ class ShadeSceneViewModelTest : SysuiTestCase() { private val underTest = ShadeSceneViewModel( applicationScope = testScope.backgroundScope, - lockscreenSceneInteractorFactory = - object : LockscreenSceneInteractor.Factory { - override fun create(containerName: String): LockscreenSceneInteractor { - return utils.lockScreenSceneInteractor( + lockscreenSceneInteractor = + utils.lockScreenSceneInteractor( + authenticationInteractor = authenticationInteractor, + bouncerInteractor = + utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), - ) - } - }, - containerName = SceneTestUtils.CONTAINER_1 + sceneInteractor = sceneInteractor, + ), + ), ) @Test @@ -87,8 +81,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -101,8 +94,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by - collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1)) + val currentScene by collectLastValue(sceneInteractor.currentScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt index 0b2da8bfa649..0cfca614a256 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt @@ -24,6 +24,7 @@ import android.util.Pair import android.view.Gravity import android.view.View import android.widget.FrameLayout +import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags @@ -38,6 +39,7 @@ import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -50,6 +52,7 @@ import org.mockito.MockitoAnnotations class SystemEventChipAnimationControllerTest : SysuiTestCase() { private lateinit var controller: SystemEventChipAnimationController + @get:Rule val animatorTestRule = AnimatorTestRule() @Mock private lateinit var sbWindowController: StatusBarWindowController @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 1dc8453a90ec..ac8b42afd4b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -789,6 +789,50 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { assertThat(row.isExpanded()).isTrue(); } + @Test + public void onDisappearAnimationFinished_shouldSetFalse_headsUpAnimatingAway() + throws Exception { + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + + // Initial state: suppose heads up animation in progress + row.setHeadsUpAnimatingAway(true); + assertThat(row.isHeadsUpAnimatingAway()).isTrue(); + + // on disappear animation ends + row.onAppearAnimationFinished(/* wasAppearing = */ false); + assertThat(row.isHeadsUpAnimatingAway()).isFalse(); + } + + @Test + public void onHUNAppear_cancelAppearDrawing_shouldResetAnimationState() throws Exception { + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + + row.performAddAnimation(/* delay */ 0, /* duration */ 1000, /* isHeadsUpAppear */ true); + + waitForIdleSync(); + assertThat(row.isDrawingAppearAnimation()).isTrue(); + + row.cancelAppearDrawing(); + + waitForIdleSync(); + assertThat(row.isDrawingAppearAnimation()).isFalse(); + } + + @Test + public void onHUNDisappear_cancelAppearDrawing_shouldResetAnimationState() throws Exception { + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + + row.performAddAnimation(/* delay */ 0, /* duration */ 1000, /* isHeadsUpAppear */ false); + + waitForIdleSync(); + assertThat(row.isDrawingAppearAnimation()).isTrue(); + + row.cancelAppearDrawing(); + + waitForIdleSync(); + assertThat(row.isDrawingAppearAnimation()).isFalse(); + } + private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable, Drawable rightIconDrawable) { ImageView iconView = mock(ImageView.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 91aa1388c31e..481f7f7858b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -199,7 +199,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT, BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */); - verify(mKeyguardViewMediator).onWakeAndUnlocking(); + verify(mKeyguardViewMediator).onWakeAndUnlocking(false); assertThat(mBiometricUnlockController.getMode()) .isEqualTo(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING); } @@ -217,7 +217,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT, BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */); - verify(mKeyguardViewMediator).onWakeAndUnlocking(); + verify(mKeyguardViewMediator).onWakeAndUnlocking(false); assertThat(mBiometricUnlockController.getMode()) .isEqualTo(MODE_WAKE_AND_UNLOCK); } @@ -671,8 +671,9 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { when(mWakefulnessLifecycle.getLastWakeReason()) .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); givenDreamingLocked(); + when(mPowerManager.isInteractive()).thenReturn(true); mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true); - verify(mKeyguardViewMediator).onWakeAndUnlocking(); + verify(mKeyguardViewMediator).onWakeAndUnlocking(true); // Ensure that the power hasn't been told to wake up yet. verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index 57037e0c9c63..ff6f40d539fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -43,7 +43,6 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.keyguard.domain.interactor.BurnInInteractor; import com.android.systemui.keyguard.domain.interactor.DozeInteractor; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.ShadeViewController; @@ -96,7 +95,6 @@ public class DozeServiceHostTest extends SysuiTestCase { @Mock private BiometricUnlockController mBiometricUnlockController; @Mock private AuthController mAuthController; @Mock private DozeHost.Callback mCallback; - @Mock private BurnInInteractor mBurnInInteractor; @Mock private DozeInteractor mDozeInteractor; @Before @@ -108,8 +106,7 @@ public class DozeServiceHostTest extends SysuiTestCase { () -> mAssistManager, mDozeScrimController, mKeyguardUpdateMonitor, mPulseExpansionHandler, mNotificationShadeWindowController, mNotificationWakeUpCoordinator, - mAuthController, mNotificationIconAreaController, mDozeInteractor, - mBurnInInteractor); + mAuthController, mNotificationIconAreaController, mDozeInteractor); mDozeServiceHost.initialize( mCentralSurfaces, @@ -234,11 +231,11 @@ public class DozeServiceHostTest extends SysuiTestCase { verifyZeroInteractions(mDozeInteractor); } @Test - public void dozeTimeTickSentTBurnInInteractor() { + public void dozeTimeTickSentToDozeInteractor() { // WHEN dozeTimeTick mDozeServiceHost.dozeTimeTick(); - // THEN burnInInteractor's dozeTimeTick is updated - verify(mBurnInInteractor).dozeTimeTick(); + // THEN dozeInteractor's dozeTimeTick is updated + verify(mDozeInteractor).dozeTimeTick(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java index c06dbdcf3a1b..7f3d4b7f9f76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java @@ -14,6 +14,7 @@ package com.android.systemui.statusbar.policy; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -24,10 +25,10 @@ import static org.mockito.Mockito.when; import android.app.NotificationManager; import android.os.Handler; -import android.os.Looper; import android.provider.Settings; import android.service.notification.ZenModeConfig; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; @@ -45,6 +46,9 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -61,9 +65,10 @@ public class ZenModeControllerImplTest extends SysuiTestCase { DumpManager mDumpManager; @Mock UserTracker mUserTracker; - private ZenModeControllerImpl mController; + private final FakeSettings mGlobalSettings = new FakeSettings(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -72,10 +77,10 @@ public class ZenModeControllerImplTest extends SysuiTestCase { mController = new ZenModeControllerImpl( mContext, - Handler.createAsync(Looper.myLooper()), + Handler.createAsync(TestableLooper.get(this).getLooper()), mBroadcastDispatcher, mDumpManager, - new FakeSettings(), + mGlobalSettings, mUserTracker); } @@ -131,4 +136,48 @@ public class ZenModeControllerImplTest extends SysuiTestCase { mController.addCallback(null); mController.fireConfigChanged(null); } + + @Test + public void testModeChange() { + List<Integer> states = List.of( + Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, + Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, + Settings.Global.ZEN_MODE_ALARMS, + Settings.Global.ZEN_MODE_ALARMS + ); + + for (Integer state : states) { + mGlobalSettings.putInt(Settings.Global.ZEN_MODE, state); + TestableLooper.get(this).processAllMessages(); + assertEquals(state.intValue(), mController.getZen()); + } + } + + @Test + public void testModeChange_callbackNotified() { + final AtomicInteger currentState = new AtomicInteger(-1); + + ZenModeController.Callback callback = new Callback() { + @Override + public void onZenChanged(int zen) { + currentState.set(zen); + } + }; + + mController.addCallback(callback); + + List<Integer> states = List.of( + Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, + Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, + Settings.Global.ZEN_MODE_ALARMS, + Settings.Global.ZEN_MODE_ALARMS + ); + + for (Integer state : states) { + mGlobalSettings.putInt(Settings.Global.ZEN_MODE, state); + TestableLooper.get(this).processAllMessages(); + assertEquals(state.intValue(), currentState.get()); + } + + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt index 64f5087d99bf..f1fadf6f1a4a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt @@ -13,9 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.android.systemui.surfaceeffects.shaders -package com.android.systemui.scene.shared.model +import android.graphics.Color +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Test +import org.junit.runner.RunWith -object SceneContainerNames { - const val SYSTEM_UI_DEFAULT = "system_ui" +@SmallTest +@RunWith(AndroidTestingRunner::class) +class SolidColorShaderTest : SysuiTestCase() { + + @Test + fun compilesShader() { + SolidColorShader(Color.RED) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt new file mode 100644 index 000000000000..64ea6a68a518 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt @@ -0,0 +1,44 @@ +/* + * 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.surfaceeffects.shaders + +import android.graphics.Color +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class SparkleShaderTest : SysuiTestCase() { + + private lateinit var sparkleShader: SparkleShader + + @Test + fun compilesSparkleShader() { + sparkleShader = + SparkleShader().apply { + setPixelateAmount( + context.resources.displayMetrics.density * + SparkleShader.DEFAULT_SPARKLE_PIXELATE_AMOUNT + ) + setColor(Color.RED) + setTime(0.01f) + setLumaMatteColor(Color.WHITE) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 2f228a8da0c8..e10a80cce793 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -158,7 +158,6 @@ import com.android.wm.shell.transition.Transitions; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -174,7 +173,6 @@ import java.util.HashMap; import java.util.List; import java.util.Optional; -@Ignore("b/292153259") @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -280,8 +278,6 @@ public class BubblesTest extends SysuiTestCase { @Mock private TaskStackListenerImpl mTaskStackListener; @Mock - private ShellTaskOrganizer mShellTaskOrganizer; - @Mock private KeyguardStateController mKeyguardStateController; @Mock private ScreenOffAnimationController mScreenOffAnimationController; @@ -298,6 +294,7 @@ public class BubblesTest extends SysuiTestCase { @Mock private Icon mAppBubbleIcon; + private ShellTaskOrganizer mShellTaskOrganizer; private TaskViewTransitions mTaskViewTransitions; private TestableBubblePositioner mPositioner; @@ -379,7 +376,13 @@ public class BubblesTest extends SysuiTestCase { mock(UiEventLogger.class), mock(UserTracker.class) ); - when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor); + + mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class), + mock(ShellCommandHandler.class), + null, + Optional.empty(), + Optional.empty(), + syncExecutor); mBubbleProperties = new FakeBubbleProperties(); mBubbleController = new TestableBubbleController( mContext, diff --git a/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt b/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt new file mode 100644 index 000000000000..026372f64e2c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt @@ -0,0 +1,55 @@ +/* + * 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 androidx.core.animation + +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +class AndroidXAnimatorIsolationRule : TestRule { + + private class TestAnimationHandler : AnimationHandler(null) { + override fun addAnimationFrameCallback(callback: AnimationFrameCallback?) = doFail() + override fun removeCallback(callback: AnimationFrameCallback?) = doFail() + override fun onAnimationFrame(frameTime: Long) = doFail() + override fun setFrameDelay(frameDelay: Long) = doFail() + override fun getFrameDelay(): Long = doFail() + } + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + @Throws(Throwable::class) + override fun evaluate() { + AnimationHandler.setTestHandler(testHandler) + try { + base.evaluate() + } finally { + AnimationHandler.setTestHandler(null) + } + } + } + } + + companion object { + private val testHandler = TestAnimationHandler() + private fun doFail(): Nothing = + error( + "Test's animations are not isolated! " + + "Did you forget to add an AnimatorTestRule to your test class?" + ) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index de177168e20f..28b7d4171df1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -33,6 +33,7 @@ import android.testing.TestWithLooperRule; import android.testing.TestableLooper; import android.util.Log; +import androidx.core.animation.AndroidXAnimatorIsolationRule; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; @@ -52,6 +53,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Rule; import org.mockito.Mockito; @@ -69,6 +71,12 @@ public abstract class SysuiTestCase { private static final String TAG = "SysuiTestCase"; private Handler mHandler; + + // set the lowest order so it's the outermost rule + @ClassRule(order = Integer.MIN_VALUE) + public static AndroidXAnimatorIsolationRule mAndroidXAnimatorIsolationRule = + new AndroidXAnimatorIsolationRule(); + @Rule public SysuiTestableContext mContext = new SysuiTestableContext( InstrumentationRegistry.getContext(), getLeakCheck()); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt index f4c2db1b944e..1e1dc4fc59b1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -61,6 +61,19 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { _wasDisabled = true } + private val faceAuthPaused = MutableStateFlow(false) + override fun pauseFaceAuth() { + faceAuthPaused.value = true + } + + override fun resumeFaceAuth() { + faceAuthPaused.value = false + } + + fun isFaceAuthPaused(): Boolean { + return faceAuthPaused.value + } + override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { _runningAuthRequest.value = uiEvent to fallbackToDetection _isAuthRunning.value = true diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index e6894d7240f8..15ce055bcc63 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -30,7 +30,6 @@ import com.android.systemui.keyguard.shared.model.WakeSleepReason import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -60,7 +59,7 @@ class FakeKeyguardRepository : KeyguardRepository { private val _isDozing = MutableStateFlow(false) override val isDozing: StateFlow<Boolean> = _isDozing - private val _dozeTimeTick = MutableSharedFlow<Unit>() + private val _dozeTimeTick = MutableStateFlow<Long>(0L) override val dozeTimeTick = _dozeTimeTick private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null) @@ -174,7 +173,11 @@ class FakeKeyguardRepository : KeyguardRepository { } override fun dozeTimeTick() { - _dozeTimeTick.tryEmit(Unit) + _dozeTimeTick.value = _dozeTimeTick.value + 1 + } + + fun dozeTimeTick(millis: Long) { + _dozeTimeTick.value = millis } override fun setLastDozeTapToWakePosition(position: Point) { @@ -237,6 +240,10 @@ class FakeKeyguardRepository : KeyguardRepository { _isBypassEnabled = isEnabled } + fun setScreenModel(screenModel: ScreenModel) { + _screenModel.value = screenModel + } + override fun isUdfpsSupported(): Boolean { return _isUdfpsSupported.value } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index f39982f54441..26a75d0cc70a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -41,7 +41,6 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.RemoteUserInput import com.android.systemui.scene.shared.model.RemoteUserInputAction import com.android.systemui.scene.shared.model.SceneContainerConfig -import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.UserRepository @@ -96,13 +95,9 @@ class SceneTestUtils( private val context = test.context fun fakeSceneContainerRepository( - containerConfigurations: Set<SceneContainerConfig> = - setOf( - fakeSceneContainerConfig(CONTAINER_1), - fakeSceneContainerConfig(CONTAINER_2), - ) + containerConfig: SceneContainerConfig = fakeSceneContainerConfig(), ): SceneContainerRepository { - return SceneContainerRepository(containerConfigurations.associateBy { it.name }) + return SceneContainerRepository(containerConfig) } fun fakeSceneKeys(): List<SceneKey> { @@ -116,11 +111,9 @@ class SceneTestUtils( } fun fakeSceneContainerConfig( - name: String, sceneKeys: List<SceneKey> = fakeSceneKeys(), ): SceneContainerConfig { return SceneContainerConfig( - name = name, sceneKeys = sceneKeys, initialSceneKey = SceneKey.Lockscreen, ) @@ -174,7 +167,6 @@ class SceneTestUtils( authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, featureFlags = featureFlags, - containerName = CONTAINER_1, ) } @@ -184,14 +176,8 @@ class SceneTestUtils( return BouncerViewModel( applicationContext = context, applicationScope = applicationScope(), - interactorFactory = - object : BouncerInteractor.Factory { - override fun create(containerName: String): BouncerInteractor { - return bouncerInteractor - } - }, + interactor = bouncerInteractor, featureFlags = featureFlags, - containerName = CONTAINER_1, ) } @@ -202,13 +188,7 @@ class SceneTestUtils( return LockscreenSceneInteractor( applicationScope = applicationScope(), authenticationInteractor = authenticationInteractor, - bouncerInteractorFactory = - object : BouncerInteractor.Factory { - override fun create(containerName: String): BouncerInteractor { - return bouncerInteractor - } - }, - containerName = CONTAINER_1, + bouncerInteractor = bouncerInteractor, ) } @@ -217,9 +197,6 @@ class SceneTestUtils( } companion object { - const val CONTAINER_1 = SceneContainerNames.SYSTEM_UI_DEFAULT - const val CONTAINER_2 = "container2" - val REMOTE_INPUT_DOWN_GESTURE = listOf( RemoteUserInput(10f, 10f, RemoteUserInputAction.DOWN), diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 254e6ce14630..cf7eb5122021 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -983,8 +983,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } if (virtualDisplayWrapper == null) { - throw new IllegalStateException( - "Virtual device doesn't have a virtual display with ID " + displayId); + Slog.w(TAG, "Virtual device " + mDeviceId + " doesn't have a virtual display with ID " + + displayId); + return; } final long ident = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 3f4f981dc0b8..23a0782dc8a3 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -989,21 +989,16 @@ public final class PlaybackActivityMonitor } List<AudioPlaybackConfiguration> getActivePlaybackConfigurations(boolean isPrivileged) { - synchronized(mPlayers) { + synchronized (mPlayerLock) { if (isPrivileged) { return new ArrayList<AudioPlaybackConfiguration>(mPlayers.values()); } else { - final List<AudioPlaybackConfiguration> configsPublic; - synchronized (mPlayerLock) { - configsPublic = anonymizeForPublicConsumption( + return anonymizeForPublicConsumption( new ArrayList<AudioPlaybackConfiguration>(mPlayers.values())); - } - return configsPublic; } } } - /** * Inner class to track clients that want to be notified of playback updates */ diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java index 060534507c5d..3ba307be2311 100644 --- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -22,6 +22,7 @@ import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; +import android.app.role.RoleManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.UserInfo; @@ -64,6 +65,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private static final int LOCAL_LOG_SIZE = 20; private static final String TAG = "BugreportManagerService"; private static final boolean DEBUG = false; + private static final String ROLE_SYSTEM_AUTOMOTIVE_PROJECTION = + "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"; private static final String BUGREPORT_SERVICE = "bugreportd"; private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000; @@ -326,11 +329,22 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { // To gain access through the DUMP permission, the OEM has to allow this package explicitly // via sysconfig and privileged permissions. - if (mBugreportAllowlistedPackages.contains(callingPackage) - && mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - == PackageManager.PERMISSION_GRANTED) { + boolean allowlisted = mBugreportAllowlistedPackages.contains(callingPackage); + if (!allowlisted) { + final long token = Binder.clearCallingIdentity(); + try { + allowlisted = mContext.getSystemService(RoleManager.class).getRoleHolders( + ROLE_SYSTEM_AUTOMOTIVE_PROJECTION).contains(callingPackage); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + if (allowlisted && mContext.checkCallingOrSelfPermission( + android.Manifest.permission.DUMP) == PackageManager.PERMISSION_GRANTED) { return; } + // For carrier privileges, this can include user-installed apps. This is essentially a // function of the current active SIM(s) in the device to let carrier apps through. final long token = Binder.clearCallingIdentity(); @@ -346,7 +360,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { String message = callingPackage - + " does not hold the DUMP permission or is not bugreport-whitelisted " + + " does not hold the DUMP permission or is not bugreport-whitelisted or " + + "does not have an allowed role " + (checkCarrierPrivileges ? "and does not have carrier privileges " : "") + "to request a bugreport"; Slog.w(TAG, message); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 710e0b72ecfb..dd434fbeecb4 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -37,6 +37,7 @@ import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -1927,11 +1928,32 @@ public class ShortcutService extends IShortcutService.Stub { } if (shortcut.getIcon() != null) { ShortcutInfo.validateIcon(shortcut.getIcon()); + validateIconURI(shortcut); } shortcut.replaceFlags(shortcut.getFlags() & ShortcutInfo.FLAG_LONG_LIVED); } + // Validates the calling process has permission to access shortcut icon's image uri + private void validateIconURI(@NonNull final ShortcutInfo si) { + final int callingUid = injectBinderCallingUid(); + final Icon icon = si.getIcon(); + if (icon == null) { + // There's no icon in this shortcut, nothing to validate here. + return; + } + int iconType = icon.getType(); + if (iconType != Icon.TYPE_URI && iconType != Icon.TYPE_URI_ADAPTIVE_BITMAP) { + // The icon is not URI-based, nothing to validate. + return; + } + final Uri uri = icon.getUri(); + mUriGrantsManagerInternal.checkGrantUriPermission(callingUid, si.getPackage(), + ContentProvider.getUriWithoutUserId(uri), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(callingUid))); + } + private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { fixUpIncomingShortcutInfo(shortcut, forUpdate, /*forPinRequest=*/ false); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index e93f35802953..62273b5bc445 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -226,6 +226,7 @@ import static com.android.server.wm.IdentifierProto.USER_ID; import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW; import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE; @@ -2676,7 +2677,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private boolean transferSplashScreenIfNeeded() { if (finishing || !mHandleExitSplashScreen || mStartingSurface == null || mStartingWindow == null - || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH) { + || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH + // skip copy splash screen to client if it was resized + || (mStartingData != null && mStartingData.mResizedFromTransfer)) { return false; } if (isTransferringSplashScreen()) { @@ -7632,7 +7635,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override void prepareSurfaces() { final boolean show = isVisible() || isAnimating(PARENTS, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS); + ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS + | ANIMATION_TYPE_PREDICT_BACK); if (mSurfaceControl != null) { if (show && !mLastSurfaceShowing) { diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 0c196d7e99e9..976641b52a16 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -26,7 +26,9 @@ import static android.view.WindowManager.TRANSIT_TO_BACK; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW; import static com.android.server.wm.BackNavigationProto.ANIMATION_IN_PROGRESS; +import static com.android.server.wm.BackNavigationProto.ANIMATION_RUNNING; import static com.android.server.wm.BackNavigationProto.LAST_BACK_TYPE; +import static com.android.server.wm.BackNavigationProto.MAIN_OPEN_ACTIVITY; import static com.android.server.wm.BackNavigationProto.SHOW_WALLPAPER; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK; @@ -50,6 +52,7 @@ import android.view.WindowInsets; import android.window.BackAnimationAdapter; import android.window.BackNavigationInfo; import android.window.IBackAnimationFinishedCallback; +import android.window.IWindowlessStartingSurfaceCallback; import android.window.OnBackInvokedCallbackInfo; import android.window.TaskSnapshot; @@ -73,6 +76,8 @@ class BackNavigationController { private @BackNavigationInfo.BackTargetType int mLastBackType; private boolean mShowWallpaper; private Runnable mPendingAnimation; + + private boolean mBackAnimationRunning; private final NavigationMonitor mNavigationMonitor = new NavigationMonitor(); private AnimationHandler mAnimationHandler; @@ -474,7 +479,7 @@ class BackNavigationController { final ActivityRecord ar = openApps.valueAt(i); if (mAnimationHandler.isTarget(ar, true /* open */)) { openApps.removeAt(i); - mAnimationHandler.mOpenTransitionTargetMatch = true; + mAnimationHandler.markStartingSurfaceMatch(); } } for (int i = closeApps.size() - 1; i >= 0; --i) { @@ -583,8 +588,9 @@ class BackNavigationController { * The closing target should only exist in close list, but the opening target can be either in * open or close list. */ - void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets) { - if (!isMonitoringTransition()) { + void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets, + SurfaceControl.Transaction startTransaction) { + if (!isMonitoringTransition() || targets.isEmpty()) { return; } for (int i = targets.size() - 1; i >= 0; --i) { @@ -613,6 +619,17 @@ class BackNavigationController { Slog.e(TAG, "Gesture animation is applied on another transition?"); } mWaitTransitionFinish = transition; + // Flag target matches to defer remove the splash screen. + for (int i = mTmpOpenApps.size() - 1; i >= 0; --i) { + final WindowContainer wc = mTmpOpenApps.get(i); + if (mAnimationHandler.isTarget(wc, true /* open */)) { + mAnimationHandler.markStartingSurfaceMatch(); + break; + } + } + // Because the target will reparent to transition root, so it cannot be controlled by + // animation leash. Hide the close target when transition starts. + startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl()); } mTmpOpenApps.clear(); mTmpCloseApps.clear(); @@ -633,6 +650,7 @@ class BackNavigationController { mAnimationHandler.clearBackAnimateTarget(); mNavigationMonitor.stopMonitorTransition(); mWaitTransitionFinish = null; + mBackAnimationRunning = false; } /** @@ -717,11 +735,7 @@ class BackNavigationController { // This will be set before transition happen, to know whether the real opening target // exactly match animating target. When target match, reparent the starting surface to // the opening target like starting window do. - private boolean mOpenTransitionTargetMatch; - // The starting surface task Id. Used to clear the starting surface if the animation has - // request one during animating. - private int mRequestedStartingSurfaceTaskId; - private SurfaceControl mStartingSurface; + private boolean mStartingSurfaceTargetMatch; private ActivityRecord mOpenActivity; AnimationHandler(WindowManagerService wms) { @@ -765,8 +779,8 @@ class BackNavigationController { return; } - mCloseAdaptor = createAdaptor(closeTarget, false /* isOpen */); - mOpenAdaptor = createAdaptor(open, true /* isOpen */); + mCloseAdaptor = createAdaptor(closeTarget, false, mSwitchType); + mOpenAdaptor = createAdaptor(open, true, mSwitchType); mOpenActivity = openActivity; if (mCloseAdaptor.mAnimationTarget == null || mOpenAdaptor.mAnimationTarget == null) { Slog.w(TAG, "composeNewAnimations fail, skip"); @@ -774,8 +788,8 @@ class BackNavigationController { } } - boolean composeAnimations(@NonNull WindowContainer close, @NonNull WindowContainer open, - ActivityRecord openActivity) { + private boolean composeAnimations(@NonNull WindowContainer close, + @NonNull WindowContainer open, ActivityRecord openActivity) { if (mComposed || mWaitTransition) { Slog.e(TAG, "Previous animation is running " + this); return false; @@ -805,28 +819,6 @@ class BackNavigationController { .isSupportWindowlessStartingSurface(); } - void createStartingSurface(TaskSnapshot snapshot) { - if (!mComposed) { - return; - } - - final ActivityRecord topActivity = getTopOpenActivity(); - if (topActivity == null) { - Slog.e(TAG, "createStartingSurface fail, no open activity: " + this); - return; - } - // TODO (b/257857570) draw snapshot by starting surface. - } - - private ActivityRecord getTopOpenActivity() { - if (mSwitchType == ACTIVITY_SWITCH) { - return mOpenAdaptor.mTarget.asActivityRecord(); - } else if (mSwitchType == TASK_SWITCH) { - return mOpenAdaptor.mTarget.asTask().getTopNonFinishingActivity(); - } - return null; - } - boolean containTarget(ArrayList<WindowContainer> wcs, boolean open) { for (int i = wcs.size() - 1; i >= 0; --i) { if (isTarget(wcs.get(i), open)) { @@ -860,13 +852,13 @@ class BackNavigationController { if (!mComposed) { return; } - cleanUpWindowlessSurface(); if (mCloseAdaptor != null) { mCloseAdaptor.mTarget.cancelAnimation(); mCloseAdaptor = null; } if (mOpenAdaptor != null) { + mOpenAdaptor.cleanUpWindowlessSurface(mStartingSurfaceTargetMatch); mOpenAdaptor.mTarget.cancelAnimation(); mOpenAdaptor = null; } @@ -875,36 +867,16 @@ class BackNavigationController { } } - private void cleanUpWindowlessSurface() { - final ActivityRecord ar = getTopOpenActivity(); - if (ar == null) { - Slog.w(TAG, "finishPresentAnimations without top activity: " + this); - } - final SurfaceControl.Transaction pendingT = ar != null ? ar.getPendingTransaction() - : mOpenAdaptor.mTarget.getPendingTransaction(); - // ensure open target is visible before cancel animation. - mOpenTransitionTargetMatch &= ar != null; - if (mOpenTransitionTargetMatch) { - pendingT.show(ar.getSurfaceControl()); - } - if (mRequestedStartingSurfaceTaskId != 0) { - // If open target match, reparent to open activity - if (mStartingSurface != null && mOpenTransitionTargetMatch) { - pendingT.reparent(mStartingSurface, ar.getSurfaceControl()); - } - // remove starting surface. - mStartingSurface = null; - // TODO (b/257857570) draw snapshot by starting surface. - mRequestedStartingSurfaceTaskId = 0; - } + void markStartingSurfaceMatch() { + mStartingSurfaceTargetMatch = true; + mOpenAdaptor.reparentWindowlessSurfaceToTarget(); } void clearBackAnimateTarget() { finishPresentAnimations(); mComposed = false; mWaitTransition = false; - mOpenTransitionTargetMatch = false; - mRequestedStartingSurfaceTaskId = 0; + mStartingSurfaceTargetMatch = false; mSwitchType = UNKNOWN; mOpenActivity = null; } @@ -935,9 +907,9 @@ class BackNavigationController { } private static BackWindowAnimationAdaptor createAdaptor( - WindowContainer target, boolean isOpen) { + WindowContainer target, boolean isOpen, int switchType) { final BackWindowAnimationAdaptor adaptor = - new BackWindowAnimationAdaptor(target, isOpen); + new BackWindowAnimationAdaptor(target, isOpen, switchType); final SurfaceControl.Transaction pt = target.getPendingTransaction(); target.startAnimation(pt, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK); // Workaround to show TaskFragment which can be hide in Transitions and won't show @@ -957,11 +929,19 @@ class BackNavigationController { private final WindowContainer mTarget; private final boolean mIsOpen; private RemoteAnimationTarget mAnimationTarget; + private final int mSwitchType; - BackWindowAnimationAdaptor(WindowContainer closeTarget, boolean isOpen) { - mBounds.set(closeTarget.getBounds()); - mTarget = closeTarget; + // The starting surface task Id. Used to clear the starting surface if the animation has + // requested one during animating. + private int mRequestedStartingSurfaceId = INVALID_TASK_ID; + private SurfaceControl mStartingSurface; + + BackWindowAnimationAdaptor(WindowContainer target, boolean isOpen, + int switchType) { + mBounds.set(target.getBounds()); + mTarget = target; mIsOpen = isOpen; + mSwitchType = switchType; } @Override public boolean getShowWallpaper() { @@ -979,6 +959,8 @@ class BackNavigationController { public void onAnimationCancelled(SurfaceControl animationLeash) { if (mCapturedLeash == animationLeash) { mCapturedLeash = null; + mRequestedStartingSurfaceId = INVALID_TASK_ID; + mStartingSurface = null; } } @@ -1009,8 +991,15 @@ class BackNavigationController { return mAnimationTarget; } Task t = mTarget.asTask(); - final ActivityRecord r = t != null ? t.getTopNonFinishingActivity() - : mTarget.asActivityRecord(); + ActivityRecord r = null; + if (t == null && mTarget.asTaskFragment() != null) { + t = mTarget.asTaskFragment().getTask(); + r = mTarget.asTaskFragment().getTopNonFinishingActivity(); + } + if (r == null) { + r = t != null ? t.getTopNonFinishingActivity() + : mTarget.asActivityRecord(); + } if (t == null && r != null) { t = r.getTask(); } @@ -1037,6 +1026,77 @@ class BackNavigationController { r.checkEnterPictureInPictureAppOpsState()); return mAnimationTarget; } + + void createStartingSurface() { + if (!mIsOpen) { + return; + } + final Task openTask = mSwitchType == TASK_SWITCH + ? mTarget.asTask() : mSwitchType == ACTIVITY_SWITCH + ? mTarget.asActivityRecord().getTask() : null; + if (openTask == null) { + return; + } + final ActivityRecord mainActivity = mSwitchType == ACTIVITY_SWITCH + ? mTarget.asActivityRecord() + : openTask.getTopNonFinishingActivity(); + if (mainActivity == null) { + return; + } + final TaskSnapshot snapshot = getSnapshot(mTarget); + mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController + .addWindowlessStartingSurface(openTask, mainActivity, + mAnimationTarget.leash, snapshot, + new IWindowlessStartingSurfaceCallback.Stub() { + // Once the starting surface has been created in shell, it will call + // onSurfaceAdded to pass the created surface to core, so if a + // transition is triggered by the back gesture, there doesn't need to + // create another starting surface for the opening target, just reparent + // the starting surface to the opening target. + // Note if cleanUpWindowlessSurface happen prior than onSurfaceAdded + // called, there won't be able to reparent the starting surface on + // opening target. But if that happens and transition target is matched, + // the app window should already draw. + @Override + public void onSurfaceAdded(SurfaceControl sc) { + synchronized (mTarget.mWmService.mGlobalLock) { + if (mRequestedStartingSurfaceId != INVALID_TASK_ID) { + mStartingSurface = sc; + } + } + } + }); + } + + // When back gesture has triggered and transition target matches navigation target, + // reparent the starting surface to the opening target as it's starting window. + void reparentWindowlessSurfaceToTarget() { + if (mRequestedStartingSurfaceId == INVALID_TASK_ID) { + return; + } + // If open target matches, reparent to open activity or task + if (mStartingSurface != null && mStartingSurface.isValid()) { + mTarget.getPendingTransaction() + .reparent(mStartingSurface, mTarget.getSurfaceControl()); + // remove starting surface. + mStartingSurface = null; + } + } + + /** + * Ask shell to clear the starting surface. + * @param openTransitionMatch if true, shell will play the remove starting window + * animation, otherwise remove it directly. + */ + void cleanUpWindowlessSurface(boolean openTransitionMatch) { + if (mRequestedStartingSurfaceId == INVALID_TASK_ID) { + return; + } + mTarget.mWmService.mAtmService.mTaskOrganizerController + .removeWindowlessStartingSurface(mRequestedStartingSurfaceId, + !openTransitionMatch); + mRequestedStartingSurfaceId = INVALID_TASK_ID; + } } ScheduleAnimationBuilder prepareAnimation(int backType, BackAnimationAdapter adapter, @@ -1089,15 +1149,13 @@ class BackNavigationController { /** * Apply preview strategy on the opening target - * @param open The opening target. + * @param openAnimationAdaptor The animator who can create starting surface. * @param visibleOpenActivity The visible activity in opening target. - * @return If the preview strategy is launch behind, returns the Activity that has - * launchBehind set, or null otherwise. */ - private void applyPreviewStrategy(WindowContainer open, + private void applyPreviewStrategy(BackWindowAnimationAdaptor openAnimationAdaptor, ActivityRecord visibleOpenActivity) { if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) { - createStartingSurface(getSnapshot(open)); + openAnimationAdaptor.createStartingSurface(); return; } setLaunchBehind(visibleOpenActivity); @@ -1119,7 +1177,7 @@ class BackNavigationController { if (!composeAnimations(mCloseTarget, mOpenTarget, openActivity)) { return null; } - applyPreviewStrategy(mOpenTarget, openActivity); + applyPreviewStrategy(mOpenAdaptor, openActivity); final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback(); final RemoteAnimationTarget[] targets = getAnimationTargets(); @@ -1220,6 +1278,7 @@ class BackNavigationController { if (mPendingAnimation != null) { mPendingAnimation.run(); mPendingAnimation = null; + mBackAnimationRunning = true; } } @@ -1236,9 +1295,6 @@ class BackNavigationController { } static TaskSnapshot getSnapshot(@NonNull WindowContainer w) { - if (!isScreenshotEnabled()) { - return null; - } if (w.asTask() != null) { final Task task = w.asTask(); return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot( @@ -1247,8 +1303,8 @@ class BackNavigationController { } if (w.asActivityRecord() != null) { - // TODO (b/259497289) return TaskSnapshot when feature complete. - return null; + final ActivityRecord ar = w.asActivityRecord(); + return ar.mWmService.mSnapshotController.mActivitySnapshotController.getSnapshot(ar); } return null; } @@ -1270,6 +1326,12 @@ class BackNavigationController { proto.write(ANIMATION_IN_PROGRESS, mBackAnimationInProgress); proto.write(LAST_BACK_TYPE, mLastBackType); proto.write(SHOW_WALLPAPER, mShowWallpaper); + if (mAnimationHandler.mOpenActivity != null) { + mAnimationHandler.mOpenActivity.writeNameToProto(proto, MAIN_OPEN_ACTIVITY); + } else { + proto.write(MAIN_OPEN_ACTIVITY, ""); + } + proto.write(ANIMATION_RUNNING, mBackAnimationRunning); proto.end(token); } } diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java index cff86add7efc..2b22d75693fe 100644 --- a/services/core/java/com/android/server/wm/StartingData.java +++ b/services/core/java/com/android/server/wm/StartingData.java @@ -38,6 +38,10 @@ public abstract class StartingData { */ Task mAssociatedTask; + + /** Whether the starting window is resized from transfer across activities. */ + boolean mResizedFromTransfer; + /** Whether the starting window is drawn. */ boolean mIsDisplayed; diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index cdb4ad645dc3..b72d02789114 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -790,12 +790,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } boolean isSupportWindowlessStartingSurface() { - // Enable after ag/20426257 final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast(); - if (lastOrganizer == null) { - return false; - } - return false; + return lastOrganizer != null; } /** * Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} that the client has diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index b7c8092e3774..f33af5efb691 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1499,7 +1499,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mTargets = calculateTargets(mParticipants, mChanges); // Check whether the participants were animated from back navigation. - mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets); + mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets, + transaction); final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction); info.setDebugId(mSyncId); mController.assignTrack(this, info); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 140255b2f016..b2a2452f1123 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1935,7 +1935,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } final ActivityRecord atoken = mActivityRecord; if (atoken != null) { - return ((!isParentWindowHidden() && atoken.isVisible()) + final boolean isVisible = isStartingWindowAssociatedToTask() + ? mStartingData.mAssociatedTask.isVisible() : atoken.isVisible(); + return ((!isParentWindowHidden() && isVisible) || isAnimationRunningSelfOrParent()); } final WallpaperWindowToken wtoken = mToken.asWallpaperToken(); @@ -2330,6 +2332,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // IME surface association. (e.g. Attach IME surface on the display instead of the // app when the app bounds being letterboxed.) mDisplayContent.updateImeControlTarget(isImeLayeringTarget() /* updateImeParent */); + // Fix the starting window to task when Activity has changed. + if (mStartingData != null && mStartingData.mAssociatedTask == null + && !mTempConfiguration.windowConfiguration.getBounds().equals(getBounds())) { + mStartingData.mResizedFromTransfer = true; + // Lock the starting window to task, so it won't resize from transfer anymore. + mActivityRecord.associateStartingWindowWithTaskIfNeeded(); + } } } @@ -3902,7 +3911,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * LetterboxUiController#shouldShowLetterboxUi} for more context. */ boolean areAppWindowBoundsLetterboxed() { - return mActivityRecord != null + return mActivityRecord != null && !isStartingWindowAssociatedToTask() && (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout()); } @@ -5668,6 +5677,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // TODO(b/233286785): Add sync support to wallpaper. return true; } + if (mActivityRecord != null && mViewVisibility != View.VISIBLE + && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION + && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) { + // Skip sync for invisible app windows which are not managed by activity lifecycle. + return false; + } // In the WindowContainer implementation we immediately mark ready // since a generic WindowContainer only needs to wait for its // children to finish and is immediately ready from its own diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java index 35b9bc3b1e06..4a8d73d23904 100644 --- a/services/print/java/com/android/server/print/PrintManagerService.java +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -254,12 +254,45 @@ public final class PrintManagerService extends SystemService { } final long identity = Binder.clearCallingIdentity(); try { - return userState.getCustomPrinterIcon(printerId); + Icon icon = userState.getCustomPrinterIcon(printerId); + return validateIconUserBoundary(icon); } finally { Binder.restoreCallingIdentity(identity); } } + /** + * Validates the custom printer icon to see if it's not in the calling user space. + * If the condition is not met, return null. Otherwise, return the original icon. + * + * @param icon + * @return icon (validated) + */ + private Icon validateIconUserBoundary(Icon icon) { + // Refer to Icon#getUriString for context. The URI string is invalid for icons of + // incompatible types. + if (icon != null && (icon.getType() == Icon.TYPE_URI + || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { + String encodedUser = icon.getUri().getEncodedUserInfo(); + + // If there is no encoded user, the URI is calling into the calling user space + if (encodedUser != null) { + int userId = Integer.parseInt(encodedUser); + // resolve encoded user + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + + synchronized (mLock) { + // Only the current group members can get the printer icons. + if (resolveCallingProfileParentLocked(resolvedUserId) + != getCurrentUserId()) { + return null; + } + } + } + } + return icon; + } + @Override public void cancelPrintJob(PrintJobId printJobId, int appId, int userId) { if (printJobId == null) { diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 107dde244940..fa0a9713fec0 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -110,6 +110,7 @@ <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" /> <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" /> <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" /> + <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" /> <queries> <package android:name="com.android.servicestests.apps.suspendtestapp" /> diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 8884dba217ed..2336374a3c5b 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -789,13 +789,6 @@ public class VirtualDeviceManagerServiceTest { } @Test - public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() { - final int unknownDisplayId = 999; - assertThrows(IllegalStateException.class, - () -> mDeviceImpl.onVirtualDisplayRemoved(unknownDisplayId)); - } - - @Test public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException { addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class); diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java index 52c6777b70af..24029b1113ea 100644 --- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java @@ -16,15 +16,18 @@ package com.android.server.os; +import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import android.app.role.RoleManager; import android.content.Context; import android.os.Binder; import android.os.BugreportManager.BugreportCallback; import android.os.IBinder; import android.os.IDumpstateListener; +import android.os.Process; import android.os.RemoteException; import android.util.ArraySet; import android.util.Pair; @@ -37,21 +40,23 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.FileDescriptor; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) public class BugreportManagerServiceImplTest { - Context mContext; - BugreportManagerServiceImpl mService; - BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager; + private Context mContext; + private BugreportManagerServiceImpl mService; + private BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager; - int mCallingUid = 1234; - String mCallingPackage = "test.package"; + private int mCallingUid = 1234; + private String mCallingPackage = "test.package"; - String mBugreportFile = "bugreport-file.zip"; - String mBugreportFile2 = "bugreport-file2.zip"; + private String mBugreportFile = "bugreport-file.zip"; + private String mBugreportFile2 = "bugreport-file2.zip"; @Before public void setUp() { @@ -109,6 +114,36 @@ public class BugreportManagerServiceImplTest { BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE); } + @Test + public void testCancelBugreportWithoutRole() throws Exception { + // Clear out allowlisted packages. + mService = new BugreportManagerServiceImpl( + new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>())); + + assertThrows(SecurityException.class, () -> mService.cancelBugreport( + Binder.getCallingUid(), mContext.getPackageName())); + } + + @Test + public void testCancelBugreportWithRole() throws Exception { + // Clear out allowlisted packages. + mService = new BugreportManagerServiceImpl( + new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>())); + RoleManager roleManager = mContext.getSystemService(RoleManager.class); + CallbackFuture future = new CallbackFuture(); + runWithShellPermissionIdentity(() -> roleManager.setBypassingRoleQualification(true)); + runWithShellPermissionIdentity(() -> roleManager.addRoleHolderAsUser( + "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION", + mContext.getPackageName(), + /* flags= */ 0, + Process.myUserHandle(), + mContext.getMainExecutor(), + future)); + + assertThat(future.get()).isEqualTo(true); + mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName()); + } + private static class Listener implements IDumpstateListener { CountDownLatch mLatch; int mErrorCode; @@ -149,4 +184,12 @@ public class BugreportManagerServiceImplTest { return mErrorCode; } } + + private static class CallbackFuture extends CompletableFuture<Boolean> + implements Consumer<Boolean> { + @Override + public void accept(Boolean successful) { + complete(successful); + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 6d7f2c13197c..1c86758f28ff 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -570,6 +570,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { final ContextWrapper contextSpy = Mockito.spy(new ContextWrapper(mWm.mContext)); final Resources resourcesSpy = Mockito.spy(contextSpy.getResources()); + spyOn(mAtm.mTaskOrganizerController); when(contextSpy.getResources()).thenReturn(resourcesSpy); MockitoSession mockitoSession = mockitoSession().mockStatic(BackNavigationController.class) @@ -597,7 +598,8 @@ public class BackNavigationControllerTests extends WindowTestsBase { mBackAnimationAdapter, task, mRootHomeTask, bottomActivity, homeActivity); assertTrue(toHomeBuilder.mIsLaunchBehind); toHomeBuilder.build(); - verify(animationHandler, never()).createStartingSurface(any()); + verify(mAtm.mTaskOrganizerController, never()) + .addWindowlessStartingSurface(any(), any(), any(), any(), any()); animationHandler.clearBackAnimateTarget(); // Back to ACTIVITY and TASK have the same logic, just with different target. @@ -609,9 +611,11 @@ public class BackNavigationControllerTests extends WindowTestsBase { assertFalse(toActivityBuilder.mIsLaunchBehind); toActivityBuilder.build(); if (preferWindowlessSurface) { - verify(animationHandler).createStartingSurface(any()); + verify(mAtm.mTaskOrganizerController) + .addWindowlessStartingSurface(any(), any(), any(), any(), any()); } else { - verify(animationHandler, never()).createStartingSurface(any()); + verify(mAtm.mTaskOrganizerController, never()) + .addWindowlessStartingSurface(any(), any(), any(), any(), any()); } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 997015ff1c08..b3db2dea4a27 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -365,7 +365,7 @@ public class SoundTriggerService extends SystemService { // Validate package name try { int uid = mPackageManager.getPackageUid(mOriginatorIdentity.packageName, - PackageManager.PackageInfoFlags.of(0)); + PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ANY_USER)); if (!UserHandle.isSameApp(uid, mOriginatorIdentity.uid)) { throw new SecurityException("Uid " + mOriginatorIdentity.uid + " attempted to spoof package name " + diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index feef0497c152..d41c0197addc 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -81,6 +81,19 @@ public class SharedConnectivityManager { mCallback = callback; } + @Override + public void onServiceConnected() { + if (mCallback != null) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onServiceConnected()); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + @Override public void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks) { if (mCallback != null) { final long token = Binder.clearCallingIdentity(); @@ -117,6 +130,7 @@ public class SharedConnectivityManager { } } + @Override public void onHotspotNetworkConnectionStatusChanged( @NonNull HotspotNetworkConnectionStatus status) { if (mCallback != null) { @@ -251,7 +265,6 @@ public class SharedConnectivityManager { synchronized (mProxyDataLock) { mProxyMap.put(callback, proxy); } - callback.onServiceConnected(); } catch (RemoteException e) { Log.e(TAG, "Exception in registerCallback", e); callback.onRegisterCallbackFailed(e); diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl index 737aa6d9964c..521f94367f6f 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl @@ -31,4 +31,5 @@ interface ISharedConnectivityCallback { oneway void onKnownNetworksUpdated(in List<KnownNetwork> networks); oneway void onKnownNetworkConnectionStatusChanged(in KnownNetworkConnectionStatus status); oneway void onSharedConnectivitySettingsChanged(in SharedConnectivitySettingsState state); + oneway void onServiceConnected(); } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java index 2bbe91958383..ebda6f1c5826 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java @@ -276,6 +276,11 @@ public abstract class SharedConnectivityService extends Service { private void onRegisterCallback(ISharedConnectivityCallback callback) { mRemoteCallbackList.register(callback); + try { + callback.onServiceConnected(); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Exception in onRegisterCallback", e); + } if (mCountDownLatch != null) { mCountDownLatch.countDown(); } |