Merge "Move CommunalSwipeDetector to SystemUI/src/.../communal/" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 9298ca2..73b35b2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -61775,7 +61775,7 @@
public final class BackEvent {
ctor public BackEvent(float, float, float, int);
ctor @FlaggedApi("com.android.window.flags.predictive_back_timestamp_api") public BackEvent(float, float, float, int, long);
- method @FlaggedApi("com.android.window.flags.predictive_back_timestamp_api") public long getFrameTime();
+ method @FlaggedApi("com.android.window.flags.predictive_back_timestamp_api") public long getFrameTimeMillis();
method @FloatRange(from=0, to=1) public float getProgress();
method public int getSwipeEdge();
method public float getTouchX();
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 90fac361..1b9235b 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -58,7 +58,7 @@
private final float mTouchX;
private final float mTouchY;
private final float mProgress;
- private final long mFrameTime;
+ private final long mFrameTimeMillis;
@SwipeEdge
private final int mSwipeEdge;
@@ -68,7 +68,7 @@
if (predictiveBackTimestampApi()) {
return new BackEvent(backMotionEvent.getTouchX(), backMotionEvent.getTouchY(),
backMotionEvent.getProgress(), backMotionEvent.getSwipeEdge(),
- backMotionEvent.getFrameTime());
+ backMotionEvent.getFrameTimeMillis());
} else {
return new BackEvent(backMotionEvent.getTouchX(), backMotionEvent.getTouchY(),
backMotionEvent.getProgress(), backMotionEvent.getSwipeEdge());
@@ -88,7 +88,7 @@
mTouchY = touchY;
mProgress = progress;
mSwipeEdge = swipeEdge;
- mFrameTime = System.nanoTime() / TimeUtils.NANOS_PER_MS;
+ mFrameTimeMillis = System.nanoTime() / TimeUtils.NANOS_PER_MS;
}
/**
@@ -98,16 +98,16 @@
* @param touchY Absolute Y location of the touch point of this event.
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param swipeEdge Indicates which edge the swipe starts from.
- * @param frameTime frame time of the back event.
+ * @param frameTimeMillis frame time of the back event.
*/
@FlaggedApi(FLAG_PREDICTIVE_BACK_TIMESTAMP_API)
public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge,
- long frameTime) {
+ long frameTimeMillis) {
mTouchX = touchX;
mTouchY = touchY;
mProgress = progress;
mSwipeEdge = swipeEdge;
- mFrameTime = frameTime;
+ mFrameTimeMillis = frameTimeMillis;
}
/**
@@ -160,8 +160,8 @@
* Returns the frameTime of the BackEvent in milliseconds. Useful for calculating velocity.
*/
@FlaggedApi(FLAG_PREDICTIVE_BACK_TIMESTAMP_API)
- public long getFrameTime() {
- return mFrameTime;
+ public long getFrameTimeMillis() {
+ return mFrameTimeMillis;
}
@Override
@@ -177,7 +177,7 @@
&& mTouchY == that.mTouchY
&& mProgress == that.mProgress
&& mSwipeEdge == that.mSwipeEdge
- && mFrameTime == that.mFrameTime;
+ && mFrameTimeMillis == that.mFrameTimeMillis;
}
@Override
@@ -187,7 +187,7 @@
+ ", mTouchY=" + mTouchY
+ ", mProgress=" + mProgress
+ ", mSwipeEdge=" + mSwipeEdge
- + ", mFrameTime=" + mFrameTime + "ms"
+ + ", mFrameTimeMillis=" + mFrameTimeMillis
+ "}";
}
}
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
index a8ec4ee..cc2afbc 100644
--- a/core/java/android/window/BackMotionEvent.java
+++ b/core/java/android/window/BackMotionEvent.java
@@ -33,7 +33,7 @@
public final class BackMotionEvent implements Parcelable {
private final float mTouchX;
private final float mTouchY;
- private final long mFrameTime;
+ private final long mFrameTimeMillis;
private final float mProgress;
private final boolean mTriggerBack;
@@ -49,7 +49,7 @@
*
* @param touchX Absolute X location of the touch point of this event.
* @param touchY Absolute Y location of the touch point of this event.
- * @param frameTime Event time of the corresponding touch event.
+ * @param frameTimeMillis Event time of the corresponding touch event.
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param triggerBack Indicates whether the back arrow is in the triggered state or not
* @param swipeEdge Indicates which edge the swipe starts from.
@@ -59,14 +59,14 @@
public BackMotionEvent(
float touchX,
float touchY,
- long frameTime,
+ long frameTimeMillis,
float progress,
boolean triggerBack,
@BackEvent.SwipeEdge int swipeEdge,
@Nullable RemoteAnimationTarget departingAnimationTarget) {
mTouchX = touchX;
mTouchY = touchY;
- mFrameTime = frameTime;
+ mFrameTimeMillis = frameTimeMillis;
mProgress = progress;
mTriggerBack = triggerBack;
mSwipeEdge = swipeEdge;
@@ -80,7 +80,7 @@
mTriggerBack = in.readBoolean();
mSwipeEdge = in.readInt();
mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
- mFrameTime = in.readLong();
+ mFrameTimeMillis = in.readLong();
}
@NonNull
@@ -109,7 +109,7 @@
dest.writeBoolean(mTriggerBack);
dest.writeInt(mSwipeEdge);
dest.writeTypedObject(mDepartingAnimationTarget, flags);
- dest.writeLong(mFrameTime);
+ dest.writeLong(mFrameTimeMillis);
}
/**
@@ -156,8 +156,8 @@
/**
* Returns the frame time of the BackMotionEvent in milliseconds.
*/
- public long getFrameTime() {
- return mFrameTime;
+ public long getFrameTimeMillis() {
+ return mFrameTimeMillis;
}
/**
@@ -175,7 +175,7 @@
return "BackMotionEvent{"
+ "mTouchX=" + mTouchX
+ ", mTouchY=" + mTouchY
- + ", mFrameTime=" + mFrameTime + "ms"
+ + ", mFrameTimeMillis=" + mFrameTimeMillis
+ ", mProgress=" + mProgress
+ ", mTriggerBack=" + mTriggerBack
+ ", mSwipeEdge=" + mSwipeEdge
diff --git a/core/java/android/window/BackTouchTracker.java b/core/java/android/window/BackTouchTracker.java
index 39a3025..4908068 100644
--- a/core/java/android/window/BackTouchTracker.java
+++ b/core/java/android/window/BackTouchTracker.java
@@ -151,7 +151,7 @@
return new BackMotionEvent(
/* touchX = */ mInitTouchX,
/* touchY = */ mInitTouchY,
- /* frameTime = */ 0,
+ /* frameTimeMillis = */ 0,
/* progress = */ 0,
/* triggerBack = */ mTriggerBack,
/* swipeEdge = */ mSwipeEdge,
@@ -236,7 +236,7 @@
return new BackMotionEvent(
/* touchX = */ mLatestTouchX,
/* touchY = */ mLatestTouchY,
- /* frameTime = */ 0,
+ /* frameTimeMillis = */ 0,
/* progress = */ progress,
/* triggerBack = */ mTriggerBack,
/* swipeEdge = */ mSwipeEdge,
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 8db1f95..bd01899 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -238,7 +238,7 @@
try {
long frameTime = 0;
if (predictiveBackTimestampApi()) {
- frameTime = backEvent.getFrameTime();
+ frameTime = backEvent.getFrameTimeMillis();
}
mIOnBackInvokedCallback.onBackStarted(
new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime,
@@ -254,7 +254,7 @@
try {
long frameTime = 0;
if (predictiveBackTimestampApi()) {
- frameTime = backEvent.getFrameTime();
+ frameTime = backEvent.getFrameTimeMillis();
}
mIOnBackInvokedCallback.onBackProgressed(
new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime,
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 46bd73e..4d6c30e 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -686,7 +686,7 @@
return new BackMotionEvent(
/* touchX = */ 0,
/* touchY = */ 0,
- /* frameTime = */ 0,
+ /* frameTimeMillis = */ 0,
/* progress = */ progress,
/* triggerBack = */ false,
/* swipeEdge = */ BackEvent.EDGE_LEFT,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 52391d2..dc50fdb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -333,7 +333,7 @@
private void onGestureProgress(@NonNull BackEvent backEvent) {
if (!mBackInProgress) {
mBackInProgress = true;
- mDownTime = backEvent.getFrameTime();
+ mDownTime = backEvent.getFrameTimeMillis();
}
float progress = backEvent.getProgress();
mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
@@ -342,7 +342,7 @@
mVelocityTracker.addMovement(
MotionEvent.obtain(
/* downTime */ mDownTime,
- /* eventTime */ backEvent.getFrameTime(),
+ /* eventTime */ backEvent.getFrameTimeMillis(),
/* action */ ACTION_MOVE,
/* x */ interpolatedProgress * SPRING_SCALE,
/* y */ 0f,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java
index 915a8a1..37369d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java
@@ -24,6 +24,7 @@
/** Handle the visibility state of the Compat UI components. */
public class CompatUIStatusManager {
+ private static final int COMPAT_UI_EDUCATION_UNDEFINED = -1;
public static final int COMPAT_UI_EDUCATION_HIDDEN = 0;
public static final int COMPAT_UI_EDUCATION_VISIBLE = 1;
@@ -32,24 +33,40 @@
@NonNull
private final IntSupplier mReader;
+ private int mCurrentValue = COMPAT_UI_EDUCATION_UNDEFINED;
+
public CompatUIStatusManager(@NonNull IntConsumer writer, @NonNull IntSupplier reader) {
mWriter = writer;
mReader = reader;
}
public CompatUIStatusManager() {
- this(i -> { }, () -> COMPAT_UI_EDUCATION_HIDDEN);
+ this(i -> {
+ }, () -> COMPAT_UI_EDUCATION_HIDDEN);
}
void onEducationShown() {
- mWriter.accept(COMPAT_UI_EDUCATION_VISIBLE);
+ if (mCurrentValue != COMPAT_UI_EDUCATION_VISIBLE) {
+ mCurrentValue = COMPAT_UI_EDUCATION_VISIBLE;
+ mWriter.accept(mCurrentValue);
+ }
}
void onEducationHidden() {
- mWriter.accept(COMPAT_UI_EDUCATION_HIDDEN);
+ if (mCurrentValue != COMPAT_UI_EDUCATION_HIDDEN) {
+ mCurrentValue = COMPAT_UI_EDUCATION_HIDDEN;
+ mWriter.accept(mCurrentValue);
+ }
}
boolean isEducationVisible() {
- return mReader.getAsInt() == COMPAT_UI_EDUCATION_VISIBLE;
+ return getCurrentValue() == COMPAT_UI_EDUCATION_VISIBLE;
}
-}
+
+ private int getCurrentValue() {
+ if (mCurrentValue == COMPAT_UI_EDUCATION_UNDEFINED) {
+ mCurrentValue = mReader.getAsInt();
+ }
+ return mCurrentValue;
+ }
+}
\ No newline at end of file
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 346f21b..7c9cd08 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
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
@@ -58,6 +59,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.Trace;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
@@ -800,8 +802,17 @@
track.mReadyTransitions.add(active);
for (int i = 0; i < mObservers.size(); ++i) {
+ final boolean useTrace = Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER);
+ if (useTrace) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+ mObservers.get(i).getClass().getSimpleName() + "#onTransitionReady: "
+ + transitTypeToString(info.getType()));
+ }
mObservers.get(i).onTransitionReady(
active.mToken, info, active.mStartT, active.mFinishT);
+ if (useTrace) {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
}
/*
@@ -931,7 +942,7 @@
onFinish(ready.mToken, null);
return;
}
- playTransition(ready);
+ playTransitionWithTracing(ready);
// Attempt to merge any more queued-up transitions.
processReadyQueue(track);
return;
@@ -1003,6 +1014,18 @@
processReadyQueue(track);
}
+ private void playTransitionWithTracing(@NonNull ActiveTransition active) {
+ final boolean useTrace = Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER);
+ if (useTrace) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+ "playTransition: " + transitTypeToString(active.mInfo.getType()));
+ }
+ playTransition(active);
+ if (useTrace) {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
private void playTransition(@NonNull ActiveTransition active) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
final var token = active.mToken;
@@ -1022,6 +1045,12 @@
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.instant(TRACE_TAG_WINDOW_MANAGER,
+ active.mHandler.getClass().getSimpleName()
+ + "#startAnimation animated "
+ + transitTypeToString(active.mInfo.getType()));
+ }
return;
}
}
@@ -1052,6 +1081,12 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
mHandlers.get(i));
mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.instant(TRACE_TAG_WINDOW_MANAGER,
+ mHandlers.get(i).getClass().getSimpleName()
+ + "#startAnimation animated "
+ + transitTypeToString(info.getType()));
+ }
return mHandlers.get(i);
}
}
@@ -1059,6 +1094,26 @@
"This shouldn't happen, maybe the default handler is broken.");
}
+ private Pair<TransitionHandler, WindowContainerTransaction> dispatchRequestWithTracing(
+ @NonNull IBinder transition, @NonNull TransitionRequestInfo request,
+ @Nullable TransitionHandler skip) {
+ final boolean useTrace = Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER);
+ if (useTrace) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+ "dispatchRequest: " + transitTypeToString(request.getType()));
+ }
+ Pair<TransitionHandler, WindowContainerTransaction> result =
+ dispatchRequest(transition, request, skip);
+ if (useTrace) {
+ if (result != null) {
+ Trace.instant(TRACE_TAG_WINDOW_MANAGER, result.first.getClass().getSimpleName()
+ + "#handleRequest handled " + transitTypeToString(request.getType()));
+ }
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+ return result;
+ }
+
/**
* Gives every handler (in order) a chance to handle request until one consumes the transition.
* @return the WindowContainerTransaction given by the handler which consumed the transition.
@@ -1197,12 +1252,11 @@
mSleepHandler.handleRequest(transitionToken, request);
active.mHandler = mSleepHandler;
} else {
- for (int i = mHandlers.size() - 1; i >= 0; --i) {
- wct = mHandlers.get(i).handleRequest(transitionToken, request);
- if (wct != null) {
- active.mHandler = mHandlers.get(i);
- break;
- }
+ Pair<TransitionHandler, WindowContainerTransaction> requestResult =
+ dispatchRequestWithTracing(transitionToken, request, /* skip= */ null);
+ if (requestResult != null) {
+ active.mHandler = requestResult.first;
+ wct = requestResult.second;
}
if (request.getDisplayChange() != null) {
TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 60c9222..78e7962 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -29,8 +29,6 @@
import android.view.SurfaceControl;
import android.window.DesktopModeFlags;
-import androidx.annotation.NonNull;
-
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -129,13 +127,15 @@
// If width or height are negative or exceeding the width or height constraints, revert the
// respective bounds to use previous bound dimensions.
- if (isExceedingWidthConstraint(repositionTaskBounds, stableBounds, displayController,
+ if (isExceedingWidthConstraint(repositionTaskBounds.width(),
+ /* startingWidth= */ oldRight - oldLeft, stableBounds, displayController,
windowDecoration)) {
repositionTaskBounds.right = oldRight;
repositionTaskBounds.left = oldLeft;
isAspectRatioMaintained = false;
}
- if (isExceedingHeightConstraint(repositionTaskBounds, stableBounds, displayController,
+ if (isExceedingHeightConstraint(repositionTaskBounds.height(),
+ /* startingHeight= */oldBottom - oldTop, stableBounds, displayController,
windowDecoration)) {
repositionTaskBounds.top = oldTop;
repositionTaskBounds.bottom = oldBottom;
@@ -208,28 +208,34 @@
return result;
}
- private static boolean isExceedingWidthConstraint(@NonNull Rect repositionTaskBounds,
+ private static boolean isExceedingWidthConstraint(int repositionedWidth, int startingWidth,
Rect maxResizeBounds, DisplayController displayController,
WindowDecoration windowDecoration) {
+ boolean isSizeIncreasing = (repositionedWidth - startingWidth) > 0;
// Check if width is less than the minimum width constraint.
- if (repositionTaskBounds.width() < getMinWidth(displayController, windowDecoration)) {
- return true;
+ if (repositionedWidth < getMinWidth(displayController, windowDecoration)) {
+ // Only allow width to be increased if it is already below minimum.
+ return !isSizeIncreasing;
}
// Check if width is more than the maximum resize bounds on desktop windowing mode.
+ // Only allow width to be decreased if it already exceeds maximum.
return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)
- && repositionTaskBounds.width() > maxResizeBounds.width();
+ && repositionedWidth > maxResizeBounds.width() && isSizeIncreasing;
}
- private static boolean isExceedingHeightConstraint(@NonNull Rect repositionTaskBounds,
+ private static boolean isExceedingHeightConstraint(int repositionedHeight, int startingHeight,
Rect maxResizeBounds, DisplayController displayController,
WindowDecoration windowDecoration) {
+ boolean isSizeIncreasing = (repositionedHeight - startingHeight) > 0;
// Check if height is less than the minimum height constraint.
- if (repositionTaskBounds.height() < getMinHeight(displayController, windowDecoration)) {
- return true;
+ if (repositionedHeight < getMinHeight(displayController, windowDecoration)) {
+ // Only allow height to be increased if it is already below minimum.
+ return !isSizeIncreasing;
}
// Check if height is more than the maximum resize bounds on desktop windowing mode.
+ // Only allow height to be decreased if it already exceeds maximum.
return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)
- && repositionTaskBounds.height() > maxResizeBounds.height();
+ && repositionedHeight > maxResizeBounds.height() && isSizeIncreasing;
}
private static float getMinWidth(DisplayController displayController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java
index d6059a8..8fd7c0e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java
@@ -16,6 +16,10 @@
package com.android.wm.shell.compatui;
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_HIDDEN;
+
+import static junit.framework.Assert.assertEquals;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -25,7 +29,6 @@
import com.android.wm.shell.ShellTestCase;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,13 +66,75 @@
assertFalse(mStatusManager.isEducationVisible());
}
+ @Test
+ public void valuesAreCached() {
+ // At the beginning the value is not read or written because
+ // we access the reader in lazy way.
+ mTestState.assertReaderInvocations(0);
+ mTestState.assertWriterInvocations(0);
+
+ // We read the value when we start. Initial value is hidden.
+ assertFalse(mStatusManager.isEducationVisible());
+ mTestState.assertReaderInvocations(1);
+ mTestState.assertWriterInvocations(0);
+
+ // We send the event for the same state which is not written.
+ mStatusManager.onEducationHidden();
+ assertFalse(mStatusManager.isEducationVisible());
+ mTestState.assertReaderInvocations(1);
+ mTestState.assertWriterInvocations(0);
+
+ // We send the event for the different state which is written but
+ // not read again.
+ mStatusManager.onEducationShown();
+ assertTrue(mStatusManager.isEducationVisible());
+ mTestState.assertReaderInvocations(1);
+ mTestState.assertWriterInvocations(1);
+
+ // We read multiple times and we don't read the value again
+ mStatusManager.isEducationVisible();
+ mStatusManager.isEducationVisible();
+ mStatusManager.isEducationVisible();
+ mTestState.assertReaderInvocations(1);
+ mTestState.assertWriterInvocations(1);
+
+ // We write different values. Writer is only accessed when
+ // the value changes.
+ mStatusManager.onEducationHidden(); // change
+ mStatusManager.onEducationHidden();
+ mStatusManager.onEducationShown(); // change
+ mStatusManager.onEducationShown();
+ mStatusManager.onEducationHidden(); // change
+ mStatusManager.onEducationShown(); // change
+ mTestState.assertReaderInvocations(1);
+ mTestState.assertWriterInvocations(5);
+ }
+
static class FakeCompatUIStatusManagerTest {
- int mCurrentStatus = 0;
+ int mCurrentStatus = COMPAT_UI_EDUCATION_HIDDEN;
- final IntSupplier mReader = () -> mCurrentStatus;
+ int mReaderInvocations;
- final IntConsumer mWriter = newStatus -> mCurrentStatus = newStatus;
+ int mWriterInvocations;
+
+ final IntSupplier mReader = () -> {
+ mReaderInvocations++;
+ return mCurrentStatus;
+ };
+
+ final IntConsumer mWriter = newStatus -> {
+ mWriterInvocations++;
+ mCurrentStatus = newStatus;
+ };
+
+ void assertWriterInvocations(int expected) {
+ assertEquals(expected, mWriterInvocations);
+ }
+
+ void assertReaderInvocations(int expected) {
+ assertEquals(expected, mReaderInvocations);
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index 24f6bec..a20a89c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -36,6 +36,7 @@
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.google.common.truth.Truth.assertThat
@@ -48,9 +49,9 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
/**
* Tests for [DragPositioningCallbackUtility].
@@ -193,6 +194,62 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING)
+ fun testChangeBounds_unresizeableApp_initialHeightLessThanMin_increasingBounds_resizeAllowed() {
+ mockWindowDecoration.mTaskInfo.isResizeable = false
+ val startingPoint = PointF(BELOW_MIN_HEIGHT_BOUNDS.right.toFloat(),
+ BELOW_MIN_HEIGHT_BOUNDS.bottom.toFloat())
+ val repositionTaskBounds = Rect(BELOW_MIN_HEIGHT_BOUNDS)
+
+ // Resize to increased bounds
+ val newX = BELOW_MIN_HEIGHT_BOUNDS.right.toFloat() + 20
+ val newY = BELOW_MIN_HEIGHT_BOUNDS.bottom.toFloat() + 10
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ // Resize should be allowed as drag is in direction of desired range
+ assertTrue(
+ DragPositioningCallbackUtility.changeBounds(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ repositionTaskBounds, BELOW_MIN_HEIGHT_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration
+ )
+ )
+
+ assertThat(repositionTaskBounds.left).isEqualTo(BELOW_MIN_HEIGHT_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(BELOW_MIN_HEIGHT_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(BELOW_MIN_HEIGHT_BOUNDS.right + 20)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(BELOW_MIN_HEIGHT_BOUNDS.bottom + 10)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING)
+ fun testChangeBounds_unresizeableApp_initialHeightMoreThanMax_decreasingBounds_resizeAllowed() {
+ mockWindowDecoration.mTaskInfo.isResizeable = false
+ val startingPoint = PointF(EXCEEDS_MAX_HEIGHT_BOUNDS.right.toFloat(),
+ EXCEEDS_MAX_HEIGHT_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect(EXCEEDS_MAX_HEIGHT_BOUNDS)
+
+ // Resize to decreased bounds.
+ val newX = EXCEEDS_MAX_HEIGHT_BOUNDS.right.toFloat() - 10
+ val newY = EXCEEDS_MAX_HEIGHT_BOUNDS.top.toFloat() + 20
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ // Resize should be allowed as drag is in direction of desired range.
+ assertTrue(
+ DragPositioningCallbackUtility.changeBounds(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ repositionTaskBounds, EXCEEDS_MAX_HEIGHT_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration
+ )
+ )
+
+ assertThat(repositionTaskBounds.left).isEqualTo(EXCEEDS_MAX_HEIGHT_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(EXCEEDS_MAX_HEIGHT_BOUNDS.top + 20)
+ assertThat(repositionTaskBounds.right).isEqualTo(EXCEEDS_MAX_HEIGHT_BOUNDS.right - 10)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(EXCEEDS_MAX_HEIGHT_BOUNDS.bottom)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING)
fun testChangeBounds_unresizeableApp_widthLessThanMin_resetToStartingBounds() {
mockWindowDecoration.mTaskInfo.isResizeable = false
val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
@@ -211,13 +268,68 @@
)
)
-
assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING)
+ fun testChangeBounds_unresizeableApp_initialWidthLessThanMin_increasingBounds_resizeAllowed() {
+ mockWindowDecoration.mTaskInfo.isResizeable = false
+ val startingPoint = PointF(BELOW_MIN_WIDTH_BOUNDS.right.toFloat(),
+ BELOW_MIN_WIDTH_BOUNDS.bottom.toFloat())
+ val repositionTaskBounds = Rect(BELOW_MIN_WIDTH_BOUNDS)
+
+ // Resize to increased bounds.
+ val newX = BELOW_MIN_WIDTH_BOUNDS.right.toFloat() + 10
+ val newY = BELOW_MIN_WIDTH_BOUNDS.bottom.toFloat() + 20
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ // Resize should be allowed as drag is in direction of desired range.
+ assertTrue(
+ DragPositioningCallbackUtility.changeBounds(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ repositionTaskBounds, BELOW_MIN_WIDTH_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration
+ )
+ )
+
+ assertThat(repositionTaskBounds.left).isEqualTo(BELOW_MIN_WIDTH_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(BELOW_MIN_WIDTH_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(BELOW_MIN_WIDTH_BOUNDS.right + 10)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(BELOW_MIN_WIDTH_BOUNDS.bottom + 20)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_SCALED_RESIZING)
+ fun testChangeBounds_unresizeableApp_initialWidthMoreThanMax_decreasingBounds_resizeAllowed() {
+ mockWindowDecoration.mTaskInfo.isResizeable = false
+ val startingPoint = PointF(EXCEEDS_MAX_WIDTH_BOUNDS.left.toFloat(),
+ EXCEEDS_MAX_WIDTH_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect(EXCEEDS_MAX_WIDTH_BOUNDS)
+
+ // Resize to decreased bounds.
+ val newX = EXCEEDS_MAX_WIDTH_BOUNDS.left.toFloat() + 20
+ val newY = EXCEEDS_MAX_WIDTH_BOUNDS.top.toFloat() + 10
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ // Resize should be allowed as drag is in direction of desired range.
+ assertTrue(
+ DragPositioningCallbackUtility.changeBounds(
+ CTRL_TYPE_LEFT or CTRL_TYPE_TOP,
+ repositionTaskBounds, EXCEEDS_MAX_WIDTH_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration
+ )
+ )
+
+ assertThat(repositionTaskBounds.left).isEqualTo(EXCEEDS_MAX_WIDTH_BOUNDS.left + 20)
+ assertThat(repositionTaskBounds.top).isEqualTo(EXCEEDS_MAX_WIDTH_BOUNDS.top + 10)
+ assertThat(repositionTaskBounds.right).isEqualTo(EXCEEDS_MAX_WIDTH_BOUNDS.right)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(EXCEEDS_MAX_WIDTH_BOUNDS.bottom)
+ }
+
@Test
fun testChangeBoundsDoesNotChangeHeightWhenNegative() {
@@ -427,6 +539,60 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
+ fun testMinHeight_initialHeightLessThanMin_increasingHeight_resizeAllowed() {
+ val startingPoint = PointF(BELOW_MIN_HEIGHT_BOUNDS.right.toFloat(),
+ BELOW_MIN_HEIGHT_BOUNDS.bottom.toFloat())
+ val repositionTaskBounds = Rect(BELOW_MIN_HEIGHT_BOUNDS)
+
+ // Attempt to increase height.
+ val newX = BELOW_MIN_HEIGHT_BOUNDS.right.toFloat()
+ val newY = BELOW_MIN_HEIGHT_BOUNDS.bottom.toFloat() + 10
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ // Resize should be allowed as drag is increasing height closer to valid region.
+ assertTrue(
+ DragPositioningCallbackUtility.changeBounds(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ repositionTaskBounds, BELOW_MIN_HEIGHT_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration
+ )
+ )
+
+ assertThat(repositionTaskBounds.left).isEqualTo(BELOW_MIN_HEIGHT_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(BELOW_MIN_HEIGHT_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(BELOW_MIN_HEIGHT_BOUNDS.right)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(BELOW_MIN_HEIGHT_BOUNDS.bottom + 10)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
+ fun testMinWidth_initialWidthLessThanMin_increasingBounds_resizeAllowed() {
+ val startingPoint = PointF(BELOW_MIN_WIDTH_BOUNDS.right.toFloat(),
+ BELOW_MIN_WIDTH_BOUNDS.bottom.toFloat())
+ val repositionTaskBounds = Rect(BELOW_MIN_WIDTH_BOUNDS)
+
+ // Attempt to increase width.
+ val newX = BELOW_MIN_WIDTH_BOUNDS.right.toFloat() + 10
+ val newY = BELOW_MIN_WIDTH_BOUNDS.bottom.toFloat()
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ // Resize should be allowed as drag is increasing width closer to valid region.
+ assertTrue(
+ DragPositioningCallbackUtility.changeBounds(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ repositionTaskBounds, BELOW_MIN_WIDTH_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration
+ )
+ )
+
+ assertThat(repositionTaskBounds.left).isEqualTo(BELOW_MIN_WIDTH_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(BELOW_MIN_WIDTH_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(BELOW_MIN_WIDTH_BOUNDS.right + 10)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(BELOW_MIN_WIDTH_BOUNDS.bottom)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeAllowedSize_shouldChangeBounds() {
doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(mockContext) }
initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
@@ -547,6 +713,61 @@
assertThat(repositionTaskBounds.height()).isLessThan(STABLE_BOUNDS.bottom)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
+ fun testMaxHeight_initialHeightMoreThanMax_decreasingHeight_resizeAllowed() {
+ mockWindowDecoration.mTaskInfo.isResizeable = false
+ val startingPoint = PointF(EXCEEDS_MAX_HEIGHT_BOUNDS.right.toFloat(),
+ EXCEEDS_MAX_HEIGHT_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect(EXCEEDS_MAX_HEIGHT_BOUNDS)
+
+ // Attempt to decrease height
+ val newX = EXCEEDS_MAX_HEIGHT_BOUNDS.right.toFloat() - 10
+ val newY = EXCEEDS_MAX_HEIGHT_BOUNDS.top.toFloat()
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ // Resize should be allowed as drag is decreasing height closer to valid region.
+ assertTrue(
+ DragPositioningCallbackUtility.changeBounds(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ repositionTaskBounds, EXCEEDS_MAX_HEIGHT_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration
+ )
+ )
+
+ assertThat(repositionTaskBounds.left).isEqualTo(EXCEEDS_MAX_HEIGHT_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(EXCEEDS_MAX_HEIGHT_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(EXCEEDS_MAX_HEIGHT_BOUNDS.right - 10)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(EXCEEDS_MAX_HEIGHT_BOUNDS.bottom )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
+ fun testMaxHeight_initialWidthMoreThanMax_decreasingBounds_resizeAllowed() {
+ val startingPoint = PointF(EXCEEDS_MAX_WIDTH_BOUNDS.left.toFloat(),
+ EXCEEDS_MAX_WIDTH_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect(EXCEEDS_MAX_WIDTH_BOUNDS)
+
+ // Attempt to decrease width.
+ val newX = EXCEEDS_MAX_WIDTH_BOUNDS.left.toFloat() + 20
+ val newY = EXCEEDS_MAX_WIDTH_BOUNDS.top.toFloat()
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ // Resize should be allowed as drag is decreasing width closer to valid region.
+ assertTrue(
+ DragPositioningCallbackUtility.changeBounds(
+ CTRL_TYPE_LEFT or CTRL_TYPE_TOP,
+ repositionTaskBounds, EXCEEDS_MAX_WIDTH_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration
+ )
+ )
+
+ assertThat(repositionTaskBounds.left).isEqualTo(EXCEEDS_MAX_WIDTH_BOUNDS.left + 20)
+ assertThat(repositionTaskBounds.top).isEqualTo(EXCEEDS_MAX_WIDTH_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(EXCEEDS_MAX_WIDTH_BOUNDS.right)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(EXCEEDS_MAX_WIDTH_BOUNDS.bottom)
+ }
+
private fun initializeTaskInfo(taskMinWidth: Int = MIN_WIDTH, taskMinHeight: Int = MIN_HEIGHT) {
mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
@@ -571,6 +792,10 @@
private const val NAVBAR_HEIGHT = 50
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ private val BELOW_MIN_WIDTH_BOUNDS = Rect(0, 0, 50, 100)
+ private val BELOW_MIN_HEIGHT_BOUNDS = Rect(0, 0, 100, 50)
+ private val EXCEEDS_MAX_WIDTH_BOUNDS = Rect(0, 0, 3000, 1500)
+ private val EXCEEDS_MAX_HEIGHT_BOUNDS = Rect(0, 0, 1000, 2000)
private val OFF_CENTER_STARTING_BOUNDS = Rect(-100, -100, 10, 10)
private val DISALLOWED_RESIZE_AREA = Rect(
DISPLAY_BOUNDS.left,
diff --git a/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg.xml b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg.xml
deleted file mode 100644
index 35517ea..0000000
--- a/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ 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.
- -->
-
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorAccentPrimary" />
- <corners android:radius="12dp" />
-</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml
index 18696c6..91a95a5 100644
--- a/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml
+++ b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml
@@ -16,6 +16,12 @@
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:color="?android:attr/colorControlHighlight">
- <item android:drawable="@drawable/audio_sharing_rounded_bg"/>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="4dp" />
+ <solid android:color="?androidprv:attr/colorAccentPrimary" />
+ </shape>
+ </item>
</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple_bottom.xml b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple_bottom.xml
new file mode 100644
index 0000000..cce8a75
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple_bottom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners
+ android:bottomLeftRadius="12dp"
+ android:bottomRightRadius="12dp"
+ android:topLeftRadius="4dp"
+ android:topRightRadius="4dp" />
+ <solid android:color="?androidprv:attr/colorAccentPrimary" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple_top.xml b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple_top.xml
new file mode 100644
index 0000000..1404197
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple_top.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners
+ android:bottomLeftRadius="4dp"
+ android:bottomRightRadius="4dp"
+ android:topLeftRadius="12dp"
+ android:topRightRadius="12dp" />
+ <solid android:color="?androidprv:attr/colorAccentPrimary" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
index 63661f6..4f315a2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -74,23 +74,7 @@
new AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(@NonNull AudioDeviceInfo[] addedDevices) {
- // Activate the last hot plugged valid input device, to match the output device
- // behavior.
- @AudioDeviceType int deviceTypeToActivate = mSelectedInputDeviceType;
- for (AudioDeviceInfo info : addedDevices) {
- if (InputMediaDevice.isSupportedInputDevice(info.getType())) {
- deviceTypeToActivate = info.getType();
- }
- }
-
- // Only activate if we find a different valid input device. e.g. if none of the
- // addedDevices is supported input device, we don't need to activate anything.
- if (mSelectedInputDeviceType != deviceTypeToActivate) {
- mSelectedInputDeviceType = deviceTypeToActivate;
- AudioDeviceAttributes deviceAttributes =
- createInputDeviceAttributes(mSelectedInputDeviceType);
- setPreferredDeviceForAllPresets(deviceAttributes);
- }
+ applyDefaultSelectedTypeToAllPresets();
}
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
index d808a25..782cee2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
@@ -24,7 +24,6 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -139,18 +138,6 @@
/* address= */ "");
}
- private AudioDeviceAttributes getUsbHeadsetDeviceAttributes() {
- return new AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_INPUT,
- AudioDeviceInfo.TYPE_USB_HEADSET,
- /* address= */ "");
- }
-
- private AudioDeviceAttributes getHdmiDeviceAttributes() {
- return new AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_HDMI, /* address= */ "");
- }
-
private void onPreferredDevicesForCapturePresetChanged(InputRouteManager inputRouteManager) {
final List<AudioDeviceAttributes> audioDeviceAttributesList =
new ArrayList<AudioDeviceAttributes>();
@@ -316,47 +303,21 @@
}
@Test
- public void onAudioDevicesAdded_shouldActivateAddedDevice() {
+ public void onAudioDevicesAdded_shouldApplyDefaultSelectedDeviceToAllPresets() {
final AudioManager audioManager = mock(AudioManager.class);
+ AudioDeviceAttributes wiredHeadsetDeviceAttributes = getWiredHeadsetDeviceAttributes();
+ when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
+ .thenReturn(Collections.singletonList(wiredHeadsetDeviceAttributes));
+
InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
AudioDeviceInfo[] devices = {mockWiredHeadsetInfo()};
inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
- // The only added wired headset will be activated.
+ // Called twice, one after initiation, the other after onAudioDevicesAdded call.
+ verify(audioManager, atLeast(2)).getDevicesForAttributes(INPUT_ATTRIBUTES);
for (@MediaRecorder.Source int preset : PRESETS) {
- verify(audioManager, atLeast(1))
- .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
- }
- }
-
- @Test
- public void onAudioDevicesAdded_shouldActivateLastAddedDevice() {
- final AudioManager audioManager = mock(AudioManager.class);
- InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
- AudioDeviceInfo[] devices = {mockWiredHeadsetInfo(), mockUsbHeadsetInfo()};
- inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
-
- // When adding multiple valid input devices, the last added device (usb headset in this
- // case) will be activated.
- for (@MediaRecorder.Source int preset : PRESETS) {
- verify(audioManager, never())
- .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
- verify(audioManager, atLeast(1))
- .setPreferredDeviceForCapturePreset(preset, getUsbHeadsetDeviceAttributes());
- }
- }
-
- @Test
- public void onAudioDevicesAdded_doNotActivateInvalidAddedDevice() {
- final AudioManager audioManager = mock(AudioManager.class);
- InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
- AudioDeviceInfo[] devices = {mockHdmiInfo()};
- inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
-
- // Do not activate since HDMI is not a valid input device.
- for (@MediaRecorder.Source int preset : PRESETS) {
- verify(audioManager, never())
- .setPreferredDeviceForCapturePreset(preset, getHdmiDeviceAttributes());
+ verify(audioManager, atLeast(2))
+ .setPreferredDeviceForCapturePreset(preset, wiredHeadsetDeviceAttributes);
}
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 1c29db1..7b8dddb 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1529,6 +1529,13 @@
}
flag {
+ name: "shade_window_goes_around"
+ namespace: "systemui"
+ description: "Enables the shade window to move between displays"
+ bug: "362719719"
+}
+
+flag {
name: "media_projection_request_attribution_fix"
namespace: "systemui"
description: "Ensure MediaProjection consent requests are properly attributed"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt
index 6da06d0..cd2dd04 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt
@@ -60,7 +60,7 @@
it.touchY,
progress / SCALE_FACTOR,
it.swipeEdge,
- it.frameTime,
+ it.frameTimeMillis,
)
onBackProgressedCompat(backEvent)
}
@@ -87,7 +87,7 @@
}
reset()
if (predictiveBackTimestampApi()) {
- downTime = backEvent.frameTime
+ downTime = backEvent.frameTimeMillis
}
onBackStartedCompat(backEvent)
}
@@ -98,7 +98,7 @@
velocityTracker.addMovement(
MotionEvent.obtain(
/* downTime */ downTime!!,
- /* eventTime */ backEvent.frameTime,
+ /* eventTime */ backEvent.frameTimeMillis,
/* action */ ACTION_MOVE,
/* x */ interpolatedProgress * SCALE_FACTOR,
/* y */ 0f,
@@ -111,7 +111,7 @@
backEvent.touchY,
interpolatedProgress,
backEvent.swipeEdge,
- backEvent.frameTime,
+ backEvent.frameTimeMillis,
)
} else {
lastBackEvent =
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
new file mode 100644
index 0000000..3115191
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2024 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.compose.ui.graphics
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.layer.GraphicsLayer
+import androidx.compose.ui.graphics.layer.drawLayer
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.modifier.ModifierLocalModifierNode
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.requireDensity
+import androidx.compose.ui.node.requireGraphicsContext
+import androidx.compose.ui.node.requireLayoutCoordinates
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.util.fastForEach
+
+/**
+ * Define this as a container into which other composables can be drawn using [drawInContainer].
+ *
+ * The elements redirected to this container will be drawn above the content of this composable.
+ */
+fun Modifier.container(state: ContainerState): Modifier {
+ return layout { measurable, constraints ->
+ val p = measurable.measure(constraints)
+ layout(p.width, p.height) {
+ val coords = coordinates
+ if (coords != null && !isLookingAhead) {
+ state.lastCoords = coords
+ }
+
+ p.place(0, 0)
+ }
+ }
+ .drawWithContent {
+ drawContent()
+ state.drawInOverlay(this)
+ }
+}
+
+/**
+ * Draw this composable into the container associated to [state].
+ *
+ * @param state the state of the container into which we should draw this composable.
+ * @param enabled whether the redirection of the drawing to the container is enabled.
+ * @param zIndex the z-index of the composable in the container.
+ * @param clipPath the clip path applied when drawing this composable into the container.
+ */
+fun Modifier.drawInContainer(
+ state: ContainerState,
+ enabled: () -> Boolean = { true },
+ zIndex: Float = 0f,
+ clipPath: (LayoutDirection, Density) -> Path? = { _, _ -> null },
+): Modifier {
+ return this.then(
+ DrawInContainerElement(
+ state = state,
+ enabled = enabled,
+ zIndex = zIndex,
+ clipPath = clipPath,
+ )
+ )
+}
+
+class ContainerState {
+ private var renderers = mutableStateListOf<LayerRenderer>()
+ internal var lastCoords: LayoutCoordinates? = null
+
+ internal fun onLayerRendererAttached(renderer: LayerRenderer) {
+ renderers.add(renderer)
+ renderers.sortBy { it.zIndex }
+ }
+
+ internal fun onLayerRendererDetached(renderer: LayerRenderer) {
+ renderers.remove(renderer)
+ }
+
+ internal fun drawInOverlay(drawScope: DrawScope) {
+ renderers.fastForEach { it.drawInOverlay(drawScope) }
+ }
+}
+
+internal interface LayerRenderer {
+ val zIndex: Float
+
+ fun drawInOverlay(drawScope: DrawScope)
+}
+
+private data class DrawInContainerElement(
+ var state: ContainerState,
+ var enabled: () -> Boolean,
+ val zIndex: Float,
+ val clipPath: (LayoutDirection, Density) -> Path?,
+) : ModifierNodeElement<DrawInContainerNode>() {
+ override fun create(): DrawInContainerNode {
+ return DrawInContainerNode(state, enabled, zIndex, clipPath)
+ }
+
+ override fun update(node: DrawInContainerNode) {
+ node.state = state
+ node.enabled = enabled
+ node.zIndex = zIndex
+ node.clipPath = clipPath
+ }
+}
+
+/**
+ * The implementation of [drawInContainer].
+ *
+ * Note: this was forked from AndroidX RenderInTransitionOverlayNodeElement.kt
+ * (http://shortn/_3dfSFPbm8f).
+ */
+internal class DrawInContainerNode(
+ var state: ContainerState,
+ var enabled: () -> Boolean = { true },
+ zIndex: Float = 0f,
+ var clipPath: (LayoutDirection, Density) -> Path? = { _, _ -> null },
+) : Modifier.Node(), DrawModifierNode, ModifierLocalModifierNode {
+ var zIndex by mutableFloatStateOf(zIndex)
+
+ private inner class LayerWithRenderer(val layer: GraphicsLayer) : LayerRenderer {
+ override val zIndex: Float
+ get() = this@DrawInContainerNode.zIndex
+
+ override fun drawInOverlay(drawScope: DrawScope) {
+ if (enabled()) {
+ with(drawScope) {
+ val containerCoords =
+ checkNotNull(state.lastCoords) { "container is not placed" }
+ val (x, y) =
+ requireLayoutCoordinates().positionInWindow() -
+ containerCoords.positionInWindow()
+ val clipPath = clipPath(layoutDirection, requireDensity())
+ if (clipPath != null) {
+ clipPath(clipPath) { translate(x, y) { drawLayer(layer) } }
+ } else {
+ translate(x, y) { drawLayer(layer) }
+ }
+ }
+ }
+ }
+ }
+
+ // Render in-place logic. Depending on the result of `renderInOverlay()`, the content will
+ // either render in-place or in the overlay, but never in both places.
+ override fun ContentDrawScope.draw() {
+ val layer = requireNotNull(layer) { "Error: layer never initialized" }
+ layer.record { this@draw.drawContent() }
+ if (!enabled()) {
+ drawLayer(layer)
+ }
+ }
+
+ val layer: GraphicsLayer?
+ get() = layerWithRenderer?.layer
+
+ private var layerWithRenderer: LayerWithRenderer? = null
+
+ override fun onAttach() {
+ LayerWithRenderer(requireGraphicsContext().createGraphicsLayer()).let {
+ state.onLayerRendererAttached(it)
+ layerWithRenderer = it
+ }
+ }
+
+ override fun onDetach() {
+ layerWithRenderer?.let {
+ state.onLayerRendererDetached(it)
+ requireGraphicsContext().releaseGraphicsLayer(it.layer)
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt
new file mode 100644
index 0000000..f5c3a83
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInOverlay.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 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.compose.ui.graphics
+
+import android.view.View
+import android.view.ViewGroupOverlay
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCompositionContext
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.unit.IntSize
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.findViewTreeViewModelStoreOwner
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.lifecycle.setViewTreeViewModelStoreOwner
+import androidx.savedstate.findViewTreeSavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+
+/**
+ * Draw this composable in the [overlay][ViewGroupOverlay] of the [current ComposeView][LocalView].
+ */
+@Composable
+fun Modifier.drawInOverlay(): Modifier {
+ val containerState = remember { ContainerState() }
+ val context = LocalContext.current
+ val localView = LocalView.current
+ val compositionContext = rememberCompositionContext()
+ val displayMetrics = context.resources.displayMetrics
+ val displaySize = IntSize(displayMetrics.widthPixels, displayMetrics.heightPixels)
+
+ DisposableEffect(containerState, context, localView, compositionContext, displaySize) {
+ val overlay = localView.rootView.overlay as ViewGroupOverlay
+ val view =
+ ComposeView(context).apply {
+ setParentCompositionContext(compositionContext)
+
+ // Set the owners.
+ setViewTreeLifecycleOwner(localView.findViewTreeLifecycleOwner())
+ setViewTreeViewModelStoreOwner(localView.findViewTreeViewModelStoreOwner())
+ setViewTreeSavedStateRegistryOwner(localView.findViewTreeSavedStateRegistryOwner())
+
+ setContent { Box(Modifier.fillMaxSize().container(containerState)) }
+ }
+
+ overlay.add(view)
+
+ // Make the ComposeView as big as the display. We have to manually measure and layout the
+ // View given that there is no layout pass in Android overlays.
+ view.measure(
+ View.MeasureSpec.makeSafeMeasureSpec(displaySize.width, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeSafeMeasureSpec(displaySize.height, View.MeasureSpec.EXACTLY),
+ )
+ view.layout(0, 0, displaySize.width, displaySize.height)
+
+ onDispose { overlay.remove(view) }
+ }
+
+ return this.drawInContainer(containerState, enabled = { true })
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
index 6e883c2..9e20e7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyboard.shortcut.data.repository
import android.hardware.input.fakeInputManager
+import android.view.KeyEvent.KEYCODE_1
import android.view.KeyEvent.KEYCODE_A
import android.view.KeyEvent.KEYCODE_B
import android.view.KeyEvent.KEYCODE_C
@@ -24,6 +25,7 @@
import android.view.KeyEvent.KEYCODE_E
import android.view.KeyEvent.KEYCODE_F
import android.view.KeyEvent.KEYCODE_G
+import android.view.KeyEvent.META_FUNCTION_ON
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -87,6 +89,48 @@
}
@Test
+ fun categories_keycodeAndModifiersAreMappedSeparatelyWhenIdentical() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(simpleGroup(simpleShortcutInfo(KEYCODE_1)))
+
+ helper.toggle(deviceId = 123)
+ val categories by collectLastValue(repo.categories)
+
+ val systemCategory = categories?.firstOrNull { it.type == ShortcutCategoryType.System }
+
+ // Keycode 0x8 should be translated to the Key 1 instead of modifier FN
+ // which has the same keycode.
+ val expectedCategory =
+ ShortcutCategory(
+ type = ShortcutCategoryType.System,
+ simpleSubCategory(simpleShortcut("1")),
+ )
+
+ assertThat(systemCategory).isEqualTo(expectedCategory)
+ }
+
+ @Test
+ fun categories_keyCodeAndModifierHaveSameCode_codesAreMappedCorrectly() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(simpleGroup(simpleShortcutInfo(KEYCODE_1, META_FUNCTION_ON)))
+
+ helper.toggle(deviceId = 123)
+ val categories by collectLastValue(repo.categories)
+
+ val systemCategory = categories?.firstOrNull { it.type == ShortcutCategoryType.System }
+
+ // Keycode 0x8 should be translated to the Key 1 instead of modifier FN
+ // which has the same keycode. while modifier mask 0x8 should be translated to FN.
+ val expectedCategory =
+ ShortcutCategory(
+ type = ShortcutCategoryType.System,
+ simpleSubCategory(simpleShortcut("Fn", "1")),
+ )
+
+ assertThat(systemCategory).isEqualTo(expectedCategory)
+ }
+
+ @Test
fun categories_multipleSubscribers_replaysExistingValueToNewSubscribers() =
testScope.runTest {
fakeSystemSource.setGroups(TestShortcuts.systemGroups)
@@ -111,24 +155,14 @@
testScope.runTest {
fakeSystemSource.setGroups(
listOf(
- simpleGroup(
- simpleShortcutInfo(KEYCODE_A),
- simpleShortcutInfo(KEYCODE_B),
- ),
- simpleGroup(
- simpleShortcutInfo(KEYCODE_C),
- ),
+ simpleGroup(simpleShortcutInfo(KEYCODE_A), simpleShortcutInfo(KEYCODE_B)),
+ simpleGroup(simpleShortcutInfo(KEYCODE_C)),
)
)
fakeMultiTaskingSource.setGroups(
listOf(
- simpleGroup(
- simpleShortcutInfo(KEYCODE_D),
- ),
- simpleGroup(
- simpleShortcutInfo(KEYCODE_E),
- simpleShortcutInfo(KEYCODE_F),
- ),
+ simpleGroup(simpleShortcutInfo(KEYCODE_D)),
+ simpleGroup(simpleShortcutInfo(KEYCODE_E), simpleShortcutInfo(KEYCODE_F)),
)
)
fakeAppCategoriesSource.setGroups(listOf(simpleGroup(simpleShortcutInfo(KEYCODE_G))))
@@ -144,16 +178,11 @@
listOf(
simpleSubCategory(simpleShortcut("B")),
simpleSubCategory(simpleShortcut("C")),
- )
+ ),
),
ShortcutCategory(
ShortcutCategoryType.MultiTasking,
- listOf(
- simpleSubCategory(
- simpleShortcut("E"),
- simpleShortcut("F"),
- ),
- )
+ listOf(simpleSubCategory(simpleShortcut("E"), simpleShortcut("F"))),
),
)
}
@@ -164,14 +193,14 @@
private fun simpleShortcut(vararg keys: String) =
Shortcut(
label = simpleShortcutLabel,
- commands = listOf(ShortcutCommand(keys.map { ShortcutKey.Text(it) }))
+ commands = listOf(ShortcutCommand(keys.map { ShortcutKey.Text(it) })),
)
private fun simpleGroup(vararg shortcuts: KeyboardShortcutInfo) =
KeyboardShortcutGroup(simpleGroupLabel, shortcuts.asList())
- private fun simpleShortcutInfo(keyCode: Int = 0) =
- KeyboardShortcutInfo(simpleShortcutLabel, keyCode, /* modifiers= */ 0)
+ private fun simpleShortcutInfo(keyCode: Int = 0, modifiers: Int = 0) =
+ KeyboardShortcutInfo(simpleShortcutLabel, keyCode, modifiers)
private val simpleShortcutLabel = "shortcut label"
private val simpleGroupLabel = "group label"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt
index 29e9ba7..d9d8169 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt
@@ -20,6 +20,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection.LEFT
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection.RIGHT
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
@@ -61,23 +63,46 @@
}
@Test
- fun triggersProgressRelativeToDistance() {
- assertProgressWhileMovingFingers(deltaX = -SWIPE_DISTANCE / 2, expectedProgress = 0.5f)
- assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE / 2, expectedProgress = 0.5f)
- assertProgressWhileMovingFingers(deltaX = -SWIPE_DISTANCE, expectedProgress = 1f)
- assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE, expectedProgress = 1f)
+ fun triggersProgressRelativeToDistanceWhenSwipingLeft() {
+ assertProgressWhileMovingFingers(
+ deltaX = -SWIPE_DISTANCE / 2,
+ expected = InProgress(progress = 0.5f, direction = LEFT),
+ )
+ assertProgressWhileMovingFingers(
+ deltaX = -SWIPE_DISTANCE,
+ expected = InProgress(progress = 1f, direction = LEFT),
+ )
}
- private fun assertProgressWhileMovingFingers(deltaX: Float, expectedProgress: Float) {
+ @Test
+ fun triggersProgressRelativeToDistanceWhenSwipingRight() {
+ assertProgressWhileMovingFingers(
+ deltaX = SWIPE_DISTANCE / 2,
+ expected = InProgress(progress = 0.5f, direction = RIGHT),
+ )
+ assertProgressWhileMovingFingers(
+ deltaX = SWIPE_DISTANCE,
+ expected = InProgress(progress = 1f, direction = RIGHT),
+ )
+ }
+
+ private fun assertProgressWhileMovingFingers(deltaX: Float, expected: InProgress) {
assertStateAfterEvents(
events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) },
- expectedState = InProgress(progress = expectedProgress),
+ expectedState = expected,
)
}
@Test
fun triggeredProgressIsNoBiggerThanOne() {
- assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE * 2, expectedProgress = 1f)
+ assertProgressWhileMovingFingers(
+ deltaX = SWIPE_DISTANCE * 2,
+ expected = InProgress(progress = 1f, direction = RIGHT),
+ )
+ assertProgressWhileMovingFingers(
+ deltaX = -SWIPE_DISTANCE * 2,
+ expected = InProgress(progress = 1f, direction = LEFT),
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
deleted file mode 100644
index faf01ed..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2024 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.volume.dialog.ringer.ui.viewmodel
-
-import android.media.AudioManager.RINGER_MODE_NORMAL
-import android.media.AudioManager.RINGER_MODE_SILENT
-import android.media.AudioManager.RINGER_MODE_VIBRATE
-import android.media.AudioManager.STREAM_RING
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.settingslib.volume.shared.model.RingerMode
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.haptics.fakeVibratorHelper
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.fakeVolumeDialogController
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
-class VolumeDialogRingerDrawerViewModelTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val controller = kosmos.fakeVolumeDialogController
- private val vibratorHelper = kosmos.fakeVibratorHelper
-
- private lateinit var underTest: VolumeDialogRingerDrawerViewModel
-
- @Before
- fun setUp() {
- underTest = kosmos.volumeDialogRingerDrawerViewModel
- }
-
- @Test
- fun onSelectedRingerNormalModeButtonClicked_openDrawer() =
- testScope.runTest {
- val ringerViewModel by collectLastValue(underTest.ringerViewModel)
- val normalRingerMode = RingerMode(RINGER_MODE_NORMAL)
-
- setUpRingerModeAndOpenDrawer(normalRingerMode)
-
- assertThat(ringerViewModel).isNotNull()
- assertThat(ringerViewModel?.drawerState)
- .isEqualTo(RingerDrawerState.Open(normalRingerMode))
- }
-
- @Test
- fun onSelectedRingerButtonClicked_drawerOpened_closeDrawer() =
- testScope.runTest {
- val ringerViewModel by collectLastValue(underTest.ringerViewModel)
- val normalRingerMode = RingerMode(RINGER_MODE_NORMAL)
-
- setUpRingerModeAndOpenDrawer(normalRingerMode)
- underTest.onRingerButtonClicked(normalRingerMode)
- controller.getState()
-
- assertThat(ringerViewModel).isNotNull()
- assertThat(ringerViewModel?.drawerState)
- .isEqualTo(RingerDrawerState.Closed(normalRingerMode))
- }
-
- @Test
- fun onNewRingerButtonClicked_drawerOpened_updateRingerMode_closeDrawer() =
- testScope.runTest {
- val ringerViewModel by collectLastValue(underTest.ringerViewModel)
- val vibrateRingerMode = RingerMode(RINGER_MODE_VIBRATE)
-
- setUpRingerModeAndOpenDrawer(RingerMode(RINGER_MODE_NORMAL))
- // Select vibrate ringer mode.
- underTest.onRingerButtonClicked(vibrateRingerMode)
- controller.getState()
- runCurrent()
-
- assertThat(ringerViewModel).isNotNull()
- assertThat(
- ringerViewModel
- ?.availableButtons
- ?.get(ringerViewModel!!.currentButtonIndex)
- ?.ringerMode
- )
- .isEqualTo(vibrateRingerMode)
- assertThat(ringerViewModel?.drawerState)
- .isEqualTo(RingerDrawerState.Closed(vibrateRingerMode))
-
- val silentRingerMode = RingerMode(RINGER_MODE_SILENT)
- // Open drawer
- underTest.onRingerButtonClicked(vibrateRingerMode)
- controller.getState()
-
- // Select silent ringer mode.
- underTest.onRingerButtonClicked(silentRingerMode)
- controller.getState()
- runCurrent()
-
- assertThat(ringerViewModel).isNotNull()
- assertThat(
- ringerViewModel
- ?.availableButtons
- ?.get(ringerViewModel!!.currentButtonIndex)
- ?.ringerMode
- )
- .isEqualTo(silentRingerMode)
- assertThat(ringerViewModel?.drawerState)
- .isEqualTo(RingerDrawerState.Closed(silentRingerMode))
- assertThat(controller.hasScheduledTouchFeedback).isFalse()
- assertThat(vibratorHelper.totalVibrations).isEqualTo(2)
- }
-
- private fun TestScope.setUpRingerModeAndOpenDrawer(selectedRingerMode: RingerMode) {
- controller.setStreamVolume(STREAM_RING, 50)
- controller.setRingerMode(selectedRingerMode.value, false)
- runCurrent()
-
- underTest.onRingerButtonClicked(RingerMode(selectedRingerMode.value))
- controller.getState()
- runCurrent()
- }
-}
diff --git a/packages/SystemUI/res/layout/contextual_edu_dialog.xml b/packages/SystemUI/res/layout/contextual_edu_dialog.xml
index ee42b23..7eb6efe 100644
--- a/packages/SystemUI/res/layout/contextual_edu_dialog.xml
+++ b/packages/SystemUI/res/layout/contextual_edu_dialog.xml
@@ -40,6 +40,5 @@
android:fontFamily="google-sans-medium"
android:maxWidth="280dp"
android:textColor="?androidprv:attr/materialColorOnTertiaryFixed"
- android:textSize="14sp"
- android:textStyle="bold" />
+ android:textSize="14sp" />
</LinearLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index 85bd0b0..a085887 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -68,7 +68,7 @@
@InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource,
@CurrentAppShortcuts private val currentAppShortcutsSource: KeyboardShortcutGroupsSource,
private val inputManager: InputManager,
- stateRepository: ShortcutHelperStateRepository
+ stateRepository: ShortcutHelperStateRepository,
) {
private val sources =
@@ -76,27 +76,27 @@
InternalGroupsSource(
source = systemShortcutsSource,
isTrusted = true,
- typeProvider = { System }
+ typeProvider = { System },
),
InternalGroupsSource(
source = multitaskingShortcutsSource,
isTrusted = true,
- typeProvider = { MultiTasking }
+ typeProvider = { MultiTasking },
),
InternalGroupsSource(
source = appCategoriesShortcutsSource,
isTrusted = true,
- typeProvider = { AppCategories }
+ typeProvider = { AppCategories },
),
InternalGroupsSource(
source = inputShortcutsSource,
isTrusted = false,
- typeProvider = { InputMethodEditor }
+ typeProvider = { InputMethodEditor },
),
InternalGroupsSource(
source = currentAppShortcutsSource,
isTrusted = false,
- typeProvider = { groups -> getCurrentAppShortcutCategoryType(groups) }
+ typeProvider = { groups -> getCurrentAppShortcutCategoryType(groups) },
),
)
@@ -179,7 +179,7 @@
shortcutGroup.items,
keepIcons,
supportedKeyCodes,
- )
+ ),
)
}
.filter { it.shortcuts.isNotEmpty() }
@@ -214,13 +214,13 @@
return Shortcut(
label = shortcutInfo.label!!.toString(),
icon = toShortcutIcon(keepIcon, shortcutInfo),
- commands = listOf(shortcutCommand)
+ commands = listOf(shortcutCommand),
)
}
private fun toShortcutIcon(
keepIcon: Boolean,
- shortcutInfo: KeyboardShortcutInfo
+ shortcutInfo: KeyboardShortcutInfo,
): ShortcutIcon? {
if (!keepIcon) {
return null
@@ -236,13 +236,13 @@
private fun toShortcutCommand(
keyCharacterMap: KeyCharacterMap,
- info: KeyboardShortcutInfo
+ info: KeyboardShortcutInfo,
): ShortcutCommand? {
val keys = mutableListOf<ShortcutKey>()
var remainingModifiers = info.modifiers
SUPPORTED_MODIFIERS.forEach { supportedModifier ->
if ((supportedModifier and remainingModifiers) != 0) {
- keys += toShortcutKey(keyCharacterMap, supportedModifier) ?: return null
+ keys += toShortcutModifierKey(supportedModifier) ?: return null
// "Remove" the modifier from the remaining modifiers
remainingModifiers = remainingModifiers and supportedModifier.inv()
}
@@ -262,6 +262,20 @@
return ShortcutCommand(keys)
}
+ private fun toShortcutModifierKey(modifierMask: Int): ShortcutKey? {
+ val iconResId = ShortcutHelperKeys.keyIcons[modifierMask]
+ if (iconResId != null) {
+ return ShortcutKey.Icon(iconResId)
+ }
+
+ val modifierLabel = ShortcutHelperKeys.modifierLabels[modifierMask]
+ if (modifierLabel != null) {
+ return ShortcutKey.Text(modifierLabel(context))
+ }
+ Log.wtf("TAG", "Couldn't find label or icon for modifier $modifierMask")
+ return null
+ }
+
private fun toShortcutKey(
keyCharacterMap: KeyCharacterMap,
keyCode: Int,
@@ -289,7 +303,7 @@
private suspend fun fetchSupportedKeyCodes(
deviceId: Int,
- groupsFromAllSources: List<List<KeyboardShortcutGroup>>
+ groupsFromAllSources: List<List<KeyboardShortcutGroup>>,
): Set<Int> =
withContext(backgroundDispatcher) {
val allUsedKeyCodes =
@@ -320,7 +334,7 @@
KeyEvent.META_ALT_ON,
KeyEvent.META_SHIFT_ON,
KeyEvent.META_SYM_ON,
- KeyEvent.META_FUNCTION_ON
+ KeyEvent.META_FUNCTION_ON,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
index 8db16fa..288efa2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
@@ -124,6 +124,17 @@
KEYCODE_RECENT_APPS to R.drawable.ic_check_box_outline_blank,
)
+ val modifierLabels =
+ mapOf<Int, (Context) -> String>(
+ // Modifiers
+ META_META_ON to { "Meta" },
+ META_CTRL_ON to { "Ctrl" },
+ META_ALT_ON to { "Alt" },
+ META_SHIFT_ON to { "Shift" },
+ META_SYM_ON to { "Sym" },
+ META_FUNCTION_ON to { "Fn" },
+ )
+
val specialKeyLabels =
mapOf<Int, (Context) -> String>(
KEYCODE_HOME to { context -> context.getString(R.string.keyboard_key_home) },
@@ -317,7 +328,7 @@
{ context ->
context.getString(
R.string.keyboard_key_numpad_template,
- context.getString(R.string.keyboard_key_enter)
+ context.getString(R.string.keyboard_key_enter),
)
},
KEYCODE_NUMPAD_EQUALS to
@@ -343,13 +354,5 @@
KEYCODE_CTRL_RIGHT to { "Ctrl" },
KEYCODE_SHIFT_LEFT to { "Shift" },
KEYCODE_SHIFT_RIGHT to { "Shift" },
-
- // Modifiers
- META_META_ON to { "Meta" },
- META_CTRL_ON to { "Ctrl" },
- META_ALT_ON to { "Alt" },
- META_SHIFT_ON to { "Shift" },
- META_SYM_ON to { "Sym" },
- META_FUNCTION_ON to { "Fn" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
new file mode 100644
index 0000000..c72db56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 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.shade
+
+import android.content.Context
+import android.content.res.Resources
+import android.view.LayoutInflater
+import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.systemui.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import dagger.Module
+import dagger.Provides
+
+/**
+ * Module responsible for managing display-specific components and resources for the notification
+ * shade window.
+ *
+ * This isolation is crucial because when the window transitions between displays, its associated
+ * context, resources, and display characteristics (like density and size) also change. If the shade
+ * window shared the same context as the rest of the system UI, it could lead to inconsistencies and
+ * errors due to incorrect display information.
+ *
+ * By using this dedicated module, we ensure the notification shade window always utilizes the
+ * correct display context and resources, regardless of the display it's on.
+ */
+@Module
+object ShadeDisplayAwareModule {
+
+ /** Creates a new context for the shade window. */
+ @Provides
+ @ShadeDisplayAware
+ @SysUISingleton
+ fun provideShadeDisplayAwareContext(context: Context): Context {
+ return if (Flags.shadeWindowGoesAround()) {
+ context
+ .createWindowContext(context.display, TYPE_APPLICATION_OVERLAY, /* options= */ null)
+ .apply { setTheme(R.style.Theme_SystemUI) }
+ } else {
+ context
+ }
+ }
+
+ @Provides
+ @ShadeDisplayAware
+ @SysUISingleton
+ fun provideShadeDisplayAwareResources(@ShadeDisplayAware context: Context): Resources {
+ return context.resources
+ }
+
+ @Provides
+ @ShadeDisplayAware
+ @SysUISingleton
+ fun providesDisplayAwareLayoutInflater(@ShadeDisplayAware context: Context): LayoutInflater {
+ return LayoutInflater.from(context)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 2348a11..6f5547a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -49,7 +49,10 @@
import javax.inject.Provider
/** Module for classes related to the notification shade. */
-@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
+@Module(
+ includes =
+ [StartShadeModule::class, ShadeViewProviderModule::class, ShadeDisplayAwareModule::class]
+)
abstract class ShadeModule {
companion object {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index ec1dc0a7..87b16ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1574,7 +1574,7 @@
if (mMaxDisplayedNotifications != -1) {
// The stack intrinsic height already contains the correct value when there is a limit
// in the max number of notifications (e.g. as in keyguard).
- height = mIntrinsicContentHeight;
+ height = mScrollViewFields.getIntrinsicStackHeight();
} else {
height = Math.max(0f, mAmbientState.getStackCutoff() - mAmbientState.getStackTop());
}
@@ -2610,7 +2610,7 @@
}
@VisibleForTesting
- void updateStackHeight() {
+ void updateIntrinsicStackHeight() {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
final int shelfIntrinsicHeight = mShelf != null ? mShelf.getIntrinsicHeight() : 0;
@@ -2621,8 +2621,11 @@
mMaxDisplayedNotifications,
shelfIntrinsicHeight
);
- mIntrinsicContentHeight = notificationsHeight;
- final int fullStackHeight = notificationsHeight + footerIntrinsicHeight + mBottomPadding;
+ // When there is a limit in the max number of notifications, we never display the footer.
+ final int fullStackHeight = mMaxDisplayedNotifications != -1
+ ? notificationsHeight
+ : notificationsHeight + footerIntrinsicHeight + mBottomPadding;
+
if (mScrollViewFields.getIntrinsicStackHeight() != fullStackHeight) {
mScrollViewFields.setIntrinsicStackHeight(fullStackHeight);
notifyStackHeightChangedListeners();
@@ -2631,7 +2634,7 @@
private void updateContentHeight() {
if (SceneContainerFlag.isEnabled()) {
- updateStackHeight();
+ updateIntrinsicStackHeight();
return;
}
@@ -5348,7 +5351,12 @@
public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
mMaxDisplayedNotifications = maxDisplayedNotifications;
- updateContentHeight();
+ if (SceneContainerFlag.isEnabled()) {
+ updateIntrinsicStackHeight();
+ updateStackEndHeightAndStackHeight(mAmbientState.getExpansionFraction());
+ } else {
+ updateContentHeight();
+ }
notifyHeightChangeListener(mShelf);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
index 024048c..35ea0ea 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
@@ -18,6 +18,9 @@
import android.util.MathUtils
import android.view.MotionEvent
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection.LEFT
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection.RIGHT
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
import kotlin.math.abs
/**
@@ -44,7 +47,13 @@
gestureStateChangedCallback,
gestureState,
isFinished = { abs(it.deltaX) >= gestureDistanceThresholdPx },
- progress = { MathUtils.saturate(abs(it.deltaX / gestureDistanceThresholdPx)) },
+ progress = ::getProgress,
)
}
+
+ private fun getProgress(it: Moving): InProgress {
+ val direction = if (it.deltaX > 0) RIGHT else LEFT
+ val value = MathUtils.saturate(abs(it.deltaX / gestureDistanceThresholdPx))
+ return InProgress(value, direction)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt
index b513c49..f27ddb5 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureState.kt
@@ -21,5 +21,11 @@
data object Finished : GestureState
- data class InProgress(val progress: Float = 0f) : GestureState
+ data class InProgress(val progress: Float = 0f, val direction: GestureDirection? = null) :
+ GestureState
+}
+
+enum class GestureDirection {
+ LEFT,
+ RIGHT,
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
index f194677..24f5d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
@@ -21,7 +21,7 @@
gestureStateChangedCallback: (GestureState) -> Unit,
gestureState: DistanceGestureState?,
isFinished: (Finished) -> Boolean,
- progress: (Moving) -> Float,
+ progress: (Moving) -> GestureState.InProgress,
) {
when (gestureState) {
is Finished -> {
@@ -32,7 +32,7 @@
}
}
is Moving -> {
- gestureStateChangedCallback(GestureState.InProgress(progress(gestureState)))
+ gestureStateChangedCallback(progress(gestureState))
}
is Started -> gestureStateChangedCallback(GestureState.InProgress())
else -> {}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
index b804b9a..e10b825 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
@@ -18,6 +18,7 @@
import android.util.MathUtils
import android.view.MotionEvent
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
/** Recognizes touchpad home gesture, that is - using three fingers on touchpad - swiping up. */
class HomeGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer {
@@ -40,7 +41,7 @@
gestureStateChangedCallback,
gestureState,
isFinished = { -it.deltaY >= gestureDistanceThresholdPx },
- progress = { MathUtils.saturate(-it.deltaY / gestureDistanceThresholdPx) },
+ progress = { InProgress(MathUtils.saturate(-it.deltaY / gestureDistanceThresholdPx)) },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
index 7d484ee..c478886 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
@@ -54,7 +54,9 @@
-state.deltaY >= gestureDistanceThresholdPx &&
abs(velocityTracker.calculateVelocity().value) <= velocityThresholdPxPerMs
},
- progress = { MathUtils.saturate(-it.deltaY / gestureDistanceThresholdPx) },
+ progress = {
+ GestureState.InProgress(MathUtils.saturate(-it.deltaY / gestureDistanceThresholdPx))
+ },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.kt
deleted file mode 100644
index 78d2d16..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2024 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.volume.dialog.ringer.ui.viewmodel
-
-import android.annotation.DrawableRes
-import android.annotation.StringRes
-import com.android.settingslib.volume.shared.model.RingerMode
-
-/** Models ringer button that corresponds to each ringer mode. */
-data class RingerButtonViewModel(
- /** Image resource id for the image button. */
- @DrawableRes val imageResId: Int,
- /** Content description for a11y. */
- @StringRes val contentDescriptionResId: Int,
- /** Hint label for accessibility use. */
- @StringRes val hintLabelResId: Int,
- /** Used to notify view model when button is clicked. */
- val ringerMode: RingerMode,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
deleted file mode 100644
index f321837..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 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.volume.dialog.ringer.ui.viewmodel
-
-import com.android.settingslib.volume.shared.model.RingerMode
-
-/** Models volume dialog ringer drawer state */
-sealed interface RingerDrawerState {
-
- /** When clicked to open drawer */
- data class Open(val mode: RingerMode) : RingerDrawerState
-
- /** When clicked to close drawer */
- data class Closed(val mode: RingerMode) : RingerDrawerState
-
- /** Initial state when volume dialog is shown with a closed drawer. */
- interface Initial : RingerDrawerState {
- companion object : Initial
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
deleted file mode 100644
index a09bfeb..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2024 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.volume.dialog.ringer.ui.viewmodel
-
-/** Models volume dialog ringer */
-data class RingerViewModel(
- /** List of the available buttons according to the available modes */
- val availableButtons: List<RingerButtonViewModel?>,
- /** The index of the currently selected button */
- val currentButtonIndex: Int,
- /** For open and close animations */
- val drawerState: RingerDrawerState,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
deleted file mode 100644
index ac82ae3..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2024 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.volume.dialog.ringer.ui.viewmodel
-
-import android.media.AudioAttributes
-import android.media.AudioManager.RINGER_MODE_NORMAL
-import android.media.AudioManager.RINGER_MODE_SILENT
-import android.media.AudioManager.RINGER_MODE_VIBRATE
-import android.os.VibrationEffect
-import com.android.settingslib.volume.shared.model.RingerMode
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.volume.Events
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
-import com.android.systemui.volume.dialog.ringer.domain.VolumeDialogRingerInteractor
-import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
-import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.stateIn
-
-private const val TAG = "VolumeDialogRingerDrawerViewModel"
-
-class VolumeDialogRingerDrawerViewModel
-@AssistedInject
-constructor(
- @VolumeDialog private val coroutineScope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
- private val interactor: VolumeDialogRingerInteractor,
- private val vibrator: VibratorHelper,
- private val volumeDialogLogger: VolumeDialogLogger,
-) {
-
- private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial)
-
- val ringerViewModel: Flow<RingerViewModel> =
- combine(interactor.ringerModel, drawerState) { ringerModel, state ->
- ringerModel.toViewModel(state)
- }
- .flowOn(backgroundDispatcher)
- .stateIn(coroutineScope, SharingStarted.Eagerly, null)
- .filterNotNull()
-
- // Vibration attributes.
- private val sonificiationVibrationAttributes =
- AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build()
-
- fun onRingerButtonClicked(ringerMode: RingerMode) {
- if (drawerState.value is RingerDrawerState.Open) {
- Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value)
- provideTouchFeedback(ringerMode)
- interactor.setRingerMode(ringerMode)
- }
- drawerState.value =
- when (drawerState.value) {
- is RingerDrawerState.Initial -> {
- RingerDrawerState.Open(ringerMode)
- }
- is RingerDrawerState.Open -> {
- RingerDrawerState.Closed(ringerMode)
- }
- is RingerDrawerState.Closed -> {
- RingerDrawerState.Open(ringerMode)
- }
- }
- }
-
- private fun provideTouchFeedback(ringerMode: RingerMode) {
- when (ringerMode.value) {
- RINGER_MODE_NORMAL -> {
- interactor.scheduleTouchFeedback()
- null
- }
- RINGER_MODE_SILENT -> VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
- RINGER_MODE_VIBRATE -> VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)
- else -> VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)
- }?.let { vibrator.vibrate(it, sonificiationVibrationAttributes) }
- }
-
- private fun VolumeDialogRingerModel.toViewModel(
- drawerState: RingerDrawerState
- ): RingerViewModel {
- val currentIndex = availableModes.indexOf(currentRingerMode)
- if (currentIndex == -1) {
- volumeDialogLogger.onCurrentRingerModeIsUnsupported(currentRingerMode)
- }
- return RingerViewModel(
- availableButtons = availableModes.map { mode -> toButtonViewModel(mode) },
- currentButtonIndex = currentIndex,
- drawerState = drawerState,
- )
- }
-
- private fun VolumeDialogRingerModel.toButtonViewModel(
- ringerMode: RingerMode
- ): RingerButtonViewModel? {
- return when (ringerMode.value) {
- RINGER_MODE_SILENT ->
- RingerButtonViewModel(
- imageResId = R.drawable.ic_speaker_mute,
- contentDescriptionResId = R.string.volume_ringer_status_silent,
- hintLabelResId = R.string.volume_ringer_hint_unmute,
- ringerMode = ringerMode,
- )
- RINGER_MODE_VIBRATE ->
- RingerButtonViewModel(
- imageResId = R.drawable.ic_volume_ringer_vibrate,
- contentDescriptionResId = R.string.volume_ringer_status_vibrate,
- hintLabelResId = R.string.volume_ringer_hint_vibrate,
- ringerMode = ringerMode,
- )
- RINGER_MODE_NORMAL ->
- when {
- isMuted && isEnabled ->
- RingerButtonViewModel(
- imageResId = R.drawable.ic_speaker_mute,
- contentDescriptionResId = R.string.volume_ringer_status_normal,
- hintLabelResId = R.string.volume_ringer_hint_unmute,
- ringerMode = ringerMode,
- )
-
- availableModes.contains(RingerMode(RINGER_MODE_VIBRATE)) ->
- RingerButtonViewModel(
- imageResId = R.drawable.ic_speaker_on,
- contentDescriptionResId = R.string.volume_ringer_status_normal,
- hintLabelResId = R.string.volume_ringer_hint_vibrate,
- ringerMode = ringerMode,
- )
-
- else ->
- RingerButtonViewModel(
- imageResId = R.drawable.ic_speaker_on,
- contentDescriptionResId = R.string.volume_ringer_status_normal,
- hintLabelResId = R.string.volume_ringer_hint_mute,
- ringerMode = ringerMode,
- )
- }
- else -> null
- }
- }
-
- @AssistedFactory
- interface Factory {
- fun create(): VolumeDialogRingerDrawerViewModel
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
index 9a3aa7e..59c38c0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
@@ -15,7 +15,6 @@
*/
package com.android.systemui.volume.dialog.shared
-import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.VolumeLog
@@ -44,13 +43,4 @@
{ "Dismiss: ${Events.DISMISS_REASONS[int1]}" },
)
}
-
- fun onCurrentRingerModeIsUnsupported(ringerMode: RingerMode) {
- logBuffer.log(
- TAG,
- LogLevel.DEBUG,
- { int1 = ringerMode.value },
- { "Current ringer mode: $int1, ringer mode is unsupported in ringer drawer options" },
- )
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
index 4eae3b9a..c7f5801 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
@@ -17,8 +17,11 @@
package com.android.systemui.volume.dialog.ui.utils
import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
import android.view.ViewPropertyAnimator
import kotlin.coroutines.resume
+import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
/**
@@ -39,11 +42,12 @@
}
override fun onAnimationEnd(animation: Animator) {
- continuation.resume(Unit)
+ continuation.resumeIfCan(Unit)
animationListener?.onAnimationEnd(animation)
}
override fun onAnimationCancel(animation: Animator) {
+ continuation.resumeIfCan(Unit)
animationListener?.onAnimationCancel(animation)
}
@@ -54,3 +58,30 @@
)
continuation.invokeOnCancellation { this.cancel() }
}
+
+/**
+ * Starts animation and suspends until it's finished. Cancels the animation if the running coroutine
+ * is cancelled.
+ */
+@Suppress("UNCHECKED_CAST")
+suspend fun <T> ValueAnimator.awaitAnimation(onValueChanged: (T) -> Unit) {
+ suspendCancellableCoroutine { continuation ->
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) = continuation.resumeIfCan(Unit)
+
+ override fun onAnimationCancel(animation: Animator) = continuation.resumeIfCan(Unit)
+ }
+ )
+ addUpdateListener { onValueChanged(it.animatedValue as T) }
+
+ start()
+ continuation.invokeOnCancellation { cancel() }
+ }
+}
+
+private fun <T> CancellableContinuation<T>.resumeIfCan(value: T) {
+ if (!isCancelled && !isCompleted) {
+ resume(value)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 87cda64..fdfc253 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -244,7 +244,7 @@
when(mStackSizeCalculator.computeHeight(eq(mStackScroller), anyInt(), anyFloat()))
.thenReturn((float) stackHeight);
- mStackScroller.updateStackHeight();
+ mStackScroller.updateIntrinsicStackHeight();
assertThat(mStackScroller.getIntrinsicStackHeight()).isEqualTo(stackHeight);
}
@@ -1218,7 +1218,38 @@
}
@Test
- @DisableSceneContainer // TODO(b/312473478): address disabled test
+ @EnableSceneContainer
+ public void testSetMaxDisplayedNotifications_updatesStackHeight() {
+ int fullStackHeight = 300;
+ int limitedStackHeight = 100;
+ int maxNotifs = 2; // any non-zero limit
+ float stackTop = 100;
+ float stackCutoff = 1100;
+ float stackViewPortHeight = stackCutoff - stackTop;
+ mStackScroller.setStackTop(stackTop);
+ mStackScroller.setStackCutoff(stackCutoff);
+ when(mStackSizeCalculator.computeHeight(eq(mStackScroller), eq(-1), anyFloat()))
+ .thenReturn((float) fullStackHeight);
+ when(mStackSizeCalculator.computeHeight(eq(mStackScroller), eq(maxNotifs), anyFloat()))
+ .thenReturn((float) limitedStackHeight);
+
+ // When we set a limit on max displayed notifications
+ mStackScroller.setMaxDisplayedNotifications(maxNotifs);
+
+ // Then
+ assertThat(mStackScroller.getIntrinsicStackHeight()).isEqualTo(limitedStackHeight);
+ assertThat(mAmbientState.getStackEndHeight()).isEqualTo(limitedStackHeight);
+
+ // When there is no limit on max displayed notifications
+ mStackScroller.setMaxDisplayedNotifications(-1);
+
+ // Then
+ assertThat(mStackScroller.getIntrinsicStackHeight()).isEqualTo(fullStackHeight);
+ assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackViewPortHeight);
+ }
+
+ @Test
+ @DisableSceneContainer
public void testSetMaxDisplayedNotifications_notifiesListeners() {
ExpandableView.OnHeightChangedListener listener =
mock(ExpandableView.OnHeightChangedListener.class);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
deleted file mode 100644
index db1c01a..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2024 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.volume.dialog.ringer.ui.viewmodel
-
-import com.android.systemui.haptics.vibratorHelper
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor
-import com.android.systemui.volume.dialog.shared.volumeDialogLogger
-
-val Kosmos.volumeDialogRingerDrawerViewModel by
- Kosmos.Fixture {
- VolumeDialogRingerDrawerViewModel(
- backgroundDispatcher = testDispatcher,
- coroutineScope = applicationCoroutineScope,
- interactor = volumeDialogRingerInteractor,
- vibrator = vibratorHelper,
- volumeDialogLogger = volumeDialogLogger,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.kt
deleted file mode 100644
index f9d4a99..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2024 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.volume.dialog.shared
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.log.logcatLogBuffer
-
-val Kosmos.volumeDialogLogger by Kosmos.Fixture { VolumeDialogLogger(logcatLogBuffer()) }