Merge "Create WM Shell Flicker helper library" into udc-qpr-dev
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3a66081..c872516 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -3535,7 +3535,7 @@
* {@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 @@
* <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 @@
*
* <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 1536376..57f7bca 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -4460,7 +4460,7 @@
* {@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 @@
* <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 @@
*
* <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/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 17afd55..4ecfc40 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -844,12 +844,12 @@
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 @@
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 c13b9ab..af24140 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -380,11 +380,17 @@
@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 @@
* 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 @@
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 @@
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 01a99b9..1b1098d 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2539,7 +2539,7 @@
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/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 325ebbe..8e619a8 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -609,4 +609,6 @@
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 fde1a6d..b06cd39 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_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 @@
}
@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 @@
}
@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 @@
}
@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 b37c8fd..bce3f3e 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTest.java
@@ -49,11 +49,11 @@
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 @@
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 @@
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 @@
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/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 04d999b..99c8acd 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 @@
* @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);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 6718565..e698601 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -312,9 +312,13 @@
+ " bubble=" + getBubbleKey());
}
if (mBubble != null) {
- // Must post because this is called from a binder thread.
- post(() -> mController.removeBubble(
- mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED));
+ mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ }
+ if (mTaskView != null) {
+ // Release the surface
+ mTaskView.release();
+ removeView(mTaskView);
+ mTaskView = null;
}
}
@@ -1058,8 +1062,10 @@
}
/**
- * Cleans up anything related to the task and {@code TaskView}. If this view should be reused
- * after this method is called, then
+ * Cleans up anything related to the task. The TaskView itself is released after the task
+ * has been removed.
+ *
+ * If this view should be reused after this method is called, then
* {@link #initialize(BubbleController, BubbleStackView, boolean)} must be invoked first.
*/
public void cleanUpExpandedState() {
@@ -1081,10 +1087,7 @@
}
}
if (mTaskView != null) {
- // Release the surface & other task view related things
- mTaskView.release();
- removeView(mTaskView);
- mTaskView = null;
+ mTaskView.setVisibility(GONE);
}
}
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 76ca68b..517f9f2 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 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 @@
}
/**
+ * 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 9bc28a46..b15fd91 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 @@
* 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 @@
}
/** 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 @@
* Move a task to desktop
*/
fun moveToDesktop(
+ decor: DesktopModeWindowDecoration,
task: RunningTaskInfo,
wct: WindowContainerTransaction = WindowContainerTransaction()
) {
@@ -195,7 +204,7 @@
addMoveToDesktopChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
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 22929c76..16b2393 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 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.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 @@
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 @@
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,138 +182,209 @@
}
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 true;
+ return animateMoveToDesktop(change, startT, finishCallback);
}
- Rect endBounds = change.getEndAbsBounds();
+ if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ return animateStartDragToDesktopMode(change, startT, finishT, finishCallback);
+ }
+
+ 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();
-
- if (mMoveToDesktopAnimator == null
- || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
- Slog.e(TAG, "No animator available for this transition");
- 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;
+ return animateCancelDragToDesktopMode(change, startT, finishT, finishCallback,
+ endBounds);
}
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;
+ }
+
+ 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();
+ }
+
+ // 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;
+ }
+ 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;
+ }
+
+ // 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
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 064af04..a743e99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -317,18 +317,12 @@
// we know about -- so leave clean-up here even if shell transitions are enabled.
if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
- if (mListener != null) {
- final int taskId = taskInfo.taskId;
- mListenerExecutor.execute(() -> {
- mListener.onTaskRemovalStarted(taskId);
- });
- }
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
+ final SurfaceControl taskLeash = mTaskLeash;
+ handleAndNotifyTaskRemoval(mTaskInfo);
// Unparent the task when this surface is destroyed
- mTransaction.reparent(mTaskLeash, null).apply();
+ mTransaction.reparent(taskLeash, null).apply();
resetTaskInfo();
- mTaskViewBase.onTaskVanished(taskInfo);
}
@Override
@@ -498,6 +492,20 @@
}
}
+ /** Notifies listeners of a task being removed and stops intercepting back presses on it. */
+ private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) {
+ if (taskInfo != null) {
+ if (mListener != null) {
+ final int taskId = taskInfo.taskId;
+ mListenerExecutor.execute(() -> {
+ mListener.onTaskRemovalStarted(taskId);
+ });
+ }
+ mTaskViewBase.onTaskVanished(taskInfo);
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, false);
+ }
+ }
+
/** Returns the task info for the task in the TaskView. */
@Nullable
public ActivityManager.RunningTaskInfo getTaskInfo() {
@@ -523,18 +531,12 @@
*/
void cleanUpPendingTask() {
if (mPendingInfo != null) {
- if (mListener != null) {
- final int taskId = mPendingInfo.taskId;
- mListenerExecutor.execute(() -> {
- mListener.onTaskRemovalStarted(taskId);
- });
- }
- mTaskViewBase.onTaskVanished(mPendingInfo);
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mPendingInfo.token, false);
+ final ActivityManager.RunningTaskInfo pendingInfo = mPendingInfo;
+ handleAndNotifyTaskRemoval(pendingInfo);
// Make sure the task is removed
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.removeTask(mPendingInfo.token);
+ wct.removeTask(pendingInfo.token);
mTaskViewTransitions.closeTaskView(wct, this);
}
resetTaskInfo();
@@ -559,16 +561,7 @@
* is used instead.
*/
void prepareCloseAnimation() {
- if (mTaskToken != null) {
- if (mListener != null) {
- final int taskId = mTaskInfo.taskId;
- mListenerExecutor.execute(() -> {
- mListener.onTaskRemovalStarted(taskId);
- });
- }
- mTaskViewBase.onTaskVanished(mTaskInfo);
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
- }
+ handleAndNotifyTaskRemoval(mTaskInfo);
resetTaskInfo();
}
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 e45dacf..e2dce88 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 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 ea9976d..4cc755b 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 @@
&& (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 @@
// 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) {
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 1477cf7..5d87cf8 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.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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
@Test
fun stashDesktopApps_stateUpdates() {
+ whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
controller.stashDesktopApps(DEFAULT_DISPLAY)
assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue()
@@ -634,6 +640,8 @@
@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 @@
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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 50435a0..d098d33 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -607,4 +607,29 @@
verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
verify(mOrganizer).applyTransaction(any());
}
+
+ @Test
+ public void testReleaseInOnTaskRemoval_noNPE() {
+ mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer,
+ mTaskViewTransitions, mSyncQueue));
+ mTaskView = new TaskView(mContext, mTaskViewTaskController);
+ mTaskView.setListener(mExecutor, new TaskView.Listener() {
+ @Override
+ public void onTaskRemovalStarted(int taskId) {
+ mTaskView.release();
+ }
+ });
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+
+ assertThat(mTaskViewTaskController.getTaskInfo()).isEqualTo(mTaskInfo);
+
+ mTaskViewTaskController.prepareCloseAnimation();
+
+ assertThat(mTaskViewTaskController.getTaskInfo()).isNull();
+ }
}
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 2711dad..3af7a45 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/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 6d9497d..b9baa793 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
@@ -46,6 +46,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
import com.android.systemui.R
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
@@ -81,7 +82,7 @@
.asStateFlow()
@Composable
- override fun Content(
+ override fun SceneScope.Content(
modifier: Modifier,
) = BouncerScene(viewModel, dialogFactory, modifier)
}
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 ab7bc26..ca7352e 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
@@ -29,6 +29,7 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.dagger.SysUISingleton
@@ -66,7 +67,7 @@
)
@Composable
- override fun Content(
+ override fun SceneScope.Content(
modifier: Modifier,
) {
LockscreenScene(
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 130395a..29763c2 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,6 +27,7 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.Direction
@@ -57,7 +58,7 @@
.asStateFlow()
@Composable
- override fun Content(
+ override fun SceneScope.Content(
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 a213666..3da6a02 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
@@ -18,9 +18,10 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.SceneScope
import com.android.systemui.scene.shared.model.Scene
/** Compose-capable extension of [Scene]. */
interface ComposableScene : Scene {
- @Composable fun Content(modifier: Modifier)
+ @Composable fun SceneScope.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 0070552..774c409 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,6 +23,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -50,7 +51,7 @@
.asStateFlow()
@Composable
- override fun Content(
+ override fun SceneScope.Content(
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 49e2bf9..3dfdbba 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
@@ -14,32 +14,31 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalAnimationApi::class)
-
package com.android.systemui.scene.ui.composable
-import androidx.activity.compose.BackHandler
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Button
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.ObservableTransitionState as SceneTransitionObservableTransitionState
+import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
+import com.android.compose.animation.scene.observableTransitionState
+import com.android.compose.animation.scene.transitions
import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.ObservableTransitionState
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.viewmodel.SceneContainerViewModel
-import java.util.Locale
+import kotlinx.coroutines.flow.map
/**
* Renders a container of a collection of "scenes" that the user can switch between using certain
@@ -64,77 +63,94 @@
sceneByKey: Map<SceneKey, ComposableScene>,
modifier: Modifier = Modifier,
) {
- val currentScene: SceneModel by viewModel.currentScene.collectAsState()
+ val currentSceneModel: SceneModel by viewModel.currentScene.collectAsState()
+ val currentSceneKey = currentSceneModel.key
+ val currentScene = checkNotNull(sceneByKey[currentSceneKey])
+ val currentDestinations: Map<UserAction, SceneModel> by
+ currentScene.destinationScenes().collectAsState()
+ val state = remember { SceneTransitionLayoutState(currentSceneKey.toTransitionSceneKey()) }
- AnimatedContent(
- targetState = currentScene.key,
- label = "scene container",
- modifier = modifier,
- ) { currentSceneKey ->
- sceneByKey.forEach { (key, composableScene) ->
- if (key == currentSceneKey) {
- Scene(
- scene = composableScene,
- onSceneChanged = viewModel::setCurrentScene,
- modifier = Modifier.fillMaxSize(),
- )
- }
- }
+ DisposableEffect(viewModel, state) {
+ viewModel.setTransitionState(state.observableTransitionState().map { it.toModel() })
+ onDispose { viewModel.setTransitionState(null) }
}
-}
-/** Renders the given [ComposableScene]. */
-@Composable
-private fun Scene(
- scene: ComposableScene,
- onSceneChanged: (SceneModel) -> Unit,
- modifier: Modifier = Modifier,
-) {
- 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)]
- val swipeDownDestinationScene = destinationScenes[UserAction.Swipe(Direction.DOWN)]
- val backDestinationScene = destinationScenes[UserAction.Back]
-
- // TODO(b/280880714): replace with the real UI and make sure to call onTransitionProgress.
- Box(modifier) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.align(Alignment.Center),
- ) {
- scene.Content(
- modifier = Modifier,
- )
-
- Row(
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- ) {
- DirectionalButton(Direction.LEFT, swipeLeftDestinationScene, onSceneChanged)
- DirectionalButton(Direction.UP, swipeUpDestinationScene, onSceneChanged)
- DirectionalButton(Direction.RIGHT, swipeRightDestinationScene, onSceneChanged)
- DirectionalButton(Direction.DOWN, swipeDownDestinationScene, onSceneChanged)
- }
-
- if (backDestinationScene != null) {
- BackHandler { onSceneChanged.invoke(backDestinationScene) }
- }
- }
- }
-}
-
-@Composable
-private fun DirectionalButton(
- direction: Direction,
- destinationScene: SceneModel?,
- onSceneChanged: (SceneModel) -> Unit,
- modifier: Modifier = Modifier,
-) {
- Button(
- onClick = { destinationScene?.let { onSceneChanged.invoke(it) } },
- enabled = destinationScene != null,
- modifier = modifier,
+ SceneTransitionLayout(
+ currentScene = currentSceneKey.toTransitionSceneKey(),
+ onChangeScene = { sceneKey -> viewModel.setCurrentScene(sceneKey.toModel()) },
+ transitions = transitions {},
+ state = state,
+ modifier = modifier.fillMaxSize(),
) {
- Text(direction.name.lowercase(Locale.getDefault()))
+ sceneByKey.forEach { (sceneKey, composableScene) ->
+ scene(
+ key = sceneKey.toTransitionSceneKey(),
+ userActions =
+ if (sceneKey == currentSceneKey) {
+ currentDestinations
+ } else {
+ composableScene.destinationScenes().value
+ }
+ .map { (userAction, destinationSceneModel) ->
+ toTransitionModels(userAction, destinationSceneModel)
+ }
+ .toMap(),
+ ) {
+ with(composableScene) {
+ this@scene.Content(
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
+ }
+ }
+ }
+}
+
+// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
+private fun SceneTransitionObservableTransitionState.toModel(): ObservableTransitionState {
+ return when (this) {
+ is SceneTransitionObservableTransitionState.Idle ->
+ ObservableTransitionState.Idle(scene.toModel().key)
+ is SceneTransitionObservableTransitionState.Transition ->
+ ObservableTransitionState.Transition(
+ fromScene = fromScene.toModel().key,
+ toScene = toScene.toModel().key,
+ progress = progress,
+ )
+ }
+}
+
+// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
+private fun toTransitionModels(
+ userAction: UserAction,
+ sceneModel: SceneModel,
+): Pair<SceneTransitionUserAction, SceneTransitionSceneKey> {
+ return userAction.toTransitionUserAction() to sceneModel.key.toTransitionSceneKey()
+}
+
+// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
+private fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
+ return SceneTransitionSceneKey(
+ name = toString(),
+ identity = this,
+ )
+}
+
+// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
+private fun SceneTransitionSceneKey.toModel(): SceneModel {
+ return SceneModel(key = identity as SceneKey)
+}
+
+// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
+private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction {
+ return when (this) {
+ is UserAction.Swipe ->
+ when (this.direction) {
+ Direction.LEFT -> Swipe.Left
+ Direction.UP -> Swipe.Up
+ Direction.RIGHT -> Swipe.Right
+ Direction.DOWN -> Swipe.Down
+ }
+ is UserAction.Back -> Back
}
}
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 b73e0b2..ff1cb5f 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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.notifications.ui.composable.Notifications
@@ -63,7 +64,7 @@
)
@Composable
- override fun Content(
+ override fun SceneScope.Content(
modifier: Modifier,
) = ShadeScene(viewModel, modifier)
@@ -86,11 +87,12 @@
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier =
- Modifier.fillMaxSize()
+ modifier
+ .fillMaxSize()
.clickable(onClick = { viewModel.onContentClicked() })
.padding(horizontal = 16.dp, vertical = 48.dp)
) {
- QuickSettings(modifier = modifier.height(160.dp))
- Notifications(modifier = modifier.weight(1f))
+ QuickSettings(modifier = Modifier.height(160.dp))
+ Notifications(modifier = Modifier.weight(1f))
}
}
diff --git a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml b/packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml
similarity index 65%
copy from packages/SystemUI/res/drawable/controls_list_divider_inset.xml
copy to packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml
index ddfa18c..2fb7222 100644
--- a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml
+++ b/packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!--
+ 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.
@@ -13,8 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<inset
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/controls_list_divider"
- android:insetRight="@dimen/control_spinner_padding_horizontal"
- android:insetLeft="@dimen/control_spinner_padding_horizontal" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/transparent" />
+ <size
+ android:width="@dimen/control_list_horizontal_spacing"
+ android:height="@dimen/control_list_vertical_spacing" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/controls_list_divider.xml b/packages/SystemUI/res/drawable/global_actions_list_divider.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/controls_list_divider.xml
rename to packages/SystemUI/res/drawable/global_actions_list_divider.xml
diff --git a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml b/packages/SystemUI/res/drawable/global_actions_list_divider_inset.xml
similarity index 93%
rename from packages/SystemUI/res/drawable/controls_list_divider_inset.xml
rename to packages/SystemUI/res/drawable/global_actions_list_divider_inset.xml
index ddfa18c..170bc0d 100644
--- a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml
+++ b/packages/SystemUI/res/drawable/global_actions_list_divider_inset.xml
@@ -15,6 +15,6 @@
-->
<inset
xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/controls_list_divider"
+ android:drawable="@drawable/global_actions_list_divider"
android:insetRight="@dimen/control_spinner_padding_horizontal"
android:insetLeft="@dimen/control_spinner_padding_horizontal" />
diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml
index e1dbe69..f1939bb 100644
--- a/packages/SystemUI/res/layout/controls_base_item.xml
+++ b/packages/SystemUI/res/layout/controls_base_item.xml
@@ -25,7 +25,6 @@
android:focusable="true"
android:screenReaderFocusable="true"
android:stateListAnimator="@anim/control_state_list_animator"
- android:layout_marginStart="@dimen/control_spacing"
android:background="@drawable/control_background">
<ImageView
diff --git a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml b/packages/SystemUI/res/layout/controls_list_view.xml
similarity index 61%
copy from packages/SystemUI/res/drawable/controls_list_divider_inset.xml
copy to packages/SystemUI/res/layout/controls_list_view.xml
index ddfa18c..2831cbf 100644
--- a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml
+++ b/packages/SystemUI/res/layout/controls_list_view.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!--
+ 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.
@@ -13,8 +14,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<inset
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/controls_list_divider"
- android:insetRight="@dimen/control_spinner_padding_horizontal"
- android:insetLeft="@dimen/control_spinner_padding_horizontal" />
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/controls_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:divider="@drawable/controls_list_divider"
+ android:orientation="vertical"
+ android:showDividers="middle" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_row.xml b/packages/SystemUI/res/layout/controls_row.xml
index 4cc461a..4923b05 100644
--- a/packages/SystemUI/res/layout/controls_row.xml
+++ b/packages/SystemUI/res/layout/controls_row.xml
@@ -14,9 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/control_spacing" />
+ android:divider="@drawable/controls_list_divider"
+ android:orientation="horizontal"
+ android:showDividers="middle" />
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index b1259e4..c60fb26 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -20,7 +20,7 @@
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/controls_header_menu_size"
android:paddingHorizontal="@dimen/controls_header_horizontal_padding"
android:layout_marginBottom="@dimen/controls_header_bottom_margin"
android:orientation="horizontal">
@@ -85,10 +85,11 @@
android:layout_weight="1"
android:clipChildren="true"
android:orientation="vertical"
- android:paddingHorizontal="16dp"
+ android:padding="@dimen/controls_content_padding"
+ android:background="@drawable/controls_panel_background"
android:scrollbars="none">
- <include layout="@layout/global_actions_controls_list_view" />
+ <include layout="@layout/controls_list_view" />
</ScrollView>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index 8bff1a1..6de10b4 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -14,34 +14,27 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<FrameLayout
+<com.android.systemui.shared.shadow.DoubleShadowTextClock
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <com.android.systemui.shared.shadow.DoubleShadowTextClock
- android:id="@+id/time_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:fontFamily="@*android:string/config_clockFontFamily"
- android:textColor="@android:color/white"
- android:format12Hour="@string/dream_time_complication_12_hr_time_format"
- android:format24Hour="@string/dream_time_complication_24_hr_time_format"
- android:fontFeatureSettings="pnum, lnum"
- android:includeFontPadding="false"
- android:letterSpacing="0.02"
- android:maxLines="1"
- android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
- app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
- app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
- app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
- app:keyShadowAlpha="0.3"
- app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius"
- app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
- app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
- app:ambientShadowAlpha="0.3"
- app:removeTextDescent="true"
- app:textDescentExtraPadding="@dimen/dream_overlay_clock_text_descent_extra_padding" />
-
-</FrameLayout>
+ android:layout_height="wrap_content"
+ android:fontFamily="@*android:string/config_clockFontFamily"
+ android:textColor="@android:color/white"
+ android:format12Hour="@string/dream_time_complication_12_hr_time_format"
+ android:format24Hour="@string/dream_time_complication_24_hr_time_format"
+ android:fontFeatureSettings="pnum, lnum"
+ android:includeFontPadding="false"
+ android:letterSpacing="0.02"
+ android:maxLines="1"
+ android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
+ app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
+ app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
+ app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
+ app:keyShadowAlpha="0.3"
+ app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius"
+ app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
+ app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
+ app:ambientShadowAlpha="0.3"
+ app:removeTextDescent="true"
+ app:textDescentExtraPadding="@dimen/dream_overlay_clock_text_descent_extra_padding" />
diff --git a/packages/SystemUI/res/layout/global_actions_controls_list_view.xml b/packages/SystemUI/res/layout/global_actions_controls_list_view.xml
deleted file mode 100644
index e1c2611..0000000
--- a/packages/SystemUI/res/layout/global_actions_controls_list_view.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/global_actions_controls_list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_marginLeft="@dimen/global_actions_side_margin"
- android:layout_marginRight="@dimen/global_actions_side_margin" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_power_dialog.xml b/packages/SystemUI/res/layout/global_actions_power_dialog.xml
index ff3f0fb..3456515 100644
--- a/packages/SystemUI/res/layout/global_actions_power_dialog.xml
+++ b/packages/SystemUI/res/layout/global_actions_power_dialog.xml
@@ -18,7 +18,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:divider="@drawable/controls_list_divider"
+ android:divider="@drawable/global_actions_list_divider"
android:showDividers="middle"
/>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 1681f7a..0667cd8 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -69,6 +69,9 @@
<dimen name="controls_header_horizontal_padding">12dp</dimen>
<dimen name="controls_content_margin_horizontal">16dp</dimen>
+ <dimen name="controls_content_padding">16dp</dimen>
+ <dimen name="control_list_vertical_spacing">8dp</dimen>
+ <dimen name="control_list_horizontal_spacing">16dp</dimen>
<!-- Rear Display Education dimens -->
<dimen name="rear_display_animation_width">246dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 2b1d9d6..7e892f7 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -103,4 +103,7 @@
<dimen name="controls_header_horizontal_padding">12dp</dimen>
<dimen name="controls_content_margin_horizontal">24dp</dimen>
+ <dimen name="controls_content_padding">24dp</dimen>
+ <dimen name="control_list_vertical_spacing">8dp</dimen>
+ <dimen name="control_list_horizontal_spacing">16dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index de913ac..d74eca6 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -24,6 +24,9 @@
<dimen name="controls_header_horizontal_padding">28dp</dimen>
<dimen name="controls_content_margin_horizontal">40dp</dimen>
+ <dimen name="controls_content_padding">32dp</dimen>
+ <dimen name="control_list_vertical_spacing">16dp</dimen>
+ <dimen name="control_list_horizontal_spacing">16dp</dimen>
<dimen name="large_screen_shade_header_height">56dp</dimen>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 9f4fc39..e942258 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -195,7 +195,7 @@
<color name="control_primary_text">#E6FFFFFF</color>
<color name="control_secondary_text">#99FFFFFF</color>
<color name="control_default_foreground">@color/GM2_grey_500</color>
- <color name="control_default_background">@color/GM2_grey_900</color>
+ <color name="control_default_background">#303134</color>
<color name="control_spinner_dropdown">@*android:color/foreground_material_dark</color>
<color name="control_more_vert">@*android:color/foreground_material_dark</color>
<color name="control_enabled_light_background">#413C2D</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index de8287e..9bee972 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1238,6 +1238,7 @@
<dimen name="controls_header_app_icon_size">24dp</dimen>
<dimen name="controls_top_margin">48dp</dimen>
<dimen name="controls_content_margin_horizontal">0dp</dimen>
+ <dimen name="controls_content_padding">16dp</dimen>
<dimen name="control_header_text_size">24sp</dimen>
<dimen name="control_item_text_size">14sp</dimen>
<dimen name="control_menu_item_text_size">16sp</dimen>
@@ -1256,6 +1257,8 @@
<dimen name="control_chevron_icon_size">20dp</dimen>
<dimen name="control_spacing">8dp</dimen>
<dimen name="control_list_divider">1dp</dimen>
+ <dimen name="control_list_vertical_spacing">8dp</dimen>
+ <dimen name="control_list_horizontal_spacing">12dp</dimen>
<dimen name="control_corner_radius">14dp</dimen>
<dimen name="control_height">104dp</dimen>
<dimen name="control_padding">12dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 16eb21d..4d196aa 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -421,6 +421,7 @@
* 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/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index fe6bbc1..8e92941 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -340,6 +340,7 @@
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onTimeChanged() {
+ Slog.v(TAG, "onTimeChanged");
refreshTime();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
index e60d4e1..0c7d56f 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 @@
)
} 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/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index ebff0b0..39a45f7 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 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.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 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 @@
@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 @@
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 9bbf1ef..b68b921 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.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 @@
// 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/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
index 9c3448b..dc32a59 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
@@ -16,34 +16,36 @@
package com.android.systemui.complication;
-import static com.android.systemui.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
+import static com.android.systemui.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
import android.view.View;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
+import com.android.systemui.complication.dagger.DreamClockTimeComplicationComponent;
import com.android.systemui.dagger.qualifiers.SystemUser;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.ViewController;
import com.android.systemui.util.condition.ConditionalCoreStartable;
import javax.inject.Inject;
import javax.inject.Named;
-import javax.inject.Provider;
/**
* Clock Time Complication that produce Clock Time view holder.
*/
public class DreamClockTimeComplication implements Complication {
- private final Provider<DreamClockTimeViewHolder> mDreamClockTimeViewHolderProvider;
+ private final DreamClockTimeComplicationComponent.Factory mComponentFactory;
/**
* Default constructor for {@link DreamClockTimeComplication}.
*/
@Inject
public DreamClockTimeComplication(
- Provider<DreamClockTimeViewHolder> dreamClockTimeViewHolderProvider) {
- mDreamClockTimeViewHolderProvider = dreamClockTimeViewHolderProvider;
+ DreamClockTimeComplicationComponent.Factory componentFactory) {
+ mComponentFactory = componentFactory;
}
@Override
@@ -56,7 +58,7 @@
*/
@Override
public ViewHolder createView(ComplicationViewModel model) {
- return mDreamClockTimeViewHolderProvider.get();
+ return mComponentFactory.create().getViewHolder();
}
/**
@@ -94,11 +96,14 @@
private final ComplicationLayoutParams mLayoutParams;
@Inject
- DreamClockTimeViewHolder(@Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view,
+ DreamClockTimeViewHolder(
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view,
@Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
- ComplicationLayoutParams layoutParams) {
+ ComplicationLayoutParams layoutParams,
+ DreamClockTimeViewController viewController) {
mView = view;
mLayoutParams = layoutParams;
+ viewController.init();
}
@Override
@@ -111,4 +116,29 @@
return mLayoutParams;
}
}
+
+ static class DreamClockTimeViewController extends ViewController<View> {
+ private final UiEventLogger mUiEventLogger;
+
+ @Inject
+ DreamClockTimeViewController(
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view,
+ UiEventLogger uiEventLogger) {
+ super(view);
+
+ mUiEventLogger = uiEventLogger;
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mView.setOnClickListener(this::onClick);
+ }
+
+ @Override
+ protected void onViewDetached() {}
+
+ private void onClick(View v) {
+ mUiEventLogger.log(DreamOverlayUiEvent.DREAM_CLOCK_TAPPED);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
index 4d99282..7ac1cc7 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
@@ -29,8 +29,6 @@
import android.view.View;
import android.widget.ImageView;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -201,23 +199,6 @@
private final UiEventLogger mUiEventLogger;
- @VisibleForTesting
- public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "The home controls on the screensaver has been tapped.")
- DREAM_HOME_CONTROLS_TAPPED(1212);
-
- private final int mId;
-
- DreamOverlayEvent(int id) {
- mId = id;
- }
-
- @Override
- public int getId() {
- return mId;
- }
- }
-
@Inject
DreamHomeControlsChipViewController(
@Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
@@ -246,7 +227,7 @@
private void onClickHomeControls(View v) {
if (DEBUG) Log.d(TAG, "home controls complication tapped");
- mUiEventLogger.log(DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+ mUiEventLogger.log(DreamOverlayUiEvent.DREAM_HOME_CONTROLS_TAPPED);
final Intent intent = new Intent(mContext, ControlsActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt b/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt
new file mode 100644
index 0000000..17cc829
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.complication
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger.UiEventEnum
+
+/** UI log events for the dream overlay. */
+enum class DreamOverlayUiEvent(private val mId: Int) : UiEventEnum {
+ @UiEvent(doc = "The home controls on the screensaver has been tapped.")
+ DREAM_HOME_CONTROLS_TAPPED(1212),
+ @UiEvent(doc = "The clock on the screensaver has been tapped") DREAM_CLOCK_TAPPED(1440),
+ @UiEvent(doc = "The weather on the screensaver has been tapped") DREAM_WEATHER_TAPPED(1441);
+
+ override fun getId(): Int {
+ return mId
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
new file mode 100644
index 0000000..87c3b2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.complication.dagger
+
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.TextClock
+import com.android.internal.util.Preconditions
+import com.android.systemui.R
+import com.android.systemui.complication.DreamClockTimeComplication
+import com.android.systemui.complication.DreamClockTimeComplication.DreamClockTimeViewHolder
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import javax.inject.Named
+import javax.inject.Scope
+
+/** Responsible for generating dependencies for the [DreamClockTimeComplication]. */
+@Subcomponent(
+ modules = [DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule::class]
+)
+@DreamClockTimeComplicationComponent.DreamClockTimeComplicationScope
+interface DreamClockTimeComplicationComponent {
+ /** Scope of the clock complication. */
+ @MustBeDocumented
+ @Retention(AnnotationRetention.RUNTIME)
+ @Scope
+ annotation class DreamClockTimeComplicationScope
+
+ /** Factory that generates a component for the clock complication. */
+ @Subcomponent.Factory
+ interface Factory {
+ fun create(): DreamClockTimeComplicationComponent
+ }
+
+ /** Creates a view holder for the clock complication. */
+ fun getViewHolder(): DreamClockTimeViewHolder
+
+ /** Module for providing injected values within the clock complication scope. */
+ @Module
+ interface DreamClockTimeComplicationModule {
+ companion object {
+ const val DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view"
+ private const val TAG_WEIGHT = "'wght' "
+ private const val WEIGHT = 400
+
+ /** Provides the complication view. */
+ @Provides
+ @DreamClockTimeComplicationScope
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW)
+ fun provideComplicationView(layoutInflater: LayoutInflater): View {
+ val view =
+ Preconditions.checkNotNull(
+ layoutInflater.inflate(
+ R.layout.dream_overlay_complication_clock_time,
+ /* root = */ null,
+ /* attachToRoot = */ false,
+ ) as TextClock,
+ "R.layout.dream_overlay_complication_clock_time did not properly inflate"
+ )
+ view.setFontVariationSettings(TAG_WEIGHT + WEIGHT)
+ return view
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationModule.java
deleted file mode 100644
index fd711ee..0000000
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationModule.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.complication.dagger;
-
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextClock;
-
-import com.android.internal.util.Preconditions;
-import com.android.systemui.R;
-import com.android.systemui.complication.DreamClockTimeComplication;
-
-import dagger.Module;
-import dagger.Provides;
-
-import javax.inject.Named;
-
-/**
- * Module for providing {@link DreamClockTimeComplication}.
- */
-@Module
-public interface DreamClockTimeComplicationModule {
- String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view";
- String TAG_WEIGHT = "'wght' ";
- int WEIGHT = 400;
-
- /**
- * Provides the complication view.
- */
- @Provides
- @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW)
- static View provideComplicationView(LayoutInflater layoutInflater) {
- final View view = Preconditions.checkNotNull(
- layoutInflater.inflate(R.layout.dream_overlay_complication_clock_time,
- null, false),
- "R.layout.dream_overlay_complication_clock_time did not properly inflated");
- ((TextClock) view.findViewById(R.id.time_view)).setFontVariationSettings(
- TAG_WEIGHT + WEIGHT);
- return view;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java
index 98975fbd..776c972 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java
@@ -35,10 +35,9 @@
* Module for all components with corresponding dream layer complications registered in
* {@link SystemUIBinder}.
*/
-@Module(includes = {
- DreamClockTimeComplicationModule.class,
- },
+@Module(
subcomponents = {
+ DreamClockTimeComplicationComponent.class,
DreamHomeControlsComplicationComponent.class,
DreamMediaEntryComplicationComponent.class
})
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 b387e4a..4c9dbe0 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.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 @@
* 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 @@
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 @@
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 59fa7f5..23721c9 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.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.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 @@
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 @@
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 @@
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 @@
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 @@
)
}
}
- 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 @@
override fun onStart() {
super.onStart()
- listingController.addCallback(listingCallback)
userTracker.addCallback(userTrackerCallback, executor)
if (DEBUG) {
@@ -440,7 +398,6 @@
override fun onStop() {
super.onStop()
- listingController.removeCallback(listingCallback)
userTracker.removeCallback(userTrackerCallback)
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index 5c2402b..4aef209 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -20,8 +20,6 @@
import android.content.Context
import android.content.SharedPreferences
import com.android.systemui.R
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
@@ -33,7 +31,6 @@
private val context: Context,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
- private val featureFlags: FeatureFlags,
) : AuthorizedPanelsRepository {
override fun getAuthorizedPanels(): Set<String> {
@@ -74,17 +71,8 @@
userTracker.userId,
)
- // We should add default packages in two cases:
- // 1) We've never run this
- // 2) APP_PANELS_REMOVE_APPS_ALLOWED got disabled after user removed all apps
- val needToSetup =
- if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
- sharedPref.getStringSet(KEY, null) == null
- } else {
- // There might be an empty set that need to be overridden after the feature has been
- // turned off after being turned on
- sharedPref.getStringSet(KEY, null).isNullOrEmpty()
- }
+ // We should add default packages when we've never run this
+ val needToSetup = sharedPref.getStringSet(KEY, null) == null
if (needToSetup) {
sharedPref.edit().putStringSet(KEY, getPreferredPackages()).apply()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index 0fb5b66..c9edd4a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -21,7 +21,6 @@
import android.content.SharedPreferences
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
@@ -83,11 +82,7 @@
}
override fun shouldAddDefaultComponent(): Boolean =
- if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
- sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
- } else {
- true
- }
+ sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
sharedPreferences.edit().putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd).apply()
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 631ed3c..b0491ce 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -72,7 +72,6 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -506,32 +505,22 @@
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),
- ADD_CONTROLS_ID
+ context.getText(R.string.controls_menu_add_another_app),
+ ADD_APP_ID
))
}
+ add(OverflowMenuAdapter.MenuItem(
+ context.getText(R.string.controls_menu_remove),
+ REMOVE_APP_ID,
+ ))
if (!isPanel) {
add(OverflowMenuAdapter.MenuItem(
context.getText(R.string.controls_menu_edit),
@@ -665,7 +654,7 @@
val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources)
- val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup
+ val listView = parent.requireViewById(R.id.controls_list) as ViewGroup
listView.removeAllViews()
var lastRow: ViewGroup = createRow(inflater, listView)
selectedStructure.controls.forEach {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 063a4d0..5a49475 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -270,7 +270,7 @@
/** Migrate the lock icon view to the new keyguard root view. */
// TODO(b/286552209): Tracking bug.
- @JvmField val MIGRATE_LOCK_ICON = releasedFlag(240, "migrate_lock_icon")
+ @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon", teamfood = true)
// TODO(b/288276738): Tracking bug.
@JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag(241, "widget_on_keyguard")
@@ -617,7 +617,8 @@
val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag(1702, "clipboard_image_timeout", teamfood = true)
// TODO(b/279405451): Tracking Bug
@JvmField
- val CLIPBOARD_SHARED_TRANSITIONS = unreleasedFlag(1703, "clipboard_shared_transitions")
+ val CLIPBOARD_SHARED_TRANSITIONS =
+ unreleasedFlag(1703, "clipboard_shared_transitions", teamfood = true)
// TODO(b/283300105): Tracking Bug
@JvmField val SCENE_CONTAINER = unreleasedFlag(1802, "scene_container")
@@ -628,14 +629,6 @@
// 2000 - device controls
@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
- val APP_PANELS_REMOVE_APPS_ALLOWED = releasedFlag(2003, "app_panels_remove_apps_allowed")
-
// 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
// TODO(b/259264861): Tracking Bug
@JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
index de67ba8..6d083f6 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
@@ -87,7 +87,7 @@
if (mIsDropDownMode) {
// use a divider
listView.setDividerHeight(res.getDimensionPixelSize(R.dimen.control_list_divider));
- listView.setDivider(res.getDrawable(R.drawable.controls_list_divider_inset));
+ listView.setDivider(res.getDrawable(R.drawable.global_actions_list_divider_inset));
} else {
if (mAdapter == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5d7ea1c..e2929ae 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.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 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
flags |= StatusBarManager.DISABLE_RECENT;
}
- if (mPowerGestureIntercepted && mOccluded && isSecure()) {
+ if (mPowerGestureIntercepted && mOccluded && isSecure()
+ && mUpdateMonitor.isFaceEnrolled()) {
flags |= StatusBarManager.DISABLE_RECENT;
}
@@ -3313,9 +3381,14 @@
}
}
- 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/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 2085c87..888f746 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -24,9 +24,11 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
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 kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
@@ -43,19 +45,27 @@
) {
override fun start() {
- listenForAodToLockscreen()
+ listenForAodToLockscreenOrOccluded()
listenForAodToGone()
}
- private fun listenForAodToLockscreen() {
+ private fun listenForAodToLockscreenOrOccluded() {
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.FINISH)
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
- .collect { pair ->
- val (dozeToAod, lastStartedStep) = pair
+ .sample(
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ keyguardInteractor.isKeyguardOccluded,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (_, lastStartedStep, occluded) ->
if (lastStartedStep.to == KeyguardState.AOD) {
- startTransitionTo(KeyguardState.LOCKSCREEN)
+ startTransitionTo(
+ if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index c867c43..76d9893 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -23,10 +23,12 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
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.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
@@ -43,20 +45,29 @@
) {
override fun start() {
- listenForDozingToLockscreen()
+ listenForDozingToLockscreenOrOccluded()
listenForDozingToGone()
}
- private fun listenForDozingToLockscreen() {
+ private fun listenForDozingToLockscreenOrOccluded() {
scope.launch {
keyguardInteractor.wakefulnessModel
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
- .collect { (wakefulnessModel, lastStartedTransition) ->
+ .sample(
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ keyguardInteractor.isKeyguardOccluded,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (wakefulnessModel, lastStartedTransition, occluded) ->
if (
wakefulnessModel.isStartingToWakeOrAwake() &&
lastStartedTransition.to == KeyguardState.DOZING
) {
- startTransitionTo(KeyguardState.LOCKSCREEN)
+ startTransitionTo(
+ if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+ )
}
}
}
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 0000000..dac6ef5
--- /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 98d7434..954ff6f 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 @@
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 f82633f..2b08b3d 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 @@
listenForGoneToAodOrDozing()
listenForGoneToDreaming()
listenForGoneToLockscreen()
+ listenForGoneToDreamingLockscreenHosted()
}
// Primarily for when the user chooses to lock down the device
@@ -63,12 +64,35 @@
}
}
+ 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 ed1bf3e..6b28b27 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 @@
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 e1754f5..9142d1f 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.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 @@
listenForPrimaryBouncerToGone()
listenForPrimaryBouncerToAodOrDozing()
listenForPrimaryBouncerToLockscreenOrOccluded()
+ listenForPrimaryBouncerToDreamingLockscreenHosted()
}
private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
@@ -62,17 +65,24 @@
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 @@
}
}
+ 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 864c4db..53d3c07 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
@@ -37,7 +37,6 @@
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
@@ -101,7 +100,7 @@
/** 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 =
@@ -241,8 +240,16 @@
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/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index efc1bd0..ba7b987 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 @@
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 45bf20d..8c4c7ae 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.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 @@
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 @@
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 4244e55..6115d90 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 @@
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/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index d9690b7..56f5529 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 @@
@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/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 87b4321..1e20cdb 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 @@
* 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 0000000..113f01c
--- /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/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 44e1fd1..cca96b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -66,6 +66,23 @@
},
)
+ /** Lockscreen alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 50.milliseconds,
+ onStart = {
+ leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+ willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
+ },
+ onStep = {
+ if (willRunDismissFromKeyguard || leaveShadeOpen) {
+ 1f
+ } else {
+ 0f
+ }
+ },
+ )
+
/** Scrim alpha values */
val scrimAlpha: Flow<ScrimAlpha> =
transitionAnimation
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 b307f1b..dd58607 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 @@
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/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 2adc211..0842fe0 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.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 @@
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 @@
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/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 3e7bdd1..9bb192b 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.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 @@
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/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 1fca488..fee3960 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
@@ -14,16 +14,23 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.scene.data.repository
+import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.SceneTransitionModel
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
/** Source of truth for scene framework application state. */
class SceneContainerRepository
@@ -38,8 +45,17 @@
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 transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
+ val transitionProgress: Flow<Float> =
+ transitionState.flatMapLatest { observableTransitionStateFlow ->
+ observableTransitionStateFlow?.flatMapLatest { observableTransitionState ->
+ when (observableTransitionState) {
+ is ObservableTransitionState.Idle -> flowOf(1f)
+ is ObservableTransitionState.Transition -> observableTransitionState.progress
+ }
+ }
+ ?: flowOf(1f)
+ }
private val _transitions = MutableStateFlow<SceneTransitionModel?>(null)
val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow()
@@ -92,8 +108,12 @@
_isVisible.value = isVisible
}
- /** Sets scene transition progress to the current scene in the container with the given name. */
- fun setSceneTransitionProgress(progress: Float) {
- _transitionProgress.value = progress
+ /**
+ * Binds the given flow so the system remembers it.
+ *
+ * Note that you must call is with `null` when the UI is done or risk a memory leak.
+ */
+ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
+ this.transitionState.value = transitionState
}
}
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 39daad3..b09a5cf 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
@@ -18,11 +18,13 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.scene.shared.model.ObservableTransitionState
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 com.android.systemui.scene.shared.model.SceneTransitionModel
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -69,13 +71,17 @@
/** Whether the container with the given name is visible. */
val isVisible: StateFlow<Boolean> = repository.isVisible
- /** Sets scene transition progress to the current scene in the container with the given name. */
- fun setSceneTransitionProgress(progress: Float) {
- repository.setSceneTransitionProgress(progress)
+ /**
+ * Binds the given flow so the system remembers it.
+ *
+ * Note that you must call is with `null` when the UI is done or risk a memory leak.
+ */
+ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
+ repository.setTransitionState(transitionState)
}
/** Progress of the transition into the current scene in the container with the given name. */
- val transitionProgress: StateFlow<Float> = repository.transitionProgress
+ val transitionProgress: Flow<Float> = repository.transitionProgress
/**
* Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
new file mode 100644
index 0000000..9a30aa6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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.shared.model
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * This is a fork of a class by the same name in the `com.android.compose.animation.scene` package.
+ *
+ * TODO(b/293899074): remove this fork, once we can compile Compose into System UI.
+ */
+sealed class ObservableTransitionState {
+ /** No transition/animation is currently running. */
+ data class Idle(val scene: SceneKey) : ObservableTransitionState()
+
+ /** There is a transition animating between two scenes. */
+ data class Transition(
+ val fromScene: SceneKey,
+ val toScene: SceneKey,
+ val progress: Flow<Float>,
+ ) : ObservableTransitionState()
+}
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 f44748a..bd73e36 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
@@ -19,10 +19,12 @@
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.ObservableTransitionState
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.Flow
import kotlinx.coroutines.flow.StateFlow
/** Models UI state for the scene container. */
@@ -54,9 +56,13 @@
interactor.setCurrentScene(scene)
}
- /** Notifies of the progress of a scene transition. */
- fun setSceneTransitionProgress(progress: Float) {
- interactor.setSceneTransitionProgress(progress)
+ /**
+ * Binds the given flow so the system remembers it.
+ *
+ * Note that you must call is with `null` when the UI is done or risk a memory leak.
+ */
+ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
+ interactor.setTransitionState(transitionState)
}
/** Handles a [MotionEvent] representing remote user input. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 2192b436..416f147 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -140,12 +140,14 @@
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;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
@@ -597,7 +599,11 @@
private final OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel;
private final LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel;
private final GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
+ private final GoneToDreamingLockscreenHostedTransitionViewModel
+ mGoneToDreamingLockscreenHostedTransitionViewModel;
+
private final LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
+ private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final KeyguardInteractor mKeyguardInteractor;
@@ -605,6 +611,7 @@
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 +659,25 @@
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,7 +760,10 @@
OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel,
LockscreenToDreamingTransitionViewModel lockscreenToDreamingTransitionViewModel,
GoneToDreamingTransitionViewModel goneToDreamingTransitionViewModel,
+ GoneToDreamingLockscreenHostedTransitionViewModel
+ goneToDreamingLockscreenHostedTransitionViewModel,
LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
+ PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
@Main CoroutineDispatcher mainDispatcher,
KeyguardTransitionInteractor keyguardTransitionInteractor,
DumpManager dumpManager,
@@ -761,7 +790,10 @@
mOccludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel;
mLockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel;
mGoneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel;
+ mGoneToDreamingLockscreenHostedTransitionViewModel =
+ goneToDreamingLockscreenHostedTransitionViewModel;
mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel;
+ mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mKeyguardInteractor = keyguardInteractor;
mKeyguardViewConfigurator = keyguardViewConfigurator;
@@ -1091,6 +1123,24 @@
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);
@@ -1126,6 +1176,10 @@
collectFlow(mView, mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY(
mLockscreenToOccludedTransitionTranslationY),
setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
+
+ // Primary bouncer->Gone (ensures lockscreen content is not visible on successful auth)
+ collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(),
+ setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
}
@VisibleForTesting
@@ -1496,13 +1550,18 @@
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 +1684,10 @@
// 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 +1716,11 @@
return mDozing && mDozeParameters.getAlwaysOn();
}
+
+ private boolean isActiveDreamLockscreenHosted() {
+ return mKeyguardInteractor.isActiveDreamLockscreenHosted().getValue();
+ }
+
private boolean hasVisibleNotifications() {
return mNotificationStackScrollLayoutController
.getVisibleNotificationCount() != 0
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 31d4ab9..ea3a8f5 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 @@
NotificationSectionHeadersModule.class,
NotificationListViewModelModule.class,
ActivatableNotificationViewModelModule.class,
+ NotificationMemoryModule.class,
})
public interface NotificationsModule {
@Binds
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 cc08778..adff681 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 @@
};
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 @@
// 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/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 1c3a8850..dabdcc5 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.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 @@
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 @@
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 @@
}
};
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 @@
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/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
index 1e73cb3..1e65566 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 @@
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/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 87b2697..6219e4d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -945,6 +945,7 @@
showRingerDrawer();
}
});
+ updateSelectedRingerContainerDescription(mIsRingerDrawerOpen);
mRingerDrawerVibrate.setOnClickListener(
new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE));
@@ -1007,6 +1008,19 @@
: 0;
}
+ @VisibleForTesting String getSelectedRingerContainerDescription() {
+ return mSelectedRingerContainer == null ? null :
+ mSelectedRingerContainer.getContentDescription().toString();
+ }
+
+ @VisibleForTesting void toggleRingerDrawer(boolean show) {
+ if (show) {
+ showRingerDrawer();
+ } else {
+ hideRingerDrawer();
+ }
+ }
+
/** Animates in the ringer drawer. */
private void showRingerDrawer() {
if (mIsRingerDrawerOpen) {
@@ -1084,12 +1098,7 @@
.start();
}
- // When the ringer drawer is open, tapping the currently selected ringer will set the ringer
- // to the current ringer mode. Change the content description to that, instead of the 'tap
- // to change ringer mode' default.
- mSelectedRingerContainer.setContentDescription(
- mContext.getString(getStringDescriptionResourceForRingerMode(
- mState.ringerModeInternal)));
+ updateSelectedRingerContainerDescription(true);
mIsRingerDrawerOpen = true;
}
@@ -1135,14 +1144,38 @@
.translationY(0f)
.start();
- // When the drawer is closed, tapping the selected ringer drawer will open it, allowing the
- // user to change the ringer.
- mSelectedRingerContainer.setContentDescription(
- mContext.getString(R.string.volume_ringer_change));
+ updateSelectedRingerContainerDescription(false);
mIsRingerDrawerOpen = false;
}
+
+ /**
+ * @param open false to set the description when drawer is closed
+ */
+ private void updateSelectedRingerContainerDescription(boolean open) {
+ if (mState == null || mSelectedRingerContainer == null) return;
+
+ String currentMode = mContext.getString(getStringDescriptionResourceForRingerMode(
+ mState.ringerModeInternal));
+ String tapToSelect;
+
+ if (open) {
+ // When the ringer drawer is open, tapping the currently selected ringer will set the
+ // ringer to the current ringer mode. Change the content description to that, instead of
+ // the 'tap to change ringer mode' default.
+ tapToSelect = "";
+
+ } else {
+ // When the drawer is closed, tapping the selected ringer drawer will open it, allowing
+ // the user to change the ringer. The user needs to know that, and also the current mode
+ currentMode += ", ";
+ tapToSelect = mContext.getString(R.string.volume_ringer_change);
+ }
+
+ mSelectedRingerContainer.setContentDescription(currentMode + tapToSelect);
+ }
+
private void initSettingsH(int lockTaskModeState) {
if (mSettingsView != null) {
mSettingsView.setVisibility(
@@ -1726,7 +1759,7 @@
});
}
- private int getStringDescriptionResourceForRingerMode(int mode) {
+ @VisibleForTesting int getStringDescriptionResourceForRingerMode(int mode) {
switch (mode) {
case RINGER_MODE_SILENT:
return R.string.volume_ringer_status_silent;
@@ -1823,6 +1856,7 @@
updateVolumeRowH(row);
}
updateRingerH();
+ updateSelectedRingerContainerDescription(mIsRingerDrawerOpen);
mWindow.setTitle(composeWindowTitle());
}
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 58982d1..7ab8e8b 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 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.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 @@
}
@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 @@
}
@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 @@
// 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/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
index 57d3a01..cbbbe52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
@@ -28,7 +28,9 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.complication.dagger.DreamClockTimeComplicationComponent;
import com.android.systemui.condition.SelfExecutingMonitor;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.shared.condition.Monitor;
@@ -36,11 +38,10 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import javax.inject.Provider;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DreamClockTimeComplicationTest extends SysuiTestCase {
@@ -55,8 +56,10 @@
private DreamClockTimeComplication mComplication;
@Mock
- private Provider<DreamClockTimeComplication.DreamClockTimeViewHolder>
- mDreamClockTimeViewHolderProvider;
+ private DreamClockTimeComplicationComponent.Factory mComponentFactory;
+
+ @Mock
+ private DreamClockTimeComplicationComponent mComponent;
@Mock
private DreamClockTimeComplication.DreamClockTimeViewHolder
@@ -71,12 +74,19 @@
@Mock
private ComplicationLayoutParams mLayoutParams;
+ @Mock
+ private DreamClockTimeComplication.DreamClockTimeViewController mViewController;
+
+ @Mock
+ private UiEventLogger mUiEventLogger;
+
private Monitor mMonitor;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mDreamClockTimeViewHolderProvider.get()).thenReturn(mDreamClockTimeViewHolder);
+ when(mComponentFactory.create()).thenReturn(mComponent);
+ when(mComponent.getViewHolder()).thenReturn(mDreamClockTimeViewHolder);
mMonitor = SelfExecutingMonitor.createInstance();
}
@@ -100,21 +110,21 @@
@Test
public void testComplicationRequiredTypeAvailability() {
final DreamClockTimeComplication complication =
- new DreamClockTimeComplication(mDreamClockTimeViewHolderProvider);
+ new DreamClockTimeComplication(mComponentFactory);
assertEquals(Complication.COMPLICATION_TYPE_TIME,
complication.getRequiredTypeAvailability());
}
/**
* Verifies {@link DreamClockTimeComplication.DreamClockTimeViewHolder} is obtainable from its
- * provider when the complication creates view.
+ * component when the complication creates view.
*/
@Test
- public void testComplicationViewHolderProviderOnCreateView() {
+ public void testComplicationViewHolderComponentOnCreateView() {
final DreamClockTimeComplication complication =
- new DreamClockTimeComplication(mDreamClockTimeViewHolderProvider);
+ new DreamClockTimeComplication(mComponentFactory);
final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
- verify(mDreamClockTimeViewHolderProvider).get();
+ verify(mComponent).getViewHolder();
assertThat(viewHolder).isEqualTo(mDreamClockTimeViewHolder);
}
@@ -125,8 +135,23 @@
@Test
public void testComplicationViewHolderContentAccessors() {
final DreamClockTimeComplication.DreamClockTimeViewHolder viewHolder =
- new DreamClockTimeComplication.DreamClockTimeViewHolder(mView, mLayoutParams);
+ new DreamClockTimeComplication.DreamClockTimeViewHolder(mView, mLayoutParams,
+ mViewController);
assertThat(viewHolder.getView()).isEqualTo(mView);
assertThat(viewHolder.getLayoutParams()).isEqualTo(mLayoutParams);
}
+
+ @Test
+ public void testClick_logUiEvent() {
+ final DreamClockTimeComplication.DreamClockTimeViewController controller =
+ new DreamClockTimeComplication.DreamClockTimeViewController(mView, mUiEventLogger);
+ controller.onViewAttached();
+
+ final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+ verify(mView).setOnClickListener(clickListenerCaptor.capture());
+
+ clickListenerCaptor.getValue().onClick(mView);
+ verify(mUiEventLogger).log(DreamOverlayUiEvent.DREAM_CLOCK_TAPPED);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
index 63b0b25..2207180 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
@@ -234,9 +234,7 @@
verify(mHomeControlsView).setOnClickListener(clickListenerCaptor.capture());
clickListenerCaptor.getValue().onClick(mHomeControlsView);
- verify(mUiEventLogger).log(
- DreamHomeControlsComplication.DreamHomeControlsChipViewController
- .DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+ verify(mUiEventLogger).log(DreamOverlayUiEvent.DREAM_HOME_CONTROLS_TAPPED);
}
private void setHaveFavorites(boolean value) {
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 71d2ec1..bd3d09d 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.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 @@
}
private val uiExecutor = FakeExecutor(FakeSystemClock())
- private val featureFlags = FakeFeatureFlags()
@Mock lateinit var controller: ControlsControllerImpl
@@ -65,7 +62,6 @@
ActivityTestRule(
/* activityFactory= */ SingleActivityFactory {
TestableControlsEditingActivity(
- featureFlags,
uiExecutor,
controller,
userTracker,
@@ -81,8 +77,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
- featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
}
@Test
@@ -101,16 +95,7 @@
}
@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 @@
@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 @@
@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 @@
@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 TestableControlsEditingActivity(
- featureFlags: FakeFeatureFlags,
executor: FakeExecutor,
controller: ControlsControllerImpl,
userTracker: UserTracker,
@@ -186,7 +167,6 @@
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 f11c296..70d93a1 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.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 @@
@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 @@
@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 @@
ActivityTestRule(
/* activityFactory= */ SingleActivityFactory {
TestableControlsFavoritingActivity(
- featureFlags,
executor,
controller,
- listingController,
userTracker,
mockDispatcher,
latch
@@ -109,7 +99,6 @@
@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 @@
}
@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 @@
}
@Test
- fun testNewFlowDisabled_buttons() {
+ fun testRearrangePressed_savesAndlaunchesActivity() {
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)
- 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 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/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index 272f589..7ac1953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -22,8 +22,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.FakeSharedPreferences
@@ -42,8 +40,6 @@
@Mock private lateinit var userTracker: UserTracker
- private val featureFlags = FakeFeatureFlags()
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -52,7 +48,6 @@
arrayOf<String>()
)
whenever(userTracker.userId).thenReturn(0)
- featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
}
@Test
@@ -132,25 +127,8 @@
assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
}
- @Test
- fun testSetAuthorizedPackageAfterFeatureDisabled() {
- mContext.orCreateTestableResources.addOverride(
- R.array.config_controlsPreferredPackages,
- arrayOf(TEST_PACKAGE)
- )
- val sharedPrefs = FakeSharedPreferences()
- val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
- val repository = createRepository(fileManager)
-
- repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
-
- featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
-
- assertThat(repository.getAuthorizedPanels()).isEqualTo(setOf(TEST_PACKAGE))
- }
-
private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
- return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker, featureFlags)
+ return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
}
private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
index 0c7b9cb..6230ea7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -22,7 +22,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
@@ -103,36 +102,18 @@
}
@Test
- fun testFeatureEnabled_shouldAddDefaultPanelDefaultsToTrue() {
- featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
-
+ fun testShouldAddDefaultPanelDefaultsToTrue() {
assertThat(repository.shouldAddDefaultComponent()).isTrue()
}
@Test
- fun testFeatureDisabled_shouldAddDefaultPanelDefaultsToTrue() {
- featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
-
- assertThat(repository.shouldAddDefaultComponent()).isTrue()
- }
-
- @Test
- fun testFeatureEnabled_shouldAddDefaultPanelChecked() {
- featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
+ fun testShouldAddDefaultPanelChecked() {
repository.setShouldAddDefaultComponent(false)
assertThat(repository.shouldAddDefaultComponent()).isFalse()
}
@Test
- fun testFeatureDisabled_shouldAlwaysAddDefaultPanelAlwaysTrue() {
- featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
- repository.setShouldAddDefaultComponent(false)
-
- assertThat(repository.shouldAddDefaultComponent()).isTrue()
- }
-
- @Test
fun testGetPreferredStructure_differentUserId() {
sharedPreferences.savePanel(COMPONENT_A)
whenever(
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 0ffa2d7..d8d3f92 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 @@
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 @@
mViewMediator.onSystemReady();
TestableLooper.get(this).processAllMessages();
+ when(mPowerManager.isInteractive()).thenReturn(true);
+
// Given device is dreaming
when(mUpdateMonitor.isDreaming()).thenReturn(true);
@@ -707,14 +709,14 @@
@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 @@
@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 @@
}
@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/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index d01a46e..aa6bd4e 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 @@
FromAlternateBouncerTransitionInteractor
private lateinit var fromPrimaryBouncerTransitionInteractor:
FromPrimaryBouncerTransitionInteractor
+ private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
+ FromDreamingLockscreenHostedTransitionInteractor
@Before
fun setUp() {
@@ -140,6 +142,15 @@
)
.apply { start() }
+ fromDreamingLockscreenHostedTransitionInteractor =
+ FromDreamingLockscreenHostedTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = createKeyguardInteractor(),
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ )
+ .apply { start() }
+
fromAodTransitionInteractor =
FromAodTransitionInteractor(
scope = testScope,
@@ -299,6 +310,38 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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
@@ -831,7 +1077,7 @@
withArgCaptor<TransitionInfo> {
verify(transitionRepository).startTransition(capture(), anyBoolean())
}
- // THEN a transition to AlternateBouncer should occur
+ // THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
@@ -840,6 +1086,61 @@
coroutineContext.cancelChildren()
}
+ @Test
+ fun dozingToOccluded() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DOZING
+ runTransition(KeyguardState.LOCKSCREEN, KeyguardState.DOZING)
+ runCurrent()
+
+ // WHEN the keyguard is occluded and device wakes up
+ keyguardRepository.setKeyguardOccluded(true)
+ keyguardRepository.setWakefulnessModel(startingToWake())
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to OCCLUDED should occur
+ assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DOZING)
+ assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun aodToOccluded() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to AOD
+ runTransition(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ runCurrent()
+
+ // WHEN the keyguard is occluded and aod ends
+ keyguardRepository.setKeyguardOccluded(true)
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(
+ from = DozeStateModel.DOZE_AOD,
+ to = DozeStateModel.FINISH,
+ )
+ )
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to OCCLUDED should occur
+ assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.AOD)
+ assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
private fun startingToWake() =
WakefulnessModel(
WakefulnessState.STARTING_TO_WAKE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index d8c78eb..904662e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -21,6 +21,7 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -31,8 +32,6 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -75,9 +74,7 @@
@Test
fun bouncerAlpha() =
runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
-
- val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+ val values by collectValues(underTest.bouncerAlpha)
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0.3f))
@@ -85,16 +82,12 @@
assertThat(values.size).isEqualTo(3)
values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
-
- job.cancel()
}
@Test
fun bouncerAlpha_runDimissFromKeyguard() =
runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
-
- val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+ val values by collectValues(underTest.bouncerAlpha)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
@@ -104,16 +97,52 @@
assertThat(values.size).isEqualTo(3)
values.forEach { assertThat(it).isEqualTo(0f) }
+ }
- job.cancel()
+ @Test
+ fun lockscreenAlpha() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values by collectValues(underTest.lockscreenAlpha)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(2)
+ values.forEach { assertThat(it).isEqualTo(0f) }
+ }
+
+ @Test
+ fun lockscreenAlpha_runDimissFromKeyguard() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values by collectValues(underTest.lockscreenAlpha)
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(2)
+ values.forEach { assertThat(it).isEqualTo(1f) }
+ }
+
+ @Test
+ fun lockscreenAlpha_leaveShadeOpen() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values by collectValues(underTest.lockscreenAlpha)
+
+ whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(2)
+ values.forEach { assertThat(it).isEqualTo(1f) }
}
@Test
fun scrimAlpha_runDimissFromKeyguard() =
runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<ScrimAlpha>()
-
- val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
+ val values by collectValues(underTest.scrimAlpha)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
@@ -124,16 +153,12 @@
assertThat(values.size).isEqualTo(4)
values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) }
-
- job.cancel()
}
@Test
fun scrimBehindAlpha_leaveShadeOpen() =
runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<ScrimAlpha>()
-
- val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
+ val values by collectValues(underTest.scrimAlpha)
whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
@@ -146,16 +171,12 @@
values.forEach {
assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
}
-
- job.cancel()
}
@Test
fun scrimBehindAlpha_doNotLeaveShadeOpen() =
runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<ScrimAlpha>()
-
- val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
+ val values by collectValues(underTest.scrimAlpha)
whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
@@ -169,8 +190,6 @@
values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
assertThat(values[3].behindAlpha).isEqualTo(0f)
-
- job.cancel()
}
private fun step(
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 c65a2d3..d933b57 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 @@
// 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 @@
@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/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 826a6cc..56e3e96 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
@@ -22,11 +22,13 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.SceneTransitionModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -91,11 +93,30 @@
val sceneTransitionProgress by collectLastValue(underTest.transitionProgress)
assertThat(sceneTransitionProgress).isEqualTo(1f)
- underTest.setSceneTransitionProgress(0.1f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ )
+ underTest.setTransitionState(transitionState)
+ assertThat(sceneTransitionProgress).isEqualTo(1f)
+
+ val progress = MutableStateFlow(1f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ )
+ assertThat(sceneTransitionProgress).isEqualTo(1f)
+
+ progress.value = 0.1f
assertThat(sceneTransitionProgress).isEqualTo(0.1f)
- underTest.setSceneTransitionProgress(0.9f)
+ progress.value = 0.9f
assertThat(sceneTransitionProgress).isEqualTo(0.9f)
+
+ underTest.setTransitionState(null)
+ assertThat(sceneTransitionProgress).isEqualTo(1f)
}
@Test
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 13a602d..c193d83 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
@@ -22,11 +22,13 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.SceneTransitionModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,7 +39,8 @@
class SceneInteractorTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
- private val underTest = utils.sceneInteractor()
+ private val repository = utils.fakeSceneContainerRepository()
+ private val underTest = utils.sceneInteractor(repository = repository)
@Test
fun allSceneKeys() {
@@ -55,11 +58,20 @@
@Test
fun sceneTransitionProgress() = runTest {
- val progress by collectLastValue(underTest.transitionProgress)
- assertThat(progress).isEqualTo(1f)
+ val transitionProgress by collectLastValue(underTest.transitionProgress)
+ assertThat(transitionProgress).isEqualTo(1f)
- underTest.setSceneTransitionProgress(0.55f)
- assertThat(progress).isEqualTo(0.55f)
+ val progress = MutableStateFlow(0.55f)
+ repository.setTransitionState(
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ ),
+ )
+ )
+ assertThat(transitionProgress).isEqualTo(0.55f)
}
@Test
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 c759e07..40d9cc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -99,12 +99,14 @@
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;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
@@ -297,7 +299,10 @@
@Mock protected LockscreenToOccludedTransitionViewModel
mLockscreenToOccludedTransitionViewModel;
@Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
-
+ @Mock protected GoneToDreamingLockscreenHostedTransitionViewModel
+ mGoneToDreamingLockscreenHostedTransitionViewModel;
+ @Mock protected PrimaryBouncerToGoneTransitionViewModel
+ mPrimaryBouncerToGoneTransitionViewModel;
@Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel;
@Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -478,6 +483,20 @@
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());
@@ -486,6 +505,10 @@
when(mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY(anyInt()))
.thenReturn(emptyFlow());
+ // Primary Bouncer->Gone
+ when(mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha())
+ .thenReturn(emptyFlow());
+
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
mDumpManager,
@@ -613,7 +636,9 @@
mOccludedToLockscreenTransitionViewModel,
mLockscreenToDreamingTransitionViewModel,
mGoneToDreamingTransitionViewModel,
+ mGoneToDreamingLockscreenHostedTransitionViewModel,
mLockscreenToOccludedTransitionViewModel,
+ mPrimaryBouncerToGoneTransitionViewModel,
mMainDispatcher,
mKeyguardTransitionInteractor,
mDumpManager,
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 91aa138..481f7f7 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 @@
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 @@
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 @@
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/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index c06dbdc..7f3d4b7 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 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.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 @@
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 @@
mController = new ZenModeControllerImpl(
mContext,
- Handler.createAsync(Looper.myLooper()),
+ Handler.createAsync(TestableLooper.get(this).getLooper()),
mBroadcastDispatcher,
mDumpManager,
- new FakeSettings(),
+ mGlobalSettings,
mUserTracker);
}
@@ -131,4 +136,48 @@
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/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index c819108..ee11cb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -16,14 +16,20 @@
package com.android.systemui.volume;
+import static android.media.AudioManager.RINGER_MODE_NORMAL;
+import static android.media.AudioManager.RINGER_MODE_SILENT;
+import static android.media.AudioManager.RINGER_MODE_VIBRATE;
+
import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assume.assumeNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -360,7 +366,7 @@
public void testSelectVibrateFromDrawer_OnewayAPI_On() {
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
- initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+ initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
mDialog.onStateChangedH(initialUnsetState);
mActiveRinger.performClick();
@@ -390,7 +396,7 @@
public void testSelectMuteFromDrawer_OnewayAPI_On() {
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
- initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+ initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
mDialog.onStateChangedH(initialUnsetState);
mActiveRinger.performClick();
@@ -428,7 +434,7 @@
// Make sure we've actually changed the ringer mode.
verify(mVolumeDialogController, times(1)).setRingerMode(
- AudioManager.RINGER_MODE_NORMAL, false);
+ RINGER_MODE_NORMAL, false);
}
/**
@@ -625,6 +631,88 @@
}
}
+ private enum RingerDrawerState {INIT, OPEN, CLOSE}
+
+ @Test
+ public void ringerModeNormal_ringerContainerDescribesItsState() {
+ assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.INIT);
+ }
+
+ @Test
+ public void ringerModeSilent_ringerContainerDescribesItsState() {
+ assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.INIT);
+ }
+
+ @Test
+ public void ringerModeVibrate_ringerContainerDescribesItsState() {
+ assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.INIT);
+ }
+
+ @Test
+ public void ringerModeNormal_openDrawer_ringerContainerDescribesItsState() {
+ assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.OPEN);
+ }
+
+ @Test
+ public void ringerModeSilent_openDrawer_ringerContainerDescribesItsState() {
+ assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.OPEN);
+ }
+
+ @Test
+ public void ringerModeVibrate_openDrawer_ringerContainerDescribesItsState() {
+ assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.OPEN);
+ }
+
+ @Test
+ public void ringerModeNormal_closeDrawer_ringerContainerDescribesItsState() {
+ assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.CLOSE);
+ }
+
+ @Test
+ public void ringerModeSilent_closeDrawer_ringerContainerDescribesItsState() {
+ assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.CLOSE);
+ }
+
+ @Test
+ public void ringerModeVibrate_closeDrawer_ringerContainerDescribesItsState() {
+ assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.CLOSE);
+ }
+
+ /**
+ * The content description should include ringer state, and the correct one.
+ */
+ private void assertRingerContainerDescribesItsState(int ringerMode,
+ RingerDrawerState drawerState) {
+ State state = createShellState();
+ state.ringerModeInternal = ringerMode;
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+ mDialog.onStateChangedH(state);
+
+ mDialog.show(SHOW_REASON_UNKNOWN);
+
+ if (drawerState != RingerDrawerState.INIT) {
+ // in both cases we first open the drawer
+ mDialog.toggleRingerDrawer(true);
+
+ if (drawerState == RingerDrawerState.CLOSE) {
+ mDialog.toggleRingerDrawer(false);
+ }
+ }
+
+ String ringerContainerDescription = mDialog.getSelectedRingerContainerDescription();
+ assumeNotNull(ringerContainerDescription);
+
+ String ringerDescription = mContext.getString(
+ mDialog.getStringDescriptionResourceForRingerMode(ringerMode));
+
+ if (drawerState == RingerDrawerState.OPEN) {
+ assertEquals(ringerDescription, ringerContainerDescription);
+ } else {
+ assertNotSame(ringerDescription, ringerContainerDescription);
+ assertTrue(ringerContainerDescription.startsWith(ringerDescription));
+ }
+ }
+
@After
public void teardown() {
cleanUp(mDialog);
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 26a75d0..70d15a0 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
@@ -119,9 +119,11 @@
)
}
- fun sceneInteractor(): SceneInteractor {
+ fun sceneInteractor(
+ repository: SceneContainerRepository = fakeSceneContainerRepository()
+ ): SceneInteractor {
return SceneInteractor(
- repository = fakeSceneContainerRepository(),
+ repository = repository,
)
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 3f4f981..23a0782 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 @@
}
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/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 61d0afe..fb3f0b3 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -260,7 +260,9 @@
}
synchronized (mLock) {
- mProjectionGrant.stop();
+ if (mProjectionGrant != null) {
+ mProjectionGrant.stop();
+ }
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 0605345..3ba307b 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.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 @@
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 @@
// 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 @@
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 710e0b7..dd434fbe 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.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 @@
}
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 e93f358..62273b5 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.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 @@
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 @@
@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 0c196d7..976641b 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 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.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 @@
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 @@
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 @@
* 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 @@
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 @@
mAnimationHandler.clearBackAnimateTarget();
mNavigationMonitor.stopMonitorTransition();
mWaitTransitionFinish = null;
+ mBackAnimationRunning = false;
}
/**
@@ -717,11 +735,7 @@
// 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 @@
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 @@
}
}
- 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 @@
.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 @@
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 @@
}
}
- 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 @@
}
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 @@
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 @@
public void onAnimationCancelled(SurfaceControl animationLeash) {
if (mCapturedLeash == animationLeash) {
mCapturedLeash = null;
+ mRequestedStartingSurfaceId = INVALID_TASK_ID;
+ mStartingSurface = null;
}
}
@@ -1009,8 +991,15 @@
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 @@
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 @@
/**
* 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 @@
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 @@
if (mPendingAnimation != null) {
mPendingAnimation.run();
mPendingAnimation = null;
+ mBackAnimationRunning = true;
}
}
@@ -1236,9 +1295,6 @@
}
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 @@
}
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 @@
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 cff86ad..2b22d75 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 @@
*/
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 cdb4ad6..b72d027 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 @@
}
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 b7c8092..f33af5e 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 @@
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 140255b..b2a2452 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 @@
}
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 @@
// 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 @@
* LetterboxUiController#shouldShowLetterboxUi} for more context.
*/
boolean areAppWindowBoundsLetterboxed() {
- return mActivityRecord != null
+ return mActivityRecord != null && !isStartingWindowAssociatedToTask()
&& (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout());
}
@@ -5668,6 +5677,12 @@
// 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/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index f660b42..fe979b6 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -418,14 +418,11 @@
setDeviceServer(server);
}
- @RequiresPermission(anyOf = {Manifest.permission.QUERY_USERS,
- Manifest.permission.CREATE_USERS,
- Manifest.permission.MANAGE_USERS})
public Device(BluetoothDevice bluetoothDevice) {
mBluetoothDevice = bluetoothDevice;
mServiceInfo = null;
mUid = mBluetoothServiceUid;
- mUserId = mUserManager.getMainUser().getIdentifier();
+ mUserId = UserHandle.getUserId(mUid);
}
private void setDeviceServer(IMidiDeviceServer server) {
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 35b9bc3..4a8d73d2 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 @@
}
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 107dde2..fa0a971 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/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 52c6777..24029b1 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.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 @@
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 @@
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 6d7f2c1..1c86758 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 @@
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 @@
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 @@
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/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index feef049..d41c019 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 @@
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 @@
}
}
+ @Override
public void onHotspotNetworkConnectionStatusChanged(
@NonNull HotspotNetworkConnectionStatus status) {
if (mCallback != null) {
@@ -251,7 +265,6 @@
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 737aa6d..521f943 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 @@
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 2bbe919..ebda6f1 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 @@
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();
}